| @@ -1,15 +1,16 @@ | |||||
| import { useQueryClient } from '@tanstack/react-query' | import { useQueryClient } from '@tanstack/react-query' | ||||
| import { useMemo } from 'react' | import { useMemo } from 'react' | ||||
| import { useNavigate } from 'react-router-dom' | |||||
| import { createPath, useNavigate } from 'react-router-dom' | |||||
| import { useOverlayStore } from '@/components/RouteBlockerOverlay' | import { useOverlayStore } from '@/components/RouteBlockerOverlay' | ||||
| import { prefetchForURL } from '@/lib/prefetchers' | import { prefetchForURL } from '@/lib/prefetchers' | ||||
| import { cn } from '@/lib/utils' | import { cn } from '@/lib/utils' | ||||
| import type { AnchorHTMLAttributes, FC, MouseEvent, TouchEvent } from 'react' | import type { AnchorHTMLAttributes, FC, MouseEvent, TouchEvent } from 'react' | ||||
| import type { To } from 'react-router-dom' | |||||
| type Props = AnchorHTMLAttributes<HTMLAnchorElement> & { | type Props = AnchorHTMLAttributes<HTMLAnchorElement> & { | ||||
| to: string | |||||
| to: To | |||||
| replace?: boolean | replace?: boolean | ||||
| className?: string | className?: string | ||||
| cancelOnError?: boolean } | cancelOnError?: boolean } | ||||
| @@ -25,7 +26,10 @@ export default (({ to, | |||||
| ...rest }: Props) => { | ...rest }: Props) => { | ||||
| const navigate = useNavigate () | const navigate = useNavigate () | ||||
| const qc = useQueryClient () | const qc = useQueryClient () | ||||
| const url = useMemo (() => (new URL (to, location.origin)).toString (), [to]) | |||||
| const url = useMemo (() => { | |||||
| const path = (typeof to === 'string') ? to : createPath (to) | |||||
| return (new URL (path, location.origin)).toString () | |||||
| }, [to]) | |||||
| const setOverlay = useOverlayStore (s => s.setActive) | const setOverlay = useOverlayStore (s => s.setActive) | ||||
| const doPrefetch = async () => { | const doPrefetch = async () => { | ||||
| @@ -74,7 +78,7 @@ export default (({ to, | |||||
| } | } | ||||
| return ( | return ( | ||||
| <a href={to} | |||||
| <a href={typeof to === 'string' ? to : createPath (to)} | |||||
| onMouseEnter={handleMouseEnter} | onMouseEnter={handleMouseEnter} | ||||
| onTouchStart={handleTouchStart} | onTouchStart={handleTouchStart} | ||||
| onClick={handleClick} | onClick={handleClick} | ||||
| @@ -1,5 +1,6 @@ | |||||
| import { Link } from 'react-router-dom' | import { Link } from 'react-router-dom' | ||||
| import PrefetchLink from '@/components/PrefetchLink' | |||||
| import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts' | import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts' | ||||
| import { cn } from '@/lib/utils' | import { cn } from '@/lib/utils' | ||||
| @@ -9,7 +10,8 @@ import type { Tag } from '@/types' | |||||
| type CommonProps = { tag: Tag | type CommonProps = { tag: Tag | ||||
| withWiki?: boolean | withWiki?: boolean | ||||
| withCount?: boolean } | |||||
| withCount?: boolean | |||||
| prefetch?: boolean } | |||||
| type PropsWithLink = | type PropsWithLink = | ||||
| CommonProps & { linkFlg?: true } & Partial<ComponentProps<typeof Link>> | CommonProps & { linkFlg?: true } & Partial<ComponentProps<typeof Link>> | ||||
| @@ -24,6 +26,7 @@ export default (({ tag, | |||||
| linkFlg = true, | linkFlg = true, | ||||
| withWiki = true, | withWiki = true, | ||||
| withCount = true, | withCount = true, | ||||
| prefetch = false, | |||||
| ...props }: Props) => { | ...props }: Props) => { | ||||
| const spanClass = cn ( | const spanClass = cn ( | ||||
| `text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`, | `text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`, | ||||
| @@ -44,11 +47,19 @@ export default (({ tag, | |||||
| </span>)} | </span>)} | ||||
| {linkFlg | {linkFlg | ||||
| ? ( | ? ( | ||||
| <Link to={`/posts?${ (new URLSearchParams ({ tags: tag.name })).toString () }`} | |||||
| prefetch | |||||
| ? <PrefetchLink | |||||
| to={`/posts?${ (new URLSearchParams ({ tags: tag.name })).toString () }`} | |||||
| className={linkClass} | className={linkClass} | ||||
| {...props}> | {...props}> | ||||
| {tag.name} | |||||
| </Link>) | |||||
| {tag.name} | |||||
| </PrefetchLink> | |||||
| : <Link | |||||
| to={`/posts?${ (new URLSearchParams ({ tags: tag.name })).toString () }`} | |||||
| className={linkClass} | |||||
| {...props}> | |||||
| {tag.name} | |||||
| </Link>) | |||||
| : ( | : ( | ||||
| <span className={spanClass} | <span className={spanClass} | ||||
| {...props}> | {...props}> | ||||
| @@ -10,16 +10,17 @@ import { API_BASE_URL } from '@/config' | |||||
| import { CATEGORIES } from '@/consts' | import { CATEGORIES } from '@/consts' | ||||
| import { cn } from '@/lib/utils' | import { cn } from '@/lib/utils' | ||||
| import type { FC } from 'react' | |||||
| import type { FC, MouseEvent } from 'react' | |||||
| import type { Post, Tag } from '@/types' | import type { Post, Tag } from '@/types' | ||||
| type TagByCategory = Record<string, Tag[]> | type TagByCategory = Record<string, Tag[]> | ||||
| type Props = { posts: Post[] } | |||||
| type Props = { posts: Post[] | |||||
| onClick?: (event: MouseEvent<HTMLElement>) => void } | |||||
| export default (({ posts }: Props) => { | |||||
| export default (({ posts, onClick }: Props) => { | |||||
| const navigate = useNavigate () | const navigate = useNavigate () | ||||
| const [tagsVsbl, setTagsVsbl] = useState (false) | const [tagsVsbl, setTagsVsbl] = useState (false) | ||||
| @@ -64,11 +65,11 @@ export default (({ posts }: Props) => { | |||||
| <div className={cn (!(tagsVsbl) && 'hidden', 'md:block mt-4')}> | <div className={cn (!(tagsVsbl) && 'hidden', 'md:block mt-4')}> | ||||
| <SectionTitle>タグ</SectionTitle> | <SectionTitle>タグ</SectionTitle> | ||||
| <ul> | <ul> | ||||
| {CATEGORIES.flatMap (cat => cat in tags ? ( | |||||
| {CATEGORIES.flatMap (cat => cat in tags ? | |||||
| tags[cat].map (tag => ( | tags[cat].map (tag => ( | ||||
| <li key={tag.id} className="mb-1"> | <li key={tag.id} className="mb-1"> | ||||
| <TagLink tag={tag}/> | |||||
| </li>))) : [])} | |||||
| <TagLink tag={tag} prefetch onClick={onClick}/> | |||||
| </li>)) : [])} | |||||
| </ul> | </ul> | ||||
| <SectionTitle>関聯</SectionTitle> | <SectionTitle>関聯</SectionTitle> | ||||
| {posts.length > 0 && ( | {posts.length > 0 && ( | ||||
| @@ -10,6 +10,7 @@ import WikiBody from '@/components/WikiBody' | |||||
| import TabGroup, { Tab } from '@/components/common/TabGroup' | import TabGroup, { Tab } from '@/components/common/TabGroup' | ||||
| import MainArea from '@/components/layout/MainArea' | import MainArea from '@/components/layout/MainArea' | ||||
| import { API_BASE_URL, SITE_TITLE } from '@/config' | import { API_BASE_URL, SITE_TITLE } from '@/config' | ||||
| import { fetchPosts } from '@/lib/posts' | |||||
| import type { Post, WikiPage } from '@/types' | import type { Post, WikiPage } from '@/types' | ||||
| @@ -28,13 +29,11 @@ export default () => { | |||||
| const loadMore = async (withCursor: boolean) => { | const loadMore = async (withCursor: boolean) => { | ||||
| setLoading (true) | setLoading (true) | ||||
| const res = await axios.get (`${ API_BASE_URL }/posts`, { | |||||
| params: { tags: tags.join (' '), | |||||
| match: anyFlg ? 'any' : 'all', | |||||
| limit: '20', | |||||
| ...(withCursor && { cursor }) } }) | |||||
| const data = toCamel (res.data as any, { deep: true }) as { posts: Post[] | |||||
| nextCursor: string } | |||||
| const data = await fetchPosts ({ | |||||
| tags: tags.join (' '), | |||||
| match: anyFlg ? 'any' : 'all', | |||||
| limit: 20, | |||||
| ...(withCursor && { cursor }) }) | |||||
| setPosts (posts => ( | setPosts (posts => ( | ||||
| [...((new Map ([...(withCursor ? posts : []), ...data.posts] | [...((new Map ([...(withCursor ? posts : []), ...data.posts] | ||||
| .map (post => [post.id, post]))) | .map (post => [post.id, post]))) | ||||
| @@ -111,7 +110,13 @@ export default () => { | |||||
| </title> | </title> | ||||
| </Helmet> | </Helmet> | ||||
| <TagSidebar posts={posts.slice (0, 20)}/> | |||||
| <TagSidebar posts={posts.slice (0, 20)} onClick={() => { | |||||
| const statesToSave = { | |||||
| posts, cursor, | |||||
| scroll: containerRef.current?.scrollTop ?? 0 } | |||||
| sessionStorage.setItem (`posts:${ tagsQuery }`, | |||||
| JSON.stringify (statesToSave)) | |||||
| }}/> | |||||
| <MainArea> | <MainArea> | ||||
| <TabGroup> | <TabGroup> | ||||