Skip to content

Commit 183e4b7

Browse files
authored
Merge pull request #386 from easyops-cn/steve/i18n-search-context-labels
feat: i18n for search context labels and force ignore noIndex
2 parents 0be2e80 + 09d0f30 commit 183e4b7

File tree

11 files changed

+187
-87
lines changed

11 files changed

+187
-87
lines changed

README.md

Lines changed: 29 additions & 28 deletions
Large diffs are not rendered by default.

docusaurus-search-local/src/client/theme/SearchBar/SearchBar.tsx

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ import {
3131
indexDocs,
3232
searchContextByPaths,
3333
hideSearchBarWithNoSearchContext,
34+
useAllContextsWithNoSearchContext,
3435
} from "../../utils/proxiedGenerated";
3536
import LoadingRing from "../LoadingRing/LoadingRing";
3637

3738
import styles from "./SearchBar.module.css";
39+
import { normalizeContextByPath } from "../../utils/normalizeContextByPath";
3840

3941
async function fetchAutoCompleteJS(): Promise<any> {
4042
const autoCompleteModule = await import("@easyops-cn/autocomplete.js");
@@ -62,6 +64,7 @@ export default function SearchBar({
6264
const isBrowser = useIsBrowser();
6365
const {
6466
siteConfig: { baseUrl },
67+
i18n: { currentLocale },
6568
} = useDocusaurusContext();
6669

6770
// It returns undefined for non-docs pages
@@ -161,39 +164,51 @@ export default function SearchBar({
161164
const a = document.createElement("a");
162165
const params = new URLSearchParams();
163166

164-
const seeAllResultsText = translate({
165-
id: "theme.SearchBar.seeAll",
166-
message: "See all results",
167-
});
168-
169-
const seeAllResultsOutsideContextText = translate(
170-
{
171-
id: "theme.SearchBar.seeAllOutsideContext",
172-
message: "See results outside {context}",
173-
},
174-
{ context: searchContext }
175-
);
176-
177-
const seeAllResultsInContextText = translate(
178-
{
179-
id: "theme.SearchBar.searchInContext",
180-
message: "See all results in {context}",
181-
},
182-
{ context: searchContext }
183-
);
184-
185167
params.set("q", query);
186168

187169
let linkText;
188-
if (searchContext && isEmpty) {
189-
linkText = seeAllResultsOutsideContextText;
190-
} else if (searchContext) {
191-
linkText = seeAllResultsInContextText;
170+
if (searchContext) {
171+
const detailedSearchContext =
172+
searchContext && Array.isArray(searchContextByPaths)
173+
? searchContextByPaths.find((item) =>
174+
typeof item === "string"
175+
? item === searchContext
176+
: item.path === searchContext
177+
)
178+
: searchContext;
179+
const translatedSearchContext = detailedSearchContext
180+
? normalizeContextByPath(detailedSearchContext, currentLocale).label
181+
: searchContext;
182+
183+
if (useAllContextsWithNoSearchContext && isEmpty) {
184+
linkText = translate(
185+
{
186+
id: "theme.SearchBar.seeAllOutsideContext",
187+
message: "See results outside {context}",
188+
},
189+
{ context: translatedSearchContext }
190+
);
191+
} else {
192+
linkText = translate(
193+
{
194+
id: "theme.SearchBar.searchInContext",
195+
message: "See all results in {context}",
196+
},
197+
{ context: translatedSearchContext }
198+
);
199+
}
192200
} else {
193-
linkText = seeAllResultsText;
201+
linkText = translate({
202+
id: "theme.SearchBar.seeAll",
203+
message: "See all results",
204+
});
194205
}
195206

196-
if (Array.isArray(searchContextByPaths) && !isEmpty) {
207+
if (
208+
searchContext &&
209+
Array.isArray(searchContextByPaths) &&
210+
(!useAllContextsWithNoSearchContext || !isEmpty)
211+
) {
197212
params.set("ctx", searchContext);
198213
}
199214

@@ -250,7 +265,10 @@ export default function SearchBar({
250265
suggestion: SuggestionTemplate,
251266
empty: EmptyTemplate,
252267
footer: ({ query, isEmpty }: any) => {
253-
if (isEmpty && !searchContext) {
268+
if (
269+
isEmpty &&
270+
(!searchContext || !useAllContextsWithNoSearchContext)
271+
) {
254272
return;
255273
}
256274
const a = searchFooterLinkElement({ query, isEmpty });

docusaurus-search-local/src/client/theme/SearchPage/SearchPage.tsx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from "../../utils/proxiedGenerated";
2424

2525
import styles from "./SearchPage.module.css";
26+
import { normalizeContextByPath } from "../../utils/normalizeContextByPath";
2627

2728
export default function SearchPage(): React.ReactElement {
2829
return (
@@ -35,6 +36,7 @@ export default function SearchPage(): React.ReactElement {
3536
function SearchPageContent(): React.ReactElement {
3637
const {
3738
siteConfig: { baseUrl },
39+
i18n: { currentLocale },
3840
} = useDocusaurusContext();
3941

4042
const { selectMessage } = usePluralForm();
@@ -106,10 +108,10 @@ function SearchPageContent(): React.ReactElement {
106108

107109
useEffect(() => {
108110
async function doFetchIndexes() {
109-
const { wrappedIndexes, zhDictionary } = await fetchIndexes(
110-
versionUrl,
111-
searchContext
112-
);
111+
const { wrappedIndexes, zhDictionary } =
112+
searchContext || useAllContextsWithNoSearchContext
113+
? await fetchIndexes(versionUrl, searchContext)
114+
: { wrappedIndexes: [], zhDictionary: [] };
113115
setSearchSource(() =>
114116
SearchSourceFactory(wrappedIndexes, zhDictionary, 100)
115117
);
@@ -166,22 +168,19 @@ function SearchPageContent(): React.ReactElement {
166168
value={searchContext}
167169
onChange={(e) => updateSearchContext(e.target.value)}
168170
>
169-
<option value="">
170-
{useAllContextsWithNoSearchContext
171-
? translate({
172-
id: "theme.SearchPage.searchContext.everywhere",
173-
message: "everywhere",
174-
})
175-
: ""}
176-
</option>
171+
{useAllContextsWithNoSearchContext && (
172+
<option value="">
173+
{translate({
174+
id: "theme.SearchPage.searchContext.everywhere",
175+
message: "everywhere",
176+
})}
177+
</option>
178+
)}
177179
{searchContextByPaths.map((context) => {
178-
let label: string;
179-
let path: string;
180-
if (typeof context === "string") {
181-
label = path = context;
182-
} else {
183-
({ label, path } = context);
184-
}
180+
const { label, path } = normalizeContextByPath(
181+
context,
182+
currentLocale
183+
);
185184
return (
186185
<option key={path} value={path}>
187186
{label}

docusaurus-search-local/src/client/theme/hooks/useSearchQuery.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { useHistory, useLocation } from "@docusaurus/router";
99
import useIsBrowser from "@docusaurus/useIsBrowser";
1010
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
11+
import { searchContextByPaths } from "../../utils/proxiedGenerated";
1112

1213
const SEARCH_PARAM_QUERY = "q";
1314
const SEARCH_PARAM_CONTEXT = "ctx";
@@ -34,21 +35,30 @@ function useSearchQuery(): any {
3435
searchParams.delete(SEARCH_PARAM_QUERY);
3536
}
3637
return searchParams;
37-
}
38+
};
3839

3940
return {
4041
searchValue,
41-
searchContext,
42+
searchContext:
43+
searchContext &&
44+
Array.isArray(searchContextByPaths) &&
45+
searchContextByPaths.some((item) =>
46+
typeof item === "string"
47+
? item === searchContext
48+
: item.path === searchContext
49+
)
50+
? searchContext
51+
: "",
4252
searchVersion,
4353
updateSearchPath: (searchValue: string) => {
4454
const searchParams = getSearchParams(searchValue);
4555
history.replace({
4656
search: searchParams.toString(),
4757
});
4858
},
49-
updateSearchContext: (searchContext: string) => {
59+
updateSearchContext: (value: string) => {
5060
const searchParams = new URLSearchParams(location.search);
51-
searchParams.set(SEARCH_PARAM_CONTEXT, searchContext);
61+
searchParams.set(SEARCH_PARAM_CONTEXT, value);
5262
history.replace({
5363
search: searchParams.toString(),
5464
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
interface DetailedItem {
2+
label: string | Record<string, string>;
3+
path: string;
4+
}
5+
6+
interface NormalizedItem {
7+
path: string;
8+
label: string;
9+
}
10+
11+
export function normalizeContextByPath(
12+
context: string | DetailedItem,
13+
currentLocale: string
14+
): NormalizedItem {
15+
if (typeof context === "string") {
16+
return {
17+
label: context,
18+
path: context,
19+
};
20+
} else {
21+
const { label, path } = context;
22+
if (typeof label === "string") {
23+
return { label, path };
24+
}
25+
if (Object.prototype.hasOwnProperty.call(label, currentLocale)) {
26+
return {
27+
label: label[currentLocale],
28+
path,
29+
};
30+
}
31+
return {
32+
label: path,
33+
path,
34+
};
35+
}
36+
}

docusaurus-search-local/src/declarations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ declare module "*/generated.js" {
2222
export const indexDocs: boolean;
2323
export const searchContextByPaths: (
2424
| string
25-
| { label: string; path: string }
25+
| { label: string | Record<string, string>; path: string }
2626
)[];
2727
export const hideSearchBarWithNoSearchContext: boolean;
2828
export const useAllContextsWithNoSearchContext: boolean;
29+
export const forceIgnoreNoIndex: boolean;
2930
// These below are for mocking only.
3031
export const __setLanguage: (value: string[]) => void;
3132
export const __setRemoveDefaultStopWordFilter: (value: boolean) => void;

docusaurus-search-local/src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ export interface PluginOptions {
167167
* Provide an list of sub-paths as separate search context, E.g.: `["docs", "community", "legacy/resources"]`.
168168
* It will create multiple search indexes by these paths.
169169
*/
170-
searchContextByPaths?: (string | { label: string; path: string })[];
170+
searchContextByPaths?: (
171+
| string
172+
| { label: string | Record<string, string>; path: string }
173+
)[];
171174

172175
/**
173176
* Whether to hide the search bar when no search context was matched.
@@ -189,4 +192,11 @@ export interface PluginOptions {
189192
* @default false
190193
*/
191194
useAllContextsWithNoSearchContext?: boolean;
195+
196+
/**
197+
* Force enable search index even if noIndex: true is set, this also affects unlisted articles.
198+
*
199+
* @default false
200+
*/
201+
forceIgnoreNoIndex?: boolean;
192202
}

docusaurus-search-local/src/server/utils/parse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ export function parse(
1010
html: string,
1111
type: "docs" | "blog" | "page",
1212
url: string,
13-
{ ignoreCssSelectors }: ProcessedPluginOptions
13+
{ ignoreCssSelectors, forceIgnoreNoIndex }: ProcessedPluginOptions
1414
): ParsedDocument | null {
1515
const $ = cheerio.load(html);
1616

1717
const robotsMeta = $('meta[name="robots"]');
18-
if (robotsMeta.attr("content")?.includes("noindex")) {
18+
if (!forceIgnoreNoIndex && robotsMeta.attr("content")?.includes("noindex")) {
1919
// Unlisted content
2020
return null;
2121
}

docusaurus-search-local/src/server/utils/validateOptions.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ describe("validateOptions", () => {
5656
searchBarShortcut: true,
5757
searchBarShortcutHint: true,
5858
searchBarPosition: "auto",
59+
forceIgnoreNoIndex: false,
5960
},
6061
],
6162
[
@@ -83,6 +84,7 @@ describe("validateOptions", () => {
8384
searchBarShortcut: true,
8485
searchBarShortcutHint: true,
8586
searchBarPosition: "auto",
87+
forceIgnoreNoIndex: false,
8688
},
8789
],
8890
[
@@ -110,6 +112,7 @@ describe("validateOptions", () => {
110112
searchBarShortcut: true,
111113
searchBarShortcutHint: true,
112114
searchBarPosition: "auto",
115+
forceIgnoreNoIndex: false,
113116
},
114117
],
115118
[
@@ -137,6 +140,7 @@ describe("validateOptions", () => {
137140
searchBarShortcut: true,
138141
searchBarShortcutHint: true,
139142
searchBarPosition: "auto",
143+
forceIgnoreNoIndex: false,
140144
},
141145
],
142146
[
@@ -151,6 +155,7 @@ describe("validateOptions", () => {
151155
explicitSearchResultPath: false,
152156
searchResultContextMaxLength: 30,
153157
searchBarShortcut: false,
158+
forceIgnoreNoIndex: true,
154159
},
155160
{
156161
blogRouteBasePath: ["blog"],
@@ -175,6 +180,7 @@ describe("validateOptions", () => {
175180
searchBarShortcut: false,
176181
searchBarShortcutHint: true,
177182
searchBarPosition: "auto",
183+
forceIgnoreNoIndex: true,
178184
},
179185
],
180186
[
@@ -207,6 +213,7 @@ describe("validateOptions", () => {
207213
searchBarShortcut: true,
208214
searchBarShortcutHint: false,
209215
searchBarPosition: "auto",
216+
forceIgnoreNoIndex: false,
210217
},
211218
],
212219
[
@@ -245,6 +252,7 @@ describe("validateOptions", () => {
245252
searchBarPosition: "left",
246253
docsPluginIdForPreferredVersion: "product",
247254
searchContextByPaths: ["docs", "community"],
255+
forceIgnoreNoIndex: false,
248256
},
249257
],
250258
[
@@ -282,6 +290,7 @@ describe("validateOptions", () => {
282290
searchBarPosition: "left",
283291
docsPluginIdForPreferredVersion: "product",
284292
searchContextByPaths: ["docs", "community"],
293+
forceIgnoreNoIndex: false,
285294
},
286295
],
287296
])("validateOptions(...) should work", (options, config) => {

0 commit comments

Comments
 (0)