blob: a8c2b72db5eb70abca10d233f9b0f830da3b320d [file]
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const targetTokenizeFile = path.join(
process.cwd(),
"node_modules",
"@yang1666204",
"docusaurus-search-local",
"dist",
"client",
"client",
"utils",
"tokenize.js"
);
const targetBuildIndexFile = path.join(
process.cwd(),
"node_modules",
"@yang1666204",
"docusaurus-search-local",
"dist",
"server",
"server",
"utils",
"buildIndex.js"
);
const targetSearchBarFile = path.join(
process.cwd(),
"node_modules",
"@yang1666204",
"docusaurus-search-local",
"dist",
"client",
"client",
"theme",
"SearchBar",
"SearchBar.jsx"
);
const targetPostBuildFactoryFile = path.join(
process.cwd(),
"node_modules",
"@yang1666204",
"docusaurus-search-local",
"dist",
"server",
"server",
"utils",
"postBuildFactory.js"
);
const targetSearchPageFile = path.join(
process.cwd(),
"node_modules",
"@yang1666204",
"docusaurus-search-local",
"dist",
"client",
"client",
"theme",
"SearchPage",
"SearchPage.jsx"
);
function patchTokenizeContent(content) {
if (content.includes("const hasJapaneseKana = /[\\u3040-\\u30ff]/u.test(text);")) {
return content;
}
const needle =
' // Some languages have their own tokenizer.\n' +
' if (language.length === 1 && ["ja", "jp", "th"].includes(language[0])) {\n' +
" return lunr[language[0]]\n" +
" .tokenizer(text)\n" +
" .map((token) => token.toString());\n" +
" }\n";
const replacement =
' // For Japanese queries (especially kana), use Japanese tokenizer even in multi-language mode.\n' +
' // This avoids dropping tokens like "データベース" when language includes "zh".\n' +
' const hasJapaneseKana = /[\\u3040-\\u30ff]/u.test(text);\n' +
' if (hasJapaneseKana && (language.includes("ja") || language.includes("jp"))) {\n' +
' const jaTokenizer = lunr.ja?.tokenizer || lunr.jp?.tokenizer;\n' +
" if (typeof jaTokenizer === \"function\") {\n" +
" return jaTokenizer(text).map((token) => token.toString());\n" +
" }\n" +
" }\n" +
' // Some languages have their own tokenizer.\n' +
' if (language.length === 1 && ["ja", "jp", "th"].includes(language[0])) {\n' +
" return lunr[language[0]]\n" +
" .tokenizer(text)\n" +
" .map((token) => token.toString());\n" +
" }\n";
if (!content.includes(needle)) {
return null;
}
return content.replace(needle, replacement);
}
function patchBuildIndexContent(content) {
if (content.includes("For zh+ja mixed mode, choose tokenizer by input text.")) {
return content;
}
const needle =
" // Override tokenizer when language `zh` is enabled.\n" +
' if (language.includes("zh")) {\n' +
" this.tokenizer = lunr_1.default.zh.tokenizer;\n" +
" }\n";
const replacement =
" // Override tokenizer when language `zh` is enabled.\n" +
" // For zh+ja mixed mode, choose tokenizer by input text.\n" +
' if (language.includes("zh")) {\n' +
' const hasJa = language.includes("ja") || language.includes("jp");\n' +
" if (hasJa) {\n" +
" this.tokenizer = function (input, metadata) {\n" +
' if (input == null) return [];\n' +
" const text = input.toString();\n" +
' if (/[\\u3040-\\u30ff]/u.test(text) && lunr_1.default.ja?.tokenizer) {\n' +
" return lunr_1.default.ja.tokenizer(input, metadata);\n" +
" }\n" +
" return lunr_1.default.zh.tokenizer(input, metadata);\n" +
" };\n" +
" }\n" +
" else {\n" +
" this.tokenizer = lunr_1.default.zh.tokenizer;\n" +
" }\n" +
" }\n";
if (!content.includes(needle)) {
return null;
}
return content.replace(needle, replacement);
}
// On non-default-locale routes (e.g. /zh-CN/docs/..., /ja/docs/...) the
// plugin strips only `baseUrl` before matching `searchContextByPaths`, so the
// remaining URI keeps the locale prefix and never matches `docs`.
// Combined with `useAllContextsWithNoSearchContext: true`, this collapses the
// search back to the root index (which contains every version, including the
// retired v4.x). These two patches strip the non-default locale prefix in both
// the runtime SearchBar and the build-time context partitioning so each locale
// gets its own per-context index files and the dropdown stays scoped to the
// active docs tree.
function patchSearchBarContent(content) {
if (content.includes("PATCH(locale-aware-context)")) {
return content;
}
const needle =
" if (location.pathname.startsWith(versionUrl)) {\n" +
" const uri = location.pathname.substring(versionUrl.length);\n" +
" let matchedPath;\n" +
" for (const _path of searchContextByPaths) {\n" +
" const path = typeof _path === \"string\" ? _path : _path.path;\n" +
" if (uri === path || uri.startsWith(`${path}/`)) {\n" +
" matchedPath = path;\n" +
" break;\n" +
" }\n" +
" }\n";
const replacement =
" if (location.pathname.startsWith(versionUrl)) {\n" +
" const uri = location.pathname.substring(versionUrl.length);\n" +
" // PATCH(locale-aware-context): strip non-default locale prefix so\n" +
" // /zh-CN/docs/... and /ja/docs/... match the configured contexts.\n" +
" const __localePrefix = currentLocale + \"/\";\n" +
" const __ctxUri = uri.startsWith(__localePrefix) ? uri.substring(__localePrefix.length) : uri;\n" +
" let matchedPath;\n" +
" for (const _path of searchContextByPaths) {\n" +
" const path = typeof _path === \"string\" ? _path : _path.path;\n" +
" if (__ctxUri === path || __ctxUri.startsWith(`${path}/`)) {\n" +
" matchedPath = path;\n" +
" break;\n" +
" }\n" +
" }\n";
if (!content.includes(needle)) {
return null;
}
return content.replace(needle, replacement);
}
function patchPostBuildFactoryContent(content) {
if (content.includes("PATCH(locale-aware-context)")) {
return content;
}
const needle1 =
" if (searchContextByPaths) {\n" +
" const { baseUrl } = buildData;\n" +
" const rootAllDocs = [];\n";
const replacement1 =
" if (searchContextByPaths) {\n" +
" const { baseUrl } = buildData;\n" +
" // PATCH(locale-aware-context): strip non-default locale prefix so\n" +
" // /zh-CN/docs/... and /ja/docs/... get assigned to the\n" +
" // configured contexts instead of falling into the root bucket.\n" +
" const __i18n = buildData.i18n;\n" +
" const __localePrefix = __i18n && __i18n.currentLocale && __i18n.currentLocale !== __i18n.defaultLocale\n" +
" ? __i18n.currentLocale + \"/\"\n" +
" : \"\";\n" +
" const rootAllDocs = [];\n";
const needle2 =
" if (doc.u.startsWith(baseUrl)) {\n" +
" const uri = doc.u.substring(baseUrl.length);\n" +
" const matchedPaths = [];\n" +
" for (const _path of searchContextByPaths) {\n" +
" const path = typeof _path === \"string\" ? _path : _path.path;\n" +
" if (uri === path || uri.startsWith(`${path}/`)) {\n" +
" matchedPaths.push(path);\n" +
" }\n" +
" }\n";
const replacement2 =
" if (doc.u.startsWith(baseUrl)) {\n" +
" const uri = doc.u.substring(baseUrl.length);\n" +
" const __ctxUri = __localePrefix && uri.startsWith(__localePrefix)\n" +
" ? uri.substring(__localePrefix.length)\n" +
" : uri;\n" +
" const matchedPaths = [];\n" +
" for (const _path of searchContextByPaths) {\n" +
" const path = typeof _path === \"string\" ? _path : _path.path;\n" +
" if (__ctxUri === path || __ctxUri.startsWith(`${path}/`)) {\n" +
" matchedPaths.push(path);\n" +
" }\n" +
" }\n";
if (!content.includes(needle1) || !content.includes(needle2)) {
return null;
}
return content.replace(needle1, replacement1).replace(needle2, replacement2);
}
// The search worker fetches `${versionUrl}search-index-*.json`. `versionUrl`
// defaults to `siteConfig.baseUrl` (= "/"), which is locale-agnostic, so on
// /zh-CN/* or /ja/* pages the worker still hits the default-locale index files
// at the site root and never sees the per-locale indexes that Docusaurus emits
// under build/{locale}/. These two patches make `versionUrl` carry the active
// locale prefix in both the SearchBar (live dropdown + `?version=` it writes
// into the "see all" link) and the SearchPage (direct loads of /{locale}/search
// with no explicit `?version=`).
function patchSearchBarVersionUrlContent(content) {
if (content.includes("PATCH(locale-aware-version-url)")) {
return content;
}
const needle1 =
" const { siteConfig: { baseUrl }, i18n: { currentLocale }, } = useDocusaurusContext();\n";
const replacement1 =
" const { siteConfig: { baseUrl }, i18n: { currentLocale, defaultLocale }, } = useDocusaurusContext();\n";
const needle2 =
" }\n" +
" }\n" +
" const history = useHistory();\n";
const replacement2 =
" }\n" +
" }\n" +
" // PATCH(locale-aware-version-url): non-default locales serve their own\n" +
" // search-index files under /{locale}/. Prepend the locale to versionUrl\n" +
" // so the worker fetches /{locale}/search-index-*.json instead of the EN one.\n" +
" if (currentLocale && defaultLocale && currentLocale !== defaultLocale) {\n" +
" const __localeSegment = currentLocale + \"/\";\n" +
" if (!versionUrl.startsWith(baseUrl + __localeSegment)) {\n" +
" versionUrl = baseUrl + __localeSegment + versionUrl.substring(baseUrl.length);\n" +
" }\n" +
" }\n" +
" const history = useHistory();\n";
if (!content.includes(needle1) || !content.includes(needle2)) {
return null;
}
return content.replace(needle1, replacement1).replace(needle2, replacement2);
}
function patchSearchPageVersionUrlContent(content) {
if (content.includes("PATCH(locale-aware-version-url)")) {
return content;
}
const needle1 =
" const { siteConfig: { baseUrl }, i18n: { currentLocale }, } = useDocusaurusContext();\n";
const replacement1 =
" const { siteConfig: { baseUrl }, i18n: { currentLocale, defaultLocale }, } = useDocusaurusContext();\n";
const needle2 = " const versionUrl = `${baseUrl}${searchVersion}`;\n";
const replacement2 =
" // PATCH(locale-aware-version-url): when there is no explicit ?version=\n" +
" // and the page is rendered under a non-default locale, fall back to that\n" +
" // locale's index files instead of the default-locale root.\n" +
" const versionUrl = searchVersion\n" +
" ? `${baseUrl}${searchVersion}`\n" +
" : (currentLocale && defaultLocale && currentLocale !== defaultLocale\n" +
" ? `${baseUrl}${currentLocale}/`\n" +
" : baseUrl);\n";
if (!content.includes(needle1) || !content.includes(needle2)) {
return null;
}
return content.replace(needle1, replacement1).replace(needle2, replacement2);
}
function patchOneFile(filePath, patchFn, label) {
if (!fs.existsSync(filePath)) {
console.warn(`[patch-search-tokenize] skip(${label}): file not found: ${filePath}`);
return;
}
const original = fs.readFileSync(filePath, "utf8");
const patched = patchFn(original);
if (patched == null) {
console.warn(
`[patch-search-tokenize] skip(${label}): target block not found (plugin content may have changed)`
);
return;
}
if (patched === original) {
console.log(`[patch-search-tokenize] already patched (${label})`);
return;
}
fs.writeFileSync(filePath, patched, "utf8");
console.log(`[patch-search-tokenize] patched (${label})`);
}
function main() {
patchOneFile(targetTokenizeFile, patchTokenizeContent, "client-tokenize");
patchOneFile(targetBuildIndexFile, patchBuildIndexContent, "server-buildIndex");
patchOneFile(targetSearchBarFile, patchSearchBarContent, "client-searchBar-locale");
patchOneFile(
targetPostBuildFactoryFile,
patchPostBuildFactoryContent,
"server-postBuildFactory-locale"
);
patchOneFile(
targetSearchBarFile,
patchSearchBarVersionUrlContent,
"client-searchBar-versionUrl"
);
patchOneFile(
targetSearchPageFile,
patchSearchPageVersionUrlContent,
"client-searchPage-versionUrl"
);
}
main();