diff --git a/packages/doc/config/utils/create-base.ts b/packages/doc/config/utils/create-base.ts index 5b9e541c..62240c57 100644 --- a/packages/doc/config/utils/create-base.ts +++ b/packages/doc/config/utils/create-base.ts @@ -342,7 +342,6 @@ function createSearchJson(mdPaths) { const title = '`' + getTitleFromMd(mdstr) + '`' const ast = mdAst.parse(mdstr).children for (let j = 0; j < ast.length; j++) { - index++ const aa = ast[j] const item: any = { routePath: `${routePath}@${index}`, @@ -350,7 +349,10 @@ function createSearchJson(mdPaths) { doc: aa, belongMenu: findBelongMenu(routePath), } - const mdType = aa.type + let mdType = aa.type + if (mdType === 'Header') { + mdType = `H${aa.depth}` + } if (mdTypeMap[routePath][mdType] === undefined) { mdTypeMap[routePath][mdType] = 0 } else { @@ -360,6 +362,7 @@ function createSearchJson(mdPaths) { item.mdTypeIndex = mdTypeMap[routePath][mdType] result.push(item) + index++ } } @@ -372,7 +375,7 @@ function findBelongMenu(path) { const items = menu[i]?.items || [] for (let j = 0; j < items.length; j++) { if (items[j]?.path === path) { - return menu[i] + return items[j] } } } diff --git a/packages/doc/src/components/search/icons.tsx b/packages/doc/src/components/search/icons.tsx index aa378364..bbd7fc04 100644 --- a/packages/doc/src/components/search/icons.tsx +++ b/packages/doc/src/components/search/icons.tsx @@ -46,3 +46,29 @@ export const LinkIcon = (props) => ( ) + +export const LoadingIcon = (props) => { + return ( + + + + ) +} + +export const EmptyIcon = () => ( + + + + +) diff --git a/packages/doc/src/components/search/index.less b/packages/doc/src/components/search/index.less index a4b5cf84..1ad24bb9 100644 --- a/packages/doc/src/components/search/index.less +++ b/packages/doc/src/components/search/index.less @@ -38,6 +38,46 @@ width: 560px; border-radius: 16px; transform: translate(-50%); + + .loading-wrapper{ + width: 100%; + padding: 30px; + display: flex; + align-items: center; + justify-content: center; + + svg path { + fill: @primary-color !important; + } + svg { + animation: SEARCH_LOADING 1s infinite linear; + } + } + + .empty-wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 30px; + color: #4c5b6a; + + .empty-tips { + margin-top: 12px; + span { + font-weight: 500; + } + } + } + } + + @keyframes SEARCH_LOADING { + 0% { + transform: rotateZ(0); + } + 100% { + transform: rotateZ(360deg); + } } &-input { @@ -97,11 +137,16 @@ max-height: 120px; overflow: hidden; + .md-content-wrapper { + width: 100%; + height: 100%; + overflow: hidden; + } + .md-content { padding: 6px 8px; font-size: 14px; line-height: 1.5; - width: 100%; } * { diff --git a/packages/doc/src/components/search/index.tsx b/packages/doc/src/components/search/index.tsx index 139d0293..5be5186d 100644 --- a/packages/doc/src/components/search/index.tsx +++ b/packages/doc/src/components/search/index.tsx @@ -1,49 +1,54 @@ // @ts-ignore -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' import MarkdownIt from 'markdown-it' import hljs from 'highlight.js' import { routerEvent } from '../../utils/history' import * as search from '../../utils/search' -import { SearchIcon, ClearIcon, PageIcon, LinkIcon } from './icons' +import { useDebounce } from '../../hooks/index' +import { + SearchIcon, + ClearIcon, + PageIcon, + LinkIcon, + LoadingIcon, + EmptyIcon, +} from './icons' import './index.less' +const clsPre = 'antm-doc-search' const mdTypeToTag = { Table: 'table', Paragraph: 'p', CodeBlock: 'code', + H1: 'h1', + H2: 'h2', + H3: 'h3', + H4: 'h4', + H5: 'h5', + List: 'ul', } -const clsPre = 'antm-doc-search' const config = { highlight: function (str, lang) { return hljs.highlight(str, { language: lang || 'markdown' }).value }, html: true, } - const Markdown = MarkdownIt(config) type Iprops = { routeType?: 'hash' | 'history' } export default function Search(props: Iprops) { - const [show, setShow] = useState(true) + const [show, setShow] = useState(false) const [result, setResult] = useState({}) + const [words, setWords] = useState('') + const [loading, setLoading] = useState(false) + const [searchWords, setSearchWords] = useState('') + const inputRef = useRef() useEffect(() => { requestIdleCallback(async () => { await search.init() - const res = await search.run('规则') - - res.map((item) => { - const menuName = item.belongMenu.name - if (!result[menuName]) { - result[menuName] = [item] - } else { - result[menuName].push(item) - } - }) - - setResult({ ...result }) }) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -51,16 +56,65 @@ export default function Search(props: Iprops) { const handlePage = (path, docTarget) => { setShow(false) routerEvent.switch(path, props.routeType) - const mdType = docTarget.doc.type + let mdType = docTarget.doc.type + if (mdType === 'Header') { + mdType = `H${docTarget.doc.depth}` + } const tagType = mdTypeToTag[mdType] + console.info(docTarget) if (tagType) { - document - .querySelectorAll(`.antm-docs-markdown ${tagType}`) - [docTarget.mdTypeIndex]?.scrollIntoView() + const tt = document.querySelectorAll(`.antm-docs-markdown ${tagType}`)[ + docTarget.mdTypeIndex + ] + + const rect = tt?.getClientRects() || [] + window.scrollTo(0, rect[0]?.y ? rect[0]?.y - 50 : 0) } } + const handleSearch = useDebounce( + async (words) => { + setSearchWords(words) + if (!words) return + setLoading(true) + const res = await search.run(words) + const result = {} + + res.map((item) => { + const menuName = item.belongMenu.title + if (!result[menuName]) { + result[menuName] = [item] + } else { + result[menuName].push(item) + } + }) + + setResult(result) + setTimeout(() => { + setLoading(false) + }, 700) + }, + 1000, + [words], + ) + + const handleInput = (e) => { + const ws = e.target.value + setWords(ws) + handleSearch(ws) + } + + useEffect(() => { + if (!show) { + setWords('') + setSearchWords('') + } + if (show) { + inputRef.current?.focus() + } + }, [show]) + return ( <>
@@ -72,39 +126,61 @@ export default function Search(props: Iprops) {
- - + + setWords('')} />
- -
- {Object.keys(result).map((key, i) => { - const item = result[key] - return ( -
-
{key}
-
- {item.map((it, index) => ( -
handlePage(it.routePath, it)} - > - -
-
+ {loading && ( +
+ +
+ )} + {!loading && searchWords && Object.keys(result).length === 0 && ( +
+ +
+ 未找到关于 “{searchWords} “{' '} + 的搜索结果 +
+
+ )} + {!!Object.keys(result).length && !loading && ( +
+ {Object.keys(result).map((key, i) => { + const item = result[key] + return ( +
+
{key}
+
+ {item.map((it, index) => ( +
handlePage(it.routePath, it)} + > + +
+
+
+
+
+
- -
- ))} + ))} +
-
- ) - })} -
+ ) + })} +
+ )}
)} diff --git a/packages/doc/src/hooks/index.ts b/packages/doc/src/hooks/index.ts index c41a284b..81ac1d2c 100644 --- a/packages/doc/src/hooks/index.ts +++ b/packages/doc/src/hooks/index.ts @@ -1,3 +1,37 @@ +import { useRef, useEffect, useCallback } from 'react' + +export function useDebounce(fn, delay, dep: any[]) { + const { current } = useRef({ fn, timer: null }); + useEffect(function () { + current.fn = fn; + }, [fn]); + + return useCallback(function f(...args) { + if (current.timer) { + clearTimeout(current.timer); + } + current.timer = setTimeout(() => { + current.fn( ...args); + }, delay); + }, dep || []) +} + +export function useThrottle(fn, delay, dep: any[]) { + const { current } = useRef({ fn, timer: null }); + useEffect(function () { + current.fn = fn; + }, [fn]); + + return useCallback(function f(...args) { + if (!current.timer) { + current.timer = setTimeout(() => { + delete current.timer; + }, delay); + current.fn(...args); + } + }, dep || []); +} + export * from './usePersistFn' export * from './useTimeout' export * from './useDepsTimeout' diff --git a/packages/doc/src/utils/common.ts b/packages/doc/src/utils/common.ts index fe2745e5..1d877927 100644 --- a/packages/doc/src/utils/common.ts +++ b/packages/doc/src/utils/common.ts @@ -54,8 +54,6 @@ export function getSimulatorUrl(simulator: IDocSimulator, currentUrl: string) { path = simulator.transform(currentUrl) } - console.info(`${domain}${path}`) - return `${domain}${path}` } @@ -79,7 +77,6 @@ function sandBox(value) { } export function JSONparse(target) { - console.info(target, 'target') return JSON.parse(JSON.stringify(target), function (_, val) { if ( /^function\s*\(.*\)\s*{/.test(val) ||