From 14e15c3d1a5087cda5012123acf4205b369dfe2c Mon Sep 17 00:00:00 2001 From: weareoutman Date: Thu, 23 May 2024 15:39:12 +0800 Subject: [PATCH] feat: support search based on description and keywords closes #344 --- .../theme/SearchBar/SuggestionTemplate.ts | 34 +++++++++----- .../client/theme/SearchPage/SearchPage.tsx | 43 +++++++++++------ .../src/client/utils/SearchSourceFactory.ts | 3 +- .../processTreeStatusOfSearchResults.spec.ts | 46 +++++++++++++++---- .../utils/processTreeStatusOfSearchResults.ts | 14 +++++- .../client/utils/sortSearchResults.spec.ts | 21 +++++---- .../src/client/utils/sortSearchResults.ts | 28 ++++++----- .../src/server/utils/parse.spec.ts | 12 ++++- .../src/server/utils/parseDocument.spec.ts | 12 ++++- .../src/server/utils/parseDocument.ts | 4 +- .../src/server/utils/parsePage.spec.ts | 14 +++++- .../src/server/utils/parsePage.ts | 5 ++ .../src/server/utils/scanDocuments.spec.ts | 20 ++++++++ .../src/server/utils/scanDocuments.ts | 32 ++++++++++++- .../src/shared/interfaces.ts | 21 ++++++--- website/docs/intro.md | 6 +++ 16 files changed, 243 insertions(+), 72 deletions(-) diff --git a/docusaurus-search-local/src/client/theme/SearchBar/SuggestionTemplate.ts b/docusaurus-search-local/src/client/theme/SearchBar/SuggestionTemplate.ts index a8bbc3b8..d8a37a89 100644 --- a/docusaurus-search-local/src/client/theme/SearchBar/SuggestionTemplate.ts +++ b/docusaurus-search-local/src/client/theme/SearchBar/SuggestionTemplate.ts @@ -1,4 +1,8 @@ -import { SearchDocument, SearchResult } from "../../../shared/interfaces"; +import { + SearchDocument, + SearchDocumentType, + SearchResult, +} from "../../../shared/interfaces"; import { concatDocumentPath } from "../../utils/concatDocumentPath"; import { getStemmedPositions } from "../../utils/getStemmedPositions"; import { highlight } from "../../utils/highlight"; @@ -23,8 +27,10 @@ export function SuggestionTemplate({ isInterOfTree, isLastOfTree, }: Omit): string { - const isTitle = type === 0; - const isHeading = type === 1; + const isTitle = type === SearchDocumentType.Title; + const isKeywords = type === SearchDocumentType.Keywords; + const isTitleRelated = isTitle || isKeywords; + const isHeading = type === SearchDocumentType.Heading; const tree: string[] = []; if (isInterOfTree) { tree.push(iconTreeInter); @@ -35,22 +41,26 @@ export function SuggestionTemplate({ (item) => `${item}` ); const icon = `${ - isTitle ? iconTitle : isHeading ? iconHeading : iconContent + isTitleRelated ? iconTitle : isHeading ? iconHeading : iconContent }`; const wrapped = [ - `${highlightStemmed( - document.t, - getStemmedPositions(metadata, "t"), - tokens - )}`, + `${ + isKeywords + ? highlight(document.s!, tokens) + : highlightStemmed( + document.t, + getStemmedPositions(metadata, "t"), + tokens + ) + }`, ]; const needsExplicitHitPath = !isInterOfTree && !isLastOfTree && explicitSearchResultPath; if (needsExplicitHitPath) { const pathItems = page - ? (page.b ?? []) - .concat(page.t) + ? page.b + ?.concat(page.t) .concat(!document.s || document.s === page.t ? [] : document.s) : document.b; wrapped.push( @@ -58,7 +68,7 @@ export function SuggestionTemplate({ pathItems ?? [] )}` ); - } else if (!isTitle) { + } else if (!isTitleRelated) { wrapped.push( `${highlight( (page as SearchDocument).t || diff --git a/docusaurus-search-local/src/client/theme/SearchPage/SearchPage.tsx b/docusaurus-search-local/src/client/theme/SearchPage/SearchPage.tsx index 29981776..2a6f22b0 100644 --- a/docusaurus-search-local/src/client/theme/SearchPage/SearchPage.tsx +++ b/docusaurus-search-local/src/client/theme/SearchPage/SearchPage.tsx @@ -10,7 +10,11 @@ import clsx from "clsx"; import useSearchQuery from "../hooks/useSearchQuery"; import { fetchIndexes } from "../SearchBar/fetchIndexes"; import { SearchSourceFactory } from "../../utils/SearchSourceFactory"; -import { SearchDocument, SearchResult } from "../../../shared/interfaces"; +import { + SearchDocument, + SearchDocumentType, + SearchResult, +} from "../../../shared/interfaces"; import { highlight } from "../../utils/highlight"; import { highlightStemmed } from "../../utils/highlightStemmed"; import { getStemmedPositions } from "../../utils/getStemmedPositions"; @@ -109,7 +113,9 @@ function SearchPageContent(): React.ReactElement { useEffect(() => { async function doFetchIndexes() { const { wrappedIndexes, zhDictionary } = - !Array.isArray(searchContextByPaths) || searchContext || useAllContextsWithNoSearchContext + !Array.isArray(searchContextByPaths) || + searchContext || + useAllContextsWithNoSearchContext ? await fetchIndexes(versionUrl, searchContext) : { wrappedIndexes: [], zhDictionary: [] }; setSearchSource(() => @@ -245,13 +251,19 @@ function SearchResultItem({ }: { searchResult: SearchResult; }): React.ReactElement { - const isTitle = type === 0; - const isContent = type === 2; + const isTitle = type === SearchDocumentType.Title; + const isKeywords = type === SearchDocumentType.Keywords; + const isDescription = type === SearchDocumentType.Description; + const isDescriptionOrKeywords = isDescription || isKeywords; + const isTitleRelated = isTitle || isDescriptionOrKeywords; + const isContent = type === SearchDocumentType.Content; const pathItems = ( (isTitle ? document.b : (page as SearchDocument).b) as string[] ).slice(); - const articleTitle = (isContent ? document.s : document.t) as string; - if (!isTitle) { + const articleTitle = ( + isContent || isDescriptionOrKeywords ? document.s : document.t + ) as string; + if (!isTitleRelated) { pathItems.push((page as SearchDocument).t); } let search = ""; @@ -268,14 +280,15 @@ function SearchResultItem({ @@ -284,7 +297,7 @@ function SearchResultItem({ {concatDocumentPath(pathItems)}

)} - {isContent && ( + {(isContent || isDescription) && (

doc.i === document.p ), diff --git a/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.spec.ts b/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.spec.ts index 019aa844..f017c0e3 100644 --- a/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.spec.ts +++ b/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.spec.ts @@ -1,4 +1,7 @@ -import { InitialSearchResult } from "../../shared/interfaces"; +import { + InitialSearchResult, + SearchDocumentType, +} from "../../shared/interfaces"; import { processTreeStatusOfSearchResults } from "./processTreeStatusOfSearchResults"; describe("processTreeStatusOfSearchResults", () => { @@ -8,14 +11,21 @@ describe("processTreeStatusOfSearchResults", () => { document: { i: 100, }, - type: 0, + type: SearchDocumentType.Title, page: undefined, }, { document: { i: 200, }, - type: 0, + type: SearchDocumentType.Title, + page: undefined, + }, + { + document: { + i: 300, + }, + type: SearchDocumentType.Title, page: undefined, }, ] as InitialSearchResult[]; @@ -24,14 +34,14 @@ describe("processTreeStatusOfSearchResults", () => { document: { i: 1, }, - type: 2, + type: SearchDocumentType.Content, page: {}, }, { document: { i: 2, }, - type: 1, + type: SearchDocumentType.Heading, page: {}, }, pageTitles[0], @@ -39,14 +49,14 @@ describe("processTreeStatusOfSearchResults", () => { document: { i: 101, }, - type: 2, + type: SearchDocumentType.Content, page: pageTitles[0].document, }, { document: { i: 3, }, - type: 1, + type: SearchDocumentType.Heading, page: {}, }, pageTitles[1], @@ -54,19 +64,33 @@ describe("processTreeStatusOfSearchResults", () => { document: { i: 201, }, - type: 1, + type: SearchDocumentType.Heading, page: pageTitles[1].document, }, { document: { i: 202, }, - type: 2, + type: SearchDocumentType.Content, page: pageTitles[1].document, }, + { + document: { + i: 301, + }, + type: SearchDocumentType.Keywords, + page: pageTitles[2].document, + }, + { + document: { + i: 302, + }, + type: SearchDocumentType.Description, + page: pageTitles[2].document, + }, ] as InitialSearchResult[]; processTreeStatusOfSearchResults(results); - const statuses: [boolean, boolean][] = [ + const statuses: [boolean | undefined, boolean | undefined][] = [ [undefined, undefined], [undefined, undefined], [undefined, undefined], @@ -75,6 +99,8 @@ describe("processTreeStatusOfSearchResults", () => { [undefined, undefined], [true, undefined], [undefined, true], + [undefined, undefined], + [undefined, true], ]; results.forEach((item, i) => { expect([item.isInterOfTree, item.isLastOfTree]).toEqual(statuses[i]); diff --git a/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.ts b/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.ts index 9cd29603..2035601c 100644 --- a/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.ts +++ b/docusaurus-search-local/src/client/utils/processTreeStatusOfSearchResults.ts @@ -1,4 +1,7 @@ -import { InitialSearchResult } from "../../shared/interfaces"; +import { + InitialSearchResult, + SearchDocumentType, +} from "../../shared/interfaces"; export function processTreeStatusOfSearchResults( results: InitialSearchResult[] @@ -7,7 +10,14 @@ export function processTreeStatusOfSearchResults( if ( i > 0 && item.page && - results.some((prev) => prev.document === item.page) + results + .slice(0, i) + .some( + (prev) => + (prev.type === SearchDocumentType.Keywords + ? prev.page + : prev.document) === item.page + ) ) { if (i < results.length - 1 && results[i + 1].page === item.page) { item.isInterOfTree = true; diff --git a/docusaurus-search-local/src/client/utils/sortSearchResults.spec.ts b/docusaurus-search-local/src/client/utils/sortSearchResults.spec.ts index 952ce296..9476dc61 100644 --- a/docusaurus-search-local/src/client/utils/sortSearchResults.spec.ts +++ b/docusaurus-search-local/src/client/utils/sortSearchResults.spec.ts @@ -1,4 +1,7 @@ -import { InitialSearchResult } from "../../shared/interfaces"; +import { + InitialSearchResult, + SearchDocumentType, +} from "../../shared/interfaces"; import { sortSearchResults } from "./sortSearchResults"; describe("sortSearchResults", () => { @@ -8,14 +11,14 @@ describe("sortSearchResults", () => { document: { i: 100, }, - type: 0, + type: SearchDocumentType.Title, page: undefined, }, { document: { i: 200, }, - type: 0, + type: SearchDocumentType.Title, page: undefined, }, ] as InitialSearchResult[]; @@ -24,14 +27,14 @@ describe("sortSearchResults", () => { document: { i: 1, }, - type: 2, + type: SearchDocumentType.Content, page: {}, }, { document: { i: 2, }, - type: 1, + type: SearchDocumentType.Heading, page: {}, }, pageTitles[0], @@ -39,21 +42,21 @@ describe("sortSearchResults", () => { document: { i: 3, }, - type: 1, + type: SearchDocumentType.Heading, page: {}, }, { document: { i: 201, }, - type: 1, + type: SearchDocumentType.Heading, page: pageTitles[1].document, }, { document: { i: 202, }, - type: 2, + type: SearchDocumentType.Content, page: pageTitles[1].document, }, pageTitles[1], @@ -61,7 +64,7 @@ describe("sortSearchResults", () => { document: { i: 101, }, - type: 2, + type: SearchDocumentType.Description, page: pageTitles[0].document, }, ] as InitialSearchResult[]; diff --git a/docusaurus-search-local/src/client/utils/sortSearchResults.ts b/docusaurus-search-local/src/client/utils/sortSearchResults.ts index 41509cc8..7c41bdc6 100644 --- a/docusaurus-search-local/src/client/utils/sortSearchResults.ts +++ b/docusaurus-search-local/src/client/utils/sortSearchResults.ts @@ -1,20 +1,30 @@ -import { InitialSearchResult, SearchResult } from "../../shared/interfaces"; +import { + InitialSearchResult, + SearchDocumentType, + SearchResult, +} from "../../shared/interfaces"; export function sortSearchResults(results: InitialSearchResult[]): void { results.forEach((item, index) => { item.index = index; }); - // Put search results of headings and contents just after + // Put search results of headings/contents/descriptions just after // their belonged page's title, if existed. (results as SearchResult[]).sort((a, b) => { let aPageIndex = - a.type > 0 && a.page + (a.type === SearchDocumentType.Heading || + a.type === SearchDocumentType.Content || + a.type === SearchDocumentType.Description) && + a.page ? results.findIndex((item) => item.document === a.page) : a.index; let bPageIndex = - b.type > 0 && b.page + (b.type === SearchDocumentType.Heading || + b.type === SearchDocumentType.Content || + b.type === SearchDocumentType.Description) && + b.page ? results.findIndex((item) => item.document === b.page) : b.index; @@ -27,14 +37,10 @@ export function sortSearchResults(results: InitialSearchResult[]): void { } if (aPageIndex === bPageIndex) { - if (a.type === 0) { - return -1; - } - if (b.type === 0) { - return 1; - } - return a.index - b.index; + const diff = (b.type === 0 ? 1 : 0) - (a.type === 0 ? 1 : 0); + return diff === 0 ? a.index - b.index : diff; } + return aPageIndex - bPageIndex; }); } diff --git a/docusaurus-search-local/src/server/utils/parse.spec.ts b/docusaurus-search-local/src/server/utils/parse.spec.ts index 53d221f7..69eed58f 100644 --- a/docusaurus-search-local/src/server/utils/parse.spec.ts +++ b/docusaurus-search-local/src/server/utils/parse.spec.ts @@ -7,7 +7,11 @@ import { parse } from "./parse"; describe("parse", () => { test.each<[string, "docs" | "blog" | "page", ParsedDocument | null]>([ [ - ` + ` + + + +

Hello World

@@ -24,6 +28,8 @@ describe("parse", () => { "page", { pageTitle: "Hello World", + description: "Hello Description", + keywords: "Hello,Keywords", sections: [ { title: "Hello World", @@ -49,6 +55,8 @@ describe("parse", () => { "docs", { pageTitle: "Hello World", + description: "", + keywords: "", sections: [ { title: "Hello World", @@ -74,6 +82,8 @@ describe("parse", () => { "docs", { pageTitle: "Hello World", + description: "", + keywords: "", sections: [ { title: "Hello World", diff --git a/docusaurus-search-local/src/server/utils/parseDocument.spec.ts b/docusaurus-search-local/src/server/utils/parseDocument.spec.ts index c5522d65..b7033087 100644 --- a/docusaurus-search-local/src/server/utils/parseDocument.spec.ts +++ b/docusaurus-search-local/src/server/utils/parseDocument.spec.ts @@ -5,7 +5,11 @@ import { parseDocument } from "./parseDocument"; describe("parseDocument", () => { test.each<[string, ParsedDocument]>([ [ - ` + ` + + + +