Skip to content

Commit 0b9e153

Browse files
cijiugechucsr632
andauthored
feat(theme-doc): recent searches (#105)
* feat: recent searches * tweak Co-authored-by: csr632 <[email protected]>
1 parent 5f1f9c4 commit 0b9e153

File tree

1 file changed

+109
-39
lines changed
  • packages/theme-doc/src/Layout/Search

1 file changed

+109
-39
lines changed

packages/theme-doc/src/Layout/Search/index.tsx

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,87 @@ import { useThemeCtx } from '../..'
1313
import s from './search.module.less'
1414
import type { PageMeta } from '../../analyzeStaticData'
1515

16+
const recentSearchesKey = '__VITE_PAGES_RECENT_SEARCHES'
17+
18+
const getPagePosition = (page: PageMetaExtended) => {
19+
return [page.groupKey, page.subGroupKey, page.pageTitle]
20+
.filter((s) => s !== '/')
21+
.join(' > ')
22+
}
23+
24+
const hasInRecentSearches = (
25+
page: PageMetaExtended,
26+
recentSearches: SearchResultItem[]
27+
) => {
28+
return recentSearches
29+
.map((item) => item.page.pagePath)
30+
.includes(page.pagePath)
31+
}
32+
33+
const renderSearchResultItem = (
34+
type: 'title' | 'heading',
35+
matchedString: string,
36+
pagePosition: string
37+
) => {
38+
if (type === 'title') {
39+
return (
40+
<div className={s.searchResultLable}>
41+
<div className={s.searchResultLableIcon}>
42+
<NumberOutlined style={{ fontSize: '36px', color: '#1f1f1f' }} />
43+
</div>
44+
<div>
45+
<div className={s.searchResultMatchedText}>
46+
Title: {matchedString}
47+
</div>
48+
<div className={s.searchResultPagePosition}>{pagePosition}</div>
49+
</div>
50+
</div>
51+
)
52+
}
53+
if (type === 'heading') {
54+
return (
55+
<div className={s.searchResultLable}>
56+
<div className={s.searchResultLableIcon}>
57+
<ProfileOutlined style={{ fontSize: '36px', color: '#1f1f1f' }} />
58+
</div>
59+
<div>
60+
<div className={s.searchResultMatchedText}>
61+
Heading: {matchedString}
62+
</div>
63+
<div className={s.searchResultPagePosition}>{pagePosition}</div>
64+
</div>
65+
</div>
66+
)
67+
}
68+
69+
throw new Error('unexpected SearchResultItem: type ' + type)
70+
}
71+
72+
const calcRecentSearchesOptions = (recentSearches: SearchResultItem[]) => {
73+
const label = <p>Recent</p>
74+
75+
const options = recentSearches.map((item) => {
76+
const { type, page, matechedString } = item
77+
78+
const value = [
79+
type,
80+
page.pagePath,
81+
type === 'heading' ? item.headingId : '',
82+
matechedString,
83+
].join(' - ')
84+
85+
const rendered = (() => {
86+
const pagePosition = getPagePosition(page)
87+
88+
return renderSearchResultItem(type, matechedString, pagePosition)
89+
})()
90+
91+
return { value, label: rendered, result: item }
92+
})
93+
94+
return [{ label, options }]
95+
}
96+
1697
interface Props {}
1798

1899
// TODO: use https://github.com/nextapps-de/flexsearch to do full text search in browser
@@ -24,10 +105,16 @@ const Search: React.FC<React.PropsWithChildren<Props>> = (props) => {
24105
const { staticData, resolvedLocale, pageGroups } = useThemeCtx()
25106
const [popupOpen, setPopupOpen] = useState(false)
26107
const [keywords, setKeywords] = useState('')
108+
const [recentSearches, setRecentSearches] = useState<SearchResultItem[]>([])
27109
const navigate = useNavigate()
28110

29111
const allPagesOutlines = useAllPagesOutlines(2000)?.allPagesOutlines
30112

113+
const recentSearchesOptions = useMemo(
114+
() => calcRecentSearchesOptions(recentSearches),
115+
[recentSearches]
116+
)
117+
31118
const preparedPages = useMemo(() => {
32119
const res = [] as PageMetaExtended[]
33120
Object.entries(pageGroups).forEach(([groupKey, group]) => {
@@ -61,44 +148,9 @@ const Search: React.FC<React.PropsWithChildren<Props>> = (props) => {
61148
return filteredData.map((item) => {
62149
const { type, page, matechedString } = item
63150
const rendered = (() => {
64-
const pagePosition = [page.groupKey, page.subGroupKey, page.pageTitle]
65-
.filter((s) => s !== '/')
66-
.join(' > ')
67-
if (type === 'title') {
68-
return (
69-
<div className={s.searchResultLable}>
70-
<div className={s.searchResultLableIcon}>
71-
<NumberOutlined
72-
style={{ fontSize: '36px', color: '#1f1f1f' }}
73-
/>
74-
</div>
75-
<div>
76-
<div className={s.searchResultMatchedText}>
77-
Title: {matechedString}
78-
</div>
79-
<div className={s.searchResultPagePosition}>{pagePosition}</div>
80-
</div>
81-
</div>
82-
)
83-
}
84-
if (type === 'heading') {
85-
return (
86-
<div className={s.searchResultLable}>
87-
<div className={s.searchResultLableIcon}>
88-
<ProfileOutlined
89-
style={{ fontSize: '36px', color: '#1f1f1f' }}
90-
/>
91-
</div>
92-
<div>
93-
<div className={s.searchResultMatchedText}>
94-
Heading: {matechedString}
95-
</div>
96-
<div className={s.searchResultPagePosition}>{pagePosition}</div>
97-
</div>
98-
</div>
99-
)
100-
}
101-
throw new Error('unexpected SearchResultItem: type ' + type)
151+
const pagePosition = getPagePosition(page)
152+
153+
return renderSearchResultItem(type, matechedString, pagePosition)
102154
})()
103155
return {
104156
value: [
@@ -113,6 +165,14 @@ const Search: React.FC<React.PropsWithChildren<Props>> = (props) => {
113165
})
114166
}, [preparedPages, keywords])
115167

168+
useEffect(() => {
169+
const value = localStorage.getItem(recentSearchesKey)
170+
171+
if (value) {
172+
setRecentSearches(JSON.parse(value))
173+
}
174+
}, [])
175+
116176
return (
117177
<div className={s['search-box']}>
118178
<AutoComplete
@@ -121,13 +181,23 @@ const Search: React.FC<React.PropsWithChildren<Props>> = (props) => {
121181
getPopupContainer={(trigger) => trigger.parentElement}
122182
dropdownMatchSelectWidth={false}
123183
style={{ width: 200 }}
124-
options={options}
184+
options={keywords ? options : (recentSearchesOptions as any)}
125185
open={popupOpen}
126186
onDropdownVisibleChange={setPopupOpen}
127187
value={keywords}
128188
onSearch={setKeywords}
129189
onSelect={(value: any, option: any) => {
130190
const result: SearchResultItem = option.result
191+
192+
if (!hasInRecentSearches(result.page, recentSearches)) {
193+
setRecentSearches((prev) => [...prev, result])
194+
195+
localStorage.setItem(
196+
recentSearchesKey,
197+
JSON.stringify([...recentSearches, result])
198+
)
199+
}
200+
131201
if (result.type === 'title') {
132202
navigate(result.page.pagePath)
133203
} else if (result.type === 'heading') {

0 commit comments

Comments
 (0)