Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support search context by paths #279

Merged
merged 2 commits into from
Oct 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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