Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 65 additions & 16 deletions src/app/archives/SearchableArchives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,55 @@ import styles from './archive.module.scss'
import Link from 'next/link'
import PostMetadata from '@/components/PostMetadata'
import type { ClientSafePost } from '@/api/datasource/types/resource/Post'
import type { FuseResult } from 'fuse.js'
import Fuse from 'fuse.js'


const ArchiveItem: React.FC<{post: ClientSafePost, style?: React.CSSProperties}> = ({ post, style }) => {
type HighlightKeywordProps = {
item: FuseResult<ClientSafePost>
expectedKey: keyof ClientSafePost
}

const HighlightKeyword:React.FC<HighlightKeywordProps> = props => {
if (!props.item.matches) {
return <span>{props.item.item[props.expectedKey]}</span>
}
const match = props.item.matches.find(v => v.key === props.expectedKey)
if (!match || !match.value) {
return <span>{props.item.item[props.expectedKey]}</span>
}
const elements: React.ReactNode[] = []
let lastR = 0
for (const index of match.indices) {
const l = index[0]
if (l > 0) {
elements.push(match.value.substring(lastR, l))
}
lastR = index[1] + 1
elements.push(<span className="text-primary">{match.value.substring(l, lastR)}</span>)
}
if (lastR < match.value.length) {
elements.push(match.value.substring(lastR))
}

return (
<span>
{ elements.map(v => v) }
</span>
)
}

const ArchiveItem: React.FC<{post: FuseResult<ClientSafePost>, style?: React.CSSProperties}> = ({ post, style }) => {
return (
<div className={styles.timeline}>
<div className="absolute top-0 w-full" style={style}>
<div className="top-0 w-full" style={style}>
<div className={styles.timelineTail}/>
<div className={styles.timelineContent}>
<div className="text-subtext">{post.formattedTime}</div>
<Link href={post.source} className="text-2xl font-bold mt-2.5 mb-2.5">{post.title}</Link>
<PostMetadata post={post} hideDate/>
<div className="text-subtext">{post.item.formattedTime}</div>
<Link href={post.item.source} className="text-2xl font-bold mt-2.5 mb-2.5">
<HighlightKeyword item={post} expectedKey="title"/>
</Link>
<PostMetadata post={post.item} hideDate/>
</div>
</div>
</div>
Expand All @@ -27,14 +64,24 @@ interface SearchableArchivesProps {
posts: ClientSafePost[]
}

/**
* 提供搜索功能.
*
* 动画构想:
*
* 搜索到结果后,所有
*/
const SearchableArchives: React.FC<SearchableArchivesProps> = props => {
const [archives, setArchives] = React.useState<ClientSafePost[]>(props.posts)
const [archives, setArchives] = React.useState<FuseResult<ClientSafePost>[]>(() =>
props.posts.map((v, i) => ({ item: v, refIndex: i }))
)
const lastSearchTimeout = useRef<ReturnType<typeof setTimeout>>()
const fuse = useRef<Fuse<ClientSafePost>>()

useEffect(() => {
fuse.current = new Fuse(props.posts, {
keys: ['title', 'categories', 'tags']
keys: ['title', 'categories', 'tags'],
includeMatches: true
})
}, [props.posts])

Expand All @@ -45,21 +92,23 @@ const SearchableArchives: React.FC<SearchableArchivesProps> = props => {
}
const value = e.currentTarget.value
lastSearchTimeout.current = setTimeout(() => {
if (value.length === 0) {
setArchives(props.posts)
} else {
setArchives(f.search(value).map(i => i.item))
}
requestAnimationFrame(() => {
if (value.length === 0) {
setArchives(props.posts.map((v, i) => ({ item: v, refIndex: i })))
} else {
setArchives(f.search(value))
}
})
}, 100)
}

return (
<div>
<input className={styles.searchBar} placeholder="搜索" onInput={onInput}/>
<div className="relative">
<div className="relative" >
{
archives.map((p, i) => (
<ArchiveItem style={{ transform: `translateY(${i * 180}px)` }} key={p.id} post={p} />
archives.map((p) => (
<ArchiveItem key={p.item.id} post={p} />
))
}
</div>
Expand Down
18 changes: 8 additions & 10 deletions src/app/archives/archive.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,22 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.9, 0.95);
}
to {
opacity: 1;
transform: scale(1, 1);
}
}

.cardIn {
animation: fadeIn .8s 1;
}

.timeline {
width: 100%;
> div {
transition: all 0.8s;
animation: 1s fadeIn 1;
}
&:after {
content: '';
height: px2Rem(180px);
width: 1px;
display: block;
}
margin-bottom: px2Rem(30px);
animation: fadeIn .8s 1;
}

.timelineTail {
Expand Down
Loading