Skip to content

Commit

Permalink
Merge pull request #279 from easyops-cn/steve/search-context
Browse files Browse the repository at this point in the history
feat: support search context by paths
  • Loading branch information
weareoutman authored Oct 16, 2022
2 parents 9bf068d + 100a7b2 commit 4b18509
Show file tree
Hide file tree
Showing 19 changed files with 370 additions and 98 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,16 @@ module.exports = {
// ... Your other themes.
[
require.resolve("@easyops-cn/docusaurus-search-local"),
{
/** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */
({
// ... Your options.
// `hashed` is recommended as long-term-cache of index file is possible.
hashed: true,
// For Docs using Chinese, The `language` is recommended to set to:
// ```
// language: ["en", "zh"],
// ```
},
}),
],
],
};
Expand Down Expand Up @@ -92,6 +93,7 @@ module.exports = {
| docsPluginIdForPreferredVersion | string | | When you're using multi-instance of docs, set the docs plugin id which you'd like to check the preferred version with, for the search index. |
| zhUserDict | string | | Provide your custom dict for language of zh, [see here](https://github.com/fxsjy/jieba#%E8%BD%BD%E5%85%A5%E8%AF%8D%E5%85%B8) |
| zhUserDictPath | string | | Provide the file path to your custom dict for language of zh, E.g.: `path.resolve("./src/zh-dict.txt")` |
| searchContextByPaths | string[] | | Provide an list of sub-paths as separate search context, E.g.: `["docs", "community", "legacy/resources"]`. It will create multiple search indexes by these paths. |

### I18N

Expand Down
7 changes: 5 additions & 2 deletions docusaurus-search-local/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
"repository": "https://github.com/easyops-cn/docusaurus-search-local",
"homepage": "https://github.com/easyops-cn/docusaurus-search-local",
"scripts": {
"start": "concurrently -k -n client,server \"yarn run start:client\" \"yarn run start:server\"",
"start": "concurrently -k -n client,server,types \"yarn run start:client\" \"yarn run start:server\" \"yarn run start:types\"",
"start:client": "tsc --watch --project tsconfig.client.json",
"start:server": "tsc --watch --project tsconfig.server.json",
"build": "rimraf dist && yarn run build:client && yarn run build:server && yarn run copy-static-files",
"start:types": "tsc --project tsconfig.types.json --watch",
"build": "rimraf dist && yarn run build:client && yarn run build:server && yarn run build:types && yarn run copy-static-files",
"build:client": "tsc --project tsconfig.client.json",
"build:server": "tsc --project tsconfig.server.json",
"build:types": "tsc --project tsconfig.types.json",
"copy-static-files": "copyfiles -u 3 \"src/client/theme/**/*.css\" dist/client/client/theme && copyfiles -u 1 \"locales/*.json\" dist/locales"
},
"main": "dist/server/server/index.js",
"typings": "dist/types/index.d.ts",
"files": [
"/dist",
"!/dist/generated.js"
Expand Down
45 changes: 36 additions & 9 deletions docusaurus-search-local/src/client/theme/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
searchBarPosition,
docsPluginIdForPreferredVersion,
indexDocs,
searchContextByPaths,
} from "../../utils/proxiedGenerated";
import LoadingRing from "../LoadingRing/LoadingRing";

Expand Down Expand Up @@ -92,24 +93,46 @@ export default function SearchBar({
const history = useHistory();
const location = useLocation();
const searchBarRef = useRef<HTMLInputElement>(null);
const indexState = useRef("empty"); // empty, loaded, done
const indexStateMap = useRef(new Map<string, "loading" | "done">());
// Should the input be focused after the index is loaded?
const focusAfterIndexLoaded = useRef(false);
const [loading, setLoading] = useState(false);
const [inputChanged, setInputChanged] = useState(false);
const [inputValue, setInputValue] = useState("");
const search = useRef<any>(null);

const prevSearchContext = useRef<string>("");
const [searchContext, setSearchContext] = useState<string>("");
useEffect(() => {
if (!Array.isArray(searchContextByPaths)) {
return;
}
let nextSearchContext = "";
if (location.pathname.startsWith(versionUrl)) {
const uri = location.pathname.substring(versionUrl.length);
const matchedPath = searchContextByPaths.find(path => uri === path || uri.startsWith(`${path}/`));
if (matchedPath) {
nextSearchContext = matchedPath;
}
}
if (prevSearchContext.current !== nextSearchContext) {
// Reset index state map once search context is changed.
indexStateMap.current.delete(nextSearchContext);
}
setSearchContext(nextSearchContext);
}, [location, setSearchContext, versionUrl]);

const loadIndex = useCallback(async () => {
if (indexState.current !== "empty") {
if (indexStateMap.current.get(searchContext)) {
// Do not load the index (again) if its already loaded or in the process of being loaded.
return;
}
indexState.current = "loading";
indexStateMap.current.set(searchContext, "loading");
search.current?.destroy();
setLoading(true);

const [{ wrappedIndexes, zhDictionary }, autoComplete] = await Promise.all([
fetchIndexes(versionUrl),
fetchIndexes(versionUrl, searchContext),
fetchAutoCompleteJS(),
]);

Expand Down Expand Up @@ -149,7 +172,11 @@ export default function SearchBar({
return;
}
const a = document.createElement("a");
const url = `${baseUrl}search?q=${encodeURIComponent(query)}`;
const url = `${baseUrl}search?q=${encodeURIComponent(query)}${
Array.isArray(searchContextByPaths)
? `&ctx=${encodeURIComponent(searchContext)}`
: ""
}`;
a.href = url;
a.textContent = translate({
id: "theme.SearchBar.seeAll",
Expand All @@ -158,7 +185,7 @@ export default function SearchBar({
a.addEventListener("click", (e) => {
if (!e.ctrlKey && !e.metaKey) {
e.preventDefault();
search.current.autocomplete.close();
search.current?.autocomplete.close();
history.push(url);
}
});
Expand Down Expand Up @@ -194,17 +221,17 @@ export default function SearchBar({
searchBarRef.current?.blur();
});

indexState.current = "done";
indexStateMap.current.set(searchContext, "done");
setLoading(false);

if (focusAfterIndexLoaded.current) {
const input = searchBarRef.current as HTMLInputElement;
if (input.value) {
search.current.autocomplete.open();
search.current?.autocomplete.open();
}
input.focus();
}
}, [baseUrl, versionUrl, history]);
}, [versionUrl, searchContext, baseUrl, history]);

useEffect(() => {
if (!Mark) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import lunr from "lunr";
import { fetchIndexes } from "./fetchIndexes";
import { legacyFetchIndexes as fetchIndexes } from "./fetchIndexes";

jest.mock("lunr");
jest.mock("../../utils/proxiedGenerated");
Expand Down Expand Up @@ -29,7 +29,7 @@ describe("fetchIndexes", () => {
mockFetch.mockResolvedValueOnce({
json: () => Promise.resolve([]),
});
const result = await fetchIndexes(baseUrl);
const result = await fetchIndexes(baseUrl, "");
expect(mockFetch).toBeCalledWith("/search-index.json?_=abc");
expect(result).toEqual({
wrappedIndexes: [],
Expand Down Expand Up @@ -58,8 +58,8 @@ describe("fetchIndexes", () => {
},
]),
});
const result = await fetchIndexes(baseUrl);
expect(mockFetch).toBeCalledWith("/search-index.json?_=abc");
const result = await fetchIndexes(baseUrl, "community");
expect(mockFetch).toBeCalledWith("/search-index-community.json?_=abc");
expect(result).toEqual({
wrappedIndexes: [
{
Expand All @@ -74,7 +74,7 @@ describe("fetchIndexes", () => {

test("development", async () => {
process.env.NODE_ENV = "development";
const result = await fetchIndexes(baseUrl);
const result = await fetchIndexes(baseUrl, "");
expect(mockFetch).not.toBeCalled();
expect(result).toEqual({
wrappedIndexes: [],
Expand Down
31 changes: 28 additions & 3 deletions docusaurus-search-local/src/client/theme/SearchBar/fetchIndexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,38 @@ interface SerializedIndex {
};
}

export async function fetchIndexes(baseUrl: string): Promise<{
export interface IndexesData {
wrappedIndexes: WrappedIndex[];
zhDictionary: string[];
}> {
}

const cache = new Map<string, Promise<IndexesData>>();

export function fetchIndexes(
baseUrl: string,
searchContext: string
): Promise<IndexesData> {
const cacheKey = `${baseUrl}${searchContext}`;
let promise = cache.get(cacheKey);
if (!promise) {
promise = legacyFetchIndexes(baseUrl, searchContext);
cache.set(cacheKey, promise);
}
return promise;
}

export async function legacyFetchIndexes(
baseUrl: string,
searchContext: string
): Promise<IndexesData> {
if (process.env.NODE_ENV === "production") {
const json = (await (
await fetch(`${baseUrl}${searchIndexUrl}`)
await fetch(
`${baseUrl}${searchIndexUrl.replace(
"{dir}",
searchContext ? `-${searchContext.replace(/\//g, "-")}` : ""
)}`
)
).json()) as SerializedIndex[];

const wrappedIndexes: WrappedIndex[] = json.map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { concatDocumentPath } from "../../utils/concatDocumentPath";
import {
docsPluginIdForPreferredVersion,
indexDocs,
searchContextByPaths,
} from "../../utils/proxiedGenerated";

import styles from "./SearchPage.module.css";
Expand Down Expand Up @@ -66,7 +67,7 @@ function SearchPageContent(): React.ReactElement {
}
}
const { selectMessage } = usePluralForm();
const { searchValue, updateSearchPath } = useSearchQuery();
const { searchValue, searchContext, updateSearchPath } = useSearchQuery();
const [searchQuery, setSearchQuery] = useState(searchValue);
const [searchSource, setSearchSource] =
useState<
Expand Down Expand Up @@ -127,13 +128,16 @@ function SearchPageContent(): React.ReactElement {

useEffect(() => {
async function doFetchIndexes() {
const { wrappedIndexes, zhDictionary } = await fetchIndexes(versionUrl);
const { wrappedIndexes, zhDictionary } = await fetchIndexes(
versionUrl,
searchContext
);
setSearchSource(() =>
SearchSourceFactory(wrappedIndexes, zhDictionary, 100)
);
}
doFetchIndexes();
}, [versionUrl]);
}, [searchContext, versionUrl]);

return (
<React.Fragment>
Expand Down
20 changes: 15 additions & 5 deletions docusaurus-search-local/src/client/theme/hooks/useSearchQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
import { useHistory, useLocation } from "@docusaurus/router";
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import {
searchContextByPaths,
} from "../../utils/proxiedGenerated";

const SEARCH_PARAM_QUERY = "q";
const SEARCH_PARAM_CONTEXT = "ctx";

function useSearchQuery(): any {
const history = useHistory();
Expand All @@ -18,11 +22,13 @@ function useSearchQuery(): any {
siteConfig: { baseUrl },
} = useDocusaurusContext();

const params = ExecutionEnvironment.canUseDOM ? new URLSearchParams(location.search) : null;
const searchValue = params?.get(SEARCH_PARAM_QUERY) || "";
const searchContext = params?.get(SEARCH_PARAM_CONTEXT) || "";

return {
searchValue:
(ExecutionEnvironment.canUseDOM &&
new URLSearchParams(location.search).get(SEARCH_PARAM_QUERY)) ||
"",
searchValue,
searchContext,
updateSearchPath: (searchValue: string) => {
const searchParams = new URLSearchParams(location.search);

Expand All @@ -37,8 +43,12 @@ function useSearchQuery(): any {
});
},
generateSearchPageLink: (searchValue: string) => {
const searchParams = new URLSearchParams(location.search);
const searchContext = searchParams.get(SEARCH_PARAM_CONTEXT) || "";
// Refer to https://github.com/facebook/docusaurus/pull/2838
return `${baseUrl}search?q=${encodeURIComponent(searchValue)}`;
return `${baseUrl}search?q=${encodeURIComponent(searchValue)}${
Array.isArray(searchContextByPaths) ? `&ctx=${encodeURIComponent(searchContext)}` : ""
}`;
},
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export let language = ["en", "zh"];
export let removeDefaultStopWordFilter = false;
export let removeDefaultStemmer = false;
export const searchIndexUrl = "search-index.json?_=abc";
export const searchIndexUrl = "search-index{dir}.json?_=abc";
export const searchResultLimits = 8;
export const searchResultContextMaxLength = 50;
export const explicitSearchResultPath = false;
Expand Down
1 change: 1 addition & 0 deletions docusaurus-search-local/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare module "*/generated.js" {
export const searchBarPosition: "left" | "right";
export const docsPluginIdForPreferredVersion: string;
export const indexDocs: boolean;
export const searchContextByPaths: string[];
// These below are for mocking only.
export const __setLanguage: (value: string[]) => void;
export const __setRemoveDefaultStopWordFilter: (value: boolean) => void;
Expand Down
Loading

0 comments on commit 4b18509

Please sign in to comment.