fix(site): [SITE-05] repair search interactions and align peer dependency

This commit is contained in:
Harvey Zhao committed 2026-09-16 09:36:39 +08:00
1 parent 61fe8deb59
commit 6b580d1667
90 files changed
+859 -365

No files matched your search

+33 -4
View File
@@ -31,7 +31,7 @@ not a VitePress page. Keep both URL surfaces compatible.
| `docs/proxy/canvas.md`, `docs/en/proxy/canvas.md` | Backing video, draw callbacks/events, Canvas priority, subtitles and compatible types | `/document/proxy/canvas.html`, `/document/en/proxy/canvas.html` |
| `docs/proxy/mediabunny.md`, `docs/en/proxy/mediabunny.md` | Input, HLS pairing, synthetic media state, lifecycle and explicit type views | `/document/proxy/mediabunny.html`, `/document/en/proxy/mediabunny.html` |
| `docs/.vitepress/config.js` | Navigation, base URL, output path, page head | Repository `docs/document/` |
| `docs/vite.config.ts` | Search plugin configuration | VitePress/Vite integration; real search acceptance remains pending |
| `docs/vite.config.ts`, `build/search.ts` | Search configuration and pinned upstream interaction adapter | Preserves the search template, CSS hooks, index and result URLs |
| `docs/public/main.js` | Run Code links and first-visit language redirect | Copied into the built site |
| `docs/public/style.css` | Documentation presentation | Copied into the built site |
@@ -45,9 +45,38 @@ serve different purposes. Neither path check replaces browser navigation tests.
The generated site lives in `docs/document/`. The current inventory contains 66 Markdown pages: 33 Chinese
and 33 English. All 16 plugins, both tools and both proxies have dedicated guides and navigation in both languages.
Core member
mapping and the current cross-check of earlier guides remain in SITE-04;
navigation links alone are not complete API documentation.
SITE-04 completed the 963 core declaration mappings and cross-checked all 40
ecosystem guides. Full site execution and delivery remain in SITE-05/SITE-06;
navigation links alone do not establish player or plugin playback compatibility.
### Local documentation search
`vitepress-plugin-search` 1.0.4-alpha.22 supplies the shared Chinese/English
index and existing search UI. Its FlexSearch peer is pinned to 0.7.43 in this
workspace, within the declared `^0.7.31` range. The plugin also embeds its own
index implementation, so changing this peer alone does not fix UI behavior.
Its existing token matching is retained; Chinese substring segmentation is not
promised. The Algolia/search-insights peer warnings originate from VitePress's
unused hosted-search dependencies; this site does not configure that provider.
`build/search.ts` runs before Vue compilation. It checks the exact installed
component hash, normalizes CRLF for patch matching, and requires each replacement
to match once. It prevents Enter from submitting the HTML form, navigates the
selected result through the router, handles empty results and composition,
resets selection when queries change, and makes Escape close the modal and
restore button focus. Focus waits for Vue's DOM update rather than a timer.
The component owns its shortcut listener and removes it on unmount; a late index
import cannot attach it after unmount. Upstream HTML/CSS and third-party attribution
stay intact. Do not modify `node_modules` or simply update the hash on an upgrade:
review these interactions and the new upstream component first.
Use `yarn test:site-build` for the pinned-component compilation and drift guard,
`yarn typecheck:docs-tools` for the adapter, and rebuild with `yarn build:docs`
before `yarn test:browser:source document-search.spec.js document-site.spec.js`.
Browser checks cover both language URLs, pointer navigation, keyboard selection,
Escape, focus, no results, and retained document identity (no form reload).
They isolate external scripts and do not validate AdSense, physical devices,
all search-index anchors or the complete online editor; those remain SITE-05 work.
Tools have their own navigation group. Iframe explains the required child inject
step and serialized commit body, without treating the protocol as a sandbox or
@@ -0,0 +1,74 @@
import type { Plugin } from 'vite'
import assert from 'node:assert/strict'
import { createHash } from 'node:crypto'
// Keep the pinned upstream template, CSS, index and URLs; patch interaction ownership only.
const componentHash = 'c53a0d2a5b5380123f775b0852b5805a4b0a3d97248d5c296972de30c35f6f9c'
export function patchSearchComponent(source: string): string {
assert.equal(createHash('sha256').update(source).digest('hex'), componentHash, 'Review the search interaction adapter before upgrading vitepress-plugin-search')
let result = source.replaceAll('\r\n', '\n')
const replace = (before: string, after: string) => {
assert.equal(result.split(before).length, 2, `Expected one search patch target: ${before}`)
result = result.replace(before, after)
}
replace('ref, onMounted, computed, nextTick', 'ref, onMounted, onUnmounted, computed, nextTick, watch')
replace('if (searchTerm.value) {', 'if (searchTerm.value && searchIndex.value) {')
replace(`const openSearch = () => {
setTimeout(() => {
if (input.value) input.value.focus();
}, 100);
cleanSearch();
open.value = true;
};`, `const openSearch = () => {
cleanSearch();
open.value = true;
nextTick(() => { if (alive && open.value) input.value?.focus(); });
};`)
replace('onMounted(async () => {', `let alive = true;
let removeHotKey = () => {};
onUnmounted(() => { alive = false; removeHotKey(); });
watch(searchTerm, () => { focused.value = 0; }, { flush: "sync" });
onMounted(async () => {`)
replace('const data = await import("virtual:search-data");', 'const data = await import("virtual:search-data");\n if (!alive) return;')
replace('if (searchTerm.value?.length == 0 && open.value)\n open.value = false', 'if (open.value) {\n e.preventDefault();\n cleanSearch();\n documentButton()?.focus();\n }')
replace('window.addEventListener("keydown", handleSearchHotKey);', 'window.addEventListener("keydown", handleSearchHotKey);\n removeHotKey = () => window.removeEventListener("keydown", handleSearchHotKey);')
replace('<form class="DocSearch-Form">', '<form class="DocSearch-Form" @submit.prevent>')
const start = result.indexOf('const handleNavigation = (e: KeyboardEvent) => {')
const end = result.indexOf('</script>', start)
assert(start > 0 && end > start, 'Missing pinned search keyboard handler')
result = `${result.slice(0, start)}function documentButton() {
return document.querySelector<HTMLButtonElement>("#docsearch button");
}
const handleNavigation = (e: KeyboardEvent) => {
if (e.isComposing || !["ArrowUp", "ArrowDown", "Enter"].includes(e.key)) return;
e.preventDefault();
const links = Array.from(modal.value?.querySelectorAll<HTMLAnchorElement>(".search-group a") || []);
if (!links.length) return;
const current = links.findIndex(link => link.classList.contains("link-focused"));
if (e.key === "Enter") {
const href = links[Math.max(current, 0)].href;
cleanSearch();
void router.go(href);
return;
}
const delta = e.key === "ArrowDown" ? 1 : -1;
const next = (Math.max(current, 0) + delta + links.length) % links.length;
focused.value = linksOrder.value[next];
links[next].scrollIntoView({ block: "nearest" });
};
${result.slice(end)}`
return result
}
export function searchInteractions(): Plugin {
return {
name: 'artplayer-search-interactions',
enforce: 'pre',
transform(source, id) {
if (id.replaceAll('\\', '/').endsWith('/vitepress-plugin-search/dist/Search.vue'))
return { code: patchSearchComponent(source), map: null }
},
}
}
@@ -1,11 +1,13 @@
import { SearchPlugin } from "vitepress-plugin-search";
import { searchInteractions } from '../build/search.ts';
export default {
plugins: [
searchInteractions(),
SearchPlugin({
placeholder: "Search docs",
buttonLabel: "Search",
previewLength: 62,
}),
],
};
};
+1 -1
View File
@@ -8,7 +8,7 @@
"preview": "vitepress preview docs"
},
"devDependencies": {
"flexsearch": "0.8.158",
"flexsearch": "0.7.43",
"markdown-it": "14.1.0",
"vitepress": "1.6.3",
"vitepress-plugin-search": "1.0.4-alpha.22",