diff --git a/packages/guider/package.json b/packages/guider/package.json index 3a536b5..f6e07dd 100644 --- a/packages/guider/package.json +++ b/packages/guider/package.json @@ -1,6 +1,6 @@ { "name": "@neato/guider", - "version": "1.0.1", + "version": "1.0.2", "description": "Beautiful documentation sites, without all the hassle", "main": "./dist/index.js", "type": "module", diff --git a/packages/guider/src/client/partials/header/search/content.ts b/packages/guider/src/client/partials/header/search/content.ts index 8b305f4..7199bb2 100644 --- a/packages/guider/src/client/partials/header/search/content.ts +++ b/packages/guider/src/client/partials/header/search/content.ts @@ -8,6 +8,7 @@ type ContentDocument = { id: number; url: string; title: string; + pageTitle?: string; content: string; }, never[] @@ -21,6 +22,7 @@ type SearchData = Record< heading?: { id: string; depth: number; text: string }; content: string; }[]; + pageTitle: string; } >; @@ -28,6 +30,7 @@ export type SearchResult = { id: string; type: 'section'; title: string; + pageTitle?: string; content: string; url: string; }; @@ -53,7 +56,7 @@ async function fetchDocument(basePath: string, key: string) { document: { id: 'id', index: ['title', 'content'], - store: ['content', 'url', 'title'], + store: ['content', 'url', 'title', 'pageTitle'], }, context: { resolution: 9, @@ -69,6 +72,7 @@ async function fetchDocument(basePath: string, key: string) { searchDocument.add({ id: pageId, title: section.heading?.text ?? '', + pageTitle: data.pageTitle, url: url + (section.heading ? `#${section.heading.id}` : ''), content: section.content, }); @@ -113,6 +117,7 @@ export function useSearch(key: string) { type: 'section', id: res.id.toString(), title: res.doc.title, + pageTitle: res.doc.pageTitle, content: res.doc.content, url: res.doc.url, }; diff --git a/packages/guider/src/client/partials/header/search/screen.tsx b/packages/guider/src/client/partials/header/search/screen.tsx index 8d063f4..d251b1c 100644 --- a/packages/guider/src/client/partials/header/search/screen.tsx +++ b/packages/guider/src/client/partials/header/search/screen.tsx @@ -26,16 +26,22 @@ function SearchMessage(props: { title: string; text: string; icon: string }) { ); } -function SearchResults(props: { results: SearchResult[] }) { +function SearchResults(props: { + results: SearchResult[]; + onClose?: () => void; +}) { return (
{props.results.map((v) => { + let title = v.pageTitle ? `${v.pageTitle} / ${v.title}` : v.title; + if (v.pageTitle === v.title) title = v.title; return ( {({ active }) => (

- {v.title} + {title}

) : query.length > 0 && results ? ( - + ) : null}

diff --git a/packages/guider/src/webpack/loader/md-loader.ts b/packages/guider/src/webpack/loader/md-loader.ts index 741c98a..a7e2237 100644 --- a/packages/guider/src/webpack/loader/md-loader.ts +++ b/packages/guider/src/webpack/loader/md-loader.ts @@ -1,5 +1,6 @@ import { compile } from '@mdx-js/mdx'; import remarkFrontmatter from 'remark-frontmatter'; +import type { Heading } from '@vcarl/remark-headings'; import remarkHeadings from '@vcarl/remark-headings'; import remarkHeadingId from 'remark-heading-id'; import grayMatter from 'gray-matter'; @@ -8,10 +9,6 @@ import rehypeExtractExcerpt from 'rehype-extract-excerpt'; import remarkLinkRewrite from 'remark-link-rewrite'; import { remarkNpm2Yarn } from '@theguild/remark-npm2yarn'; import remarkGfm from 'remark-gfm'; -import { SKIP, visit } from 'unist-util-visit'; -import type { Heading, HeadingData, Root } from 'mdast'; -import { phrasing } from 'mdast-util-phrasing'; -import type { VFileWithOutput } from 'unified'; import { transformerNotationDiff, transformerNotationHighlight, @@ -19,14 +16,10 @@ import { transformerNotationWordHighlight, transformerNotationErrorLevel, } from '@shikijs/transformers'; +import { remarkSearchData } from './search-data'; const EXPORT_FOOTER = 'export default '; -interface Section { - heading?: { id: string; depth: Heading['depth']; text: string }; - content: string; -} - export async function mdLoader(source: string) { const meta = grayMatter(source); const file = await compile(source, { @@ -75,67 +68,7 @@ export async function mdLoader(source: string) { }, }, ], - () => { - return (root: Root, vFile: VFileWithOutput) => { - const sections: Section[] = []; - - let currentSection: Section | undefined; - let previousParentNode: any; - - visit(root, (node, _, parent) => { - if (node.type === 'heading') { - if (currentSection) { - sections.push(currentSection); - } - - const heading = node; - const id = (heading.data as HeadingData & { id: string })?.id; - const depth = heading.depth; - let text = ''; - visit(heading, ['text', 'inlineCode'], (hChild: any) => { - text += hChild.value; - }); - - currentSection = { - heading: { - id, - depth, - text, - }, - content: '', - }; - - return SKIP; - } - - const types = ['text', 'inlineCode']; - - if (types.includes(node.type)) { - if (!currentSection) { - currentSection = { - heading: undefined, - content: '', - }; - } - - if ( - previousParentNode && - previousParentNode !== parent && - !phrasing(previousParentNode) - ) { - currentSection.content += ' '; - } - currentSection.content += (node as any).value; - - previousParentNode = parent; - } - }); - - if (currentSection) sections.push(currentSection); - - vFile.data = { ...vFile.data, sections }; - }; - }, + remarkSearchData, ], rehypePlugins: [ rehypeExtractExcerpt, @@ -168,6 +101,10 @@ export async function mdLoader(source: string) { excerpt: file.data.excerpt, }; + const firstHeading = (file.data.headings as Heading[]).find( + (h) => h.depth === 1, + ); + const script = ` import { createMdxPage } from "@neato/guider/client"; @@ -185,6 +122,7 @@ export async function mdLoader(source: string) { script, searchData: { sections: file.data.sections, + pageTitle: meta.data?.title ?? firstHeading?.value ?? undefined, }, }; } diff --git a/packages/guider/src/webpack/loader/search-data.ts b/packages/guider/src/webpack/loader/search-data.ts new file mode 100644 index 0000000..ae76d43 --- /dev/null +++ b/packages/guider/src/webpack/loader/search-data.ts @@ -0,0 +1,75 @@ +import { SKIP, visit } from 'unist-util-visit'; +import type { Heading, Root } from 'mdast'; +import { phrasing } from 'mdast-util-phrasing'; +import type { VFileWithOutput } from 'unified'; + +interface Section { + heading?: { id: string; depth: Heading['depth']; text: string }; + content: string; +} + +declare module 'mdast' { + interface HeadingData { + id: string; + } +} + +export function remarkSearchData() { + return (root: Root, vFile: VFileWithOutput) => { + const sections: Section[] = []; + + let currentSection: Section | undefined; + let previousParentNode: any; + + visit(root, (node, _, parent) => { + if (node.type === 'heading') { + if (currentSection) { + sections.push(currentSection); + } + + const heading = node; + const id = heading.data?.id ?? ''; + const depth = heading.depth; + let text = ''; + visit(heading, ['text', 'inlineCode'], (hChild: any) => { + text += hChild.value; + }); + + currentSection = { + heading: { + id, + depth, + text, + }, + content: '', + }; + + return SKIP; + } + + if (node.type === 'text' || node.type === 'inlineCode') { + if (!currentSection) { + currentSection = { + heading: undefined, + content: '', + }; + } + + if ( + previousParentNode && + previousParentNode !== parent && + !phrasing(previousParentNode) + ) { + currentSection.content += ' '; + } + currentSection.content += node.value; + + previousParentNode = parent; + } + }); + + if (currentSection) sections.push(currentSection); + + vFile.data = { ...vFile.data, sections }; + }; +}