create-zudo-doc 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-md-gen.d.ts +8 -0
- package/dist/claude-md-gen.js +43 -26
- package/dist/compose.d.ts +14 -20
- package/dist/compose.js +15 -25
- package/dist/features/body-foot-util.d.ts +8 -4
- package/dist/features/body-foot-util.js +8 -4
- package/dist/features/claude-resources.d.ts +12 -0
- package/dist/features/claude-resources.js +13 -6
- package/dist/features/design-token-panel.d.ts +0 -12
- package/dist/features/design-token-panel.js +32 -93
- package/dist/features/doc-history.d.ts +18 -3
- package/dist/features/doc-history.js +45 -60
- package/dist/features/doc-tags.d.ts +9 -13
- package/dist/features/doc-tags.js +10 -26
- package/dist/features/dynamic-page-transition.d.ts +19 -30
- package/dist/features/dynamic-page-transition.js +21 -209
- package/dist/features/footer-taglist.d.ts +1 -1
- package/dist/features/footer-taglist.js +1 -1
- package/dist/features/footer.d.ts +3 -2
- package/dist/features/footer.js +3 -2
- package/dist/features/i18n.d.ts +13 -8
- package/dist/features/i18n.js +14 -9
- package/dist/features/image-enlarge.d.ts +7 -26
- package/dist/features/image-enlarge.js +7 -26
- package/dist/features/llms-txt.d.ts +3 -5
- package/dist/features/llms-txt.js +3 -5
- package/dist/features/search.d.ts +7 -3
- package/dist/features/search.js +7 -3
- package/dist/features/sidebar-resizer.d.ts +4 -8
- package/dist/features/sidebar-resizer.js +4 -8
- package/dist/features/sidebar-toggle.d.ts +7 -7
- package/dist/features/sidebar-toggle.js +7 -7
- package/dist/features/tag-governance.d.ts +14 -7
- package/dist/features/tag-governance.js +52 -8
- package/dist/features/tauri.d.ts +13 -10
- package/dist/features/tauri.js +16 -52
- package/dist/features/versioning.d.ts +12 -24
- package/dist/features/versioning.js +13 -39
- package/dist/index.js +2 -3
- package/dist/scaffold.d.ts +13 -6
- package/dist/scaffold.js +64 -45
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +14 -0
- package/dist/zfb-config-gen.d.ts +32 -20
- package/dist/zfb-config-gen.js +400 -53
- package/package.json +2 -2
- package/templates/base/pages/docs/[[...slug]].tsx +59 -0
- package/templates/base/pages/index.tsx +6 -41
- package/templates/base/src/styles/global.css +15 -340
- package/templates/base/tsconfig.json +3 -26
- package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +88 -0
- package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +3 -1
- package/dist/settings-gen.d.ts +0 -2
- package/dist/settings-gen.js +0 -320
- package/templates/base/.htmlvalidate.json +0 -5
- package/templates/base/.zfb/doc-history-meta.json +0 -1
- package/templates/base/pages/_data.ts +0 -160
- package/templates/base/pages/lib/_body-end-islands.tsx +0 -165
- package/templates/base/pages/lib/_chrome.ts +0 -167
- package/templates/base/pages/lib/_details.tsx +0 -29
- package/templates/base/pages/lib/_doc-route-entries.ts +0 -10
- package/templates/base/pages/lib/_extract-headings.ts +0 -37
- package/templates/base/pages/lib/_frontmatter-preview-data.ts +0 -27
- package/templates/base/pages/lib/_nav-source-cache.ts +0 -100
- package/templates/base/pages/lib/_nav-source-docs.ts +0 -17
- package/templates/base/pages/lib/_preset-generator.tsx +0 -81
- package/templates/base/pages/lib/_route-context.ts +0 -32
- package/templates/base/pages/lib/_search-widget.tsx +0 -17
- package/templates/base/pages/lib/doc-page-props.ts +0 -30
- package/templates/base/pages/lib/locale-merge.ts +0 -59
- package/templates/base/scripts/run-b4push.sh +0 -102
- package/templates/base/src/components/ai-chat-modal.tsx +0 -18
- package/templates/base/src/components/content/code-group.tsx +0 -3
- package/templates/base/src/components/content/content-admonition.tsx +0 -4
- package/templates/base/src/components/desktop-sidebar-toggle.tsx +0 -15
- package/templates/base/src/components/doc-history.tsx +0 -21
- package/templates/base/src/components/image-enlarge.tsx +0 -24
- package/templates/base/src/components/preset-generator.tsx +0 -14
- package/templates/base/src/components/sidebar-toggle.tsx +0 -6
- package/templates/base/src/components/sidebar-tree.tsx +0 -6
- package/templates/base/src/config/color-scheme-utils.ts +0 -69
- package/templates/base/src/config/color-schemes.ts +0 -165
- package/templates/base/src/config/docs-schema.ts +0 -95
- package/templates/base/src/config/frontmatter-preview-defaults.ts +0 -27
- package/templates/base/src/config/frontmatter-preview-renderers.tsx +0 -46
- package/templates/base/src/config/i18n.ts +0 -239
- package/templates/base/src/config/settings-types.ts +0 -45
- package/templates/base/src/config/sidebars.ts +0 -66
- package/templates/base/src/config/tag-vocabulary-types.ts +0 -4
- package/templates/base/src/config/tag-vocabulary.ts +0 -20
- package/templates/base/src/config/z-index-tokens.ts +0 -128
- package/templates/base/src/types/docs-entry.ts +0 -28
- package/templates/base/src/types/heading.ts +0 -5
- package/templates/base/src/types/locale.ts +0 -10
- package/templates/base/src/utils/base.ts +0 -164
- package/templates/base/src/utils/docs.ts +0 -446
- package/templates/base/src/utils/git-info.ts +0 -70
- package/templates/base/src/utils/github.ts +0 -22
- package/templates/base/src/utils/nav-scope.ts +0 -34
- package/templates/base/src/utils/sidebar.ts +0 -36
- package/templates/base/src/utils/slug.ts +0 -10
- package/templates/base/src/utils/smart-break.tsx +0 -12
- package/templates/base/src/utils/tags.ts +0 -83
- package/templates/base/zfb-shim.d.ts +0 -183
- package/templates/features/bodyFootUtil/files/src/utils/github.ts +0 -22
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +0 -42
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +0 -752
- package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +0 -97
- package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +0 -735
- package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +0 -15
- package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +0 -435
- package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +0 -174
- package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +0 -30
- package/templates/features/docHistory/files/src/components/doc-history.tsx +0 -10
- package/templates/features/docHistory/files/src/types/doc-history.ts +0 -7
- package/templates/features/dynamicPageTransition/files/src/components/client-router-bootstrap.tsx +0 -72
- package/templates/features/i18n/files/pages/[locale]/index.tsx +0 -72
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +0 -11
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +0 -6
- package/templates/features/tauri/files/src/components/find-bar.tsx +0 -122
- package/templates/features/tauri/files/src/components/find-in-page-init.tsx +0 -59
- package/templates/features/tauri/files/src/utils/find-in-page.ts +0 -175
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
// Re-export stub for DocHistory types.
|
|
2
|
-
//
|
|
3
|
-
// DocHistoryData and DocHistoryEntry now ship from the package at
|
|
4
|
-
// @takazudo/zudo-doc/island-types (epic #2344, S1a).
|
|
5
|
-
// This file is kept as a stable re-export in case any project-local code
|
|
6
|
-
// references the @/types/doc-history path.
|
|
7
|
-
export type { DocHistoryData, DocHistoryEntry } from "@takazudo/zudo-doc/island-types";
|
package/templates/features/dynamicPageTransition/files/src/components/client-router-bootstrap.tsx
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// Client-side bootstrap for the @takazudo/zfb-runtime ClientRouter.
|
|
4
|
-
//
|
|
5
|
-
// Why this exists (zudolab/zudo-doc#1524, W7A verification):
|
|
6
|
-
// `<ClientRouter />` (mounted in doc-layout.tsx) emits SSR meta tags + CSS
|
|
7
|
-
// for the SPA soft-swap router, but the actual click/form intercept
|
|
8
|
-
// registration runs as a top-of-module side effect inside
|
|
9
|
-
// `@takazudo/zfb-runtime/src/client-router.ts`:
|
|
10
|
-
//
|
|
11
|
-
// if (typeof document !== "undefined") { init(); }
|
|
12
|
-
//
|
|
13
|
-
// The host's existing `import { ClientRouter } from "@takazudo/zfb-runtime"`
|
|
14
|
-
// in doc-layout.tsx happens during SSR (where typeof document ===
|
|
15
|
-
// "undefined"), so the side effect is silently skipped — the router
|
|
16
|
-
// module never reaches the client bundle and every navigation falls
|
|
17
|
-
// through to a full page load. W7A's Playwright harness confirmed this:
|
|
18
|
-
// pageswap.viewTransition is null, getAnimations() is empty during nav,
|
|
19
|
-
// and the sidebar DOM identity is destroyed on every click.
|
|
20
|
-
//
|
|
21
|
-
// The fix is a minimal "use client" island whose only job is to
|
|
22
|
-
// side-effect-import `@takazudo/zfb-runtime/client-router` so the same
|
|
23
|
-
// guard fires in the browser. The `init()` is idempotent (guarded by an
|
|
24
|
-
// `initialized` flag in router.ts), so even if the import path is
|
|
25
|
-
// reached again later the registration runs exactly once.
|
|
26
|
-
//
|
|
27
|
-
// Bundle cost: the only thing this island ships to the client is the
|
|
28
|
-
// client-router subgraph (router.ts + swap-functions.ts + events.ts +
|
|
29
|
-
// types.ts + cssesc.ts) plus its single dep on `@takazudo/zfb/runtime`'s
|
|
30
|
-
// island manager. The server-only zfb-runtime modules
|
|
31
|
-
// (createPageRouter, snapshot, framework adapter) are not transitively
|
|
32
|
-
// reachable from the client-router barrel and stay out of the bundle.
|
|
33
|
-
//
|
|
34
|
-
// Why not call init() in useEffect instead of a side-effect import:
|
|
35
|
-
// useEffect fires after Preact mounts, which is after the islands
|
|
36
|
-
// runtime fetches and executes the bundle. Module top-level code runs
|
|
37
|
-
// as soon as the bundle is parsed by the browser — earlier than mount,
|
|
38
|
-
// closer to Astro's <script type="module"> emission timing. The first
|
|
39
|
-
// click on a fresh page typically arrives 100ms+ after parse, so this
|
|
40
|
-
// timing is what makes the SPA-router actually intercept.
|
|
41
|
-
|
|
42
|
-
// Side-effect import — running this file's bundle in the browser
|
|
43
|
-
// triggers the `if (typeof document !== "undefined") { init(); }` guard
|
|
44
|
-
// at the top of `@takazudo/zfb-runtime/src/client-router.ts`.
|
|
45
|
-
import "@takazudo/zfb-runtime/client-router";
|
|
46
|
-
|
|
47
|
-
import type { JSX } from "preact";
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Renders nothing. The island marker exists only so zfb's island scanner
|
|
51
|
-
* walks page → BodyEndIslands → ClientRouterBootstrap and includes the
|
|
52
|
-
* client-router barrel in the per-island bundle, where the side-effect
|
|
53
|
-
* import above can fire on the client.
|
|
54
|
-
*
|
|
55
|
-
* Note: the SSR `<ClientRouter />` mounted by `DocLayout` (via
|
|
56
|
-
* `enableClientRouter`) self-activates the SPA router when its head meta
|
|
57
|
-
* tags reach the browser. This host island is therefore a no-op for
|
|
58
|
-
* router activation — it is kept only for the `when="load"` hydration
|
|
59
|
-
* slot and bundle inclusion. The actual on/off gate is
|
|
60
|
-
* `enableClientRouter` on `<DocLayoutWithDefaults>`, set to
|
|
61
|
-
* `settings.dynamicPageTransition` at every call site.
|
|
62
|
-
*/
|
|
63
|
-
function ClientRouterBootstrap(): JSX.Element | null {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Stable marker name across SSR / scanner / hydration manifest. Required
|
|
68
|
-
// for production minification per the existing pattern in
|
|
69
|
-
// `pages/lib/_body-end-islands.tsx` (zfb PR #150 marker-name alignment).
|
|
70
|
-
ClientRouterBootstrap.displayName = "ClientRouterBootstrap";
|
|
71
|
-
|
|
72
|
-
export default ClientRouterBootstrap;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/** @jsxRuntime automatic */
|
|
2
|
-
/** @jsxImportSource preact */
|
|
3
|
-
// Page module for the locale-prefixed site index route.
|
|
4
|
-
//
|
|
5
|
-
// Non-default-locale site index. paths() emits one route per locale defined
|
|
6
|
-
// in settings.locales (never the default locale — that is handled by
|
|
7
|
-
// pages/index.tsx since prefixDefaultLocale is false).
|
|
8
|
-
//
|
|
9
|
-
// paths() contract (zfb ADR-004 — synchronous):
|
|
10
|
-
// params: { locale: string } — e.g. "ja"
|
|
11
|
-
// props: { locale } — resolved locale passed to component
|
|
12
|
-
//
|
|
13
|
-
// Data flow (inside component — sync per ADR-004):
|
|
14
|
-
// routeContext host RouteContext (settings + i18n + nav helpers)
|
|
15
|
-
// → prepareHomeData() nav tree, category order, tag count
|
|
16
|
-
// → HomePageView renders hero + SiteTreeNav grid + tag section
|
|
17
|
-
//
|
|
18
|
-
// Thin consumer of `HomePageView` (S3 #2502) and `prepareHomeData` (#2519):
|
|
19
|
-
// this file's only job is `paths()` (a locale-routing concern) — mirroring
|
|
20
|
-
// the package route's shape (`packages/zudo-doc/src/routes/locale-index.tsx`).
|
|
21
|
-
// The locale-specific data prep — `resolveNavSource` with the locale-home
|
|
22
|
-
// filter options, `loadCategoryMeta`, and the locale-config guard — now lives
|
|
23
|
-
// in `prepareHomeData`. No `extras` here — the showcase's `@Takazudo` brand
|
|
24
|
-
// link (#1453) is project-specific and is not part of the generated
|
|
25
|
-
// project's hero.
|
|
26
|
-
|
|
27
|
-
import { settings } from "@/config/settings";
|
|
28
|
-
import { routeContext } from "../lib/_route-context";
|
|
29
|
-
import { prepareHomeData } from "@takazudo/zudo-doc/home-page";
|
|
30
|
-
import type { JSX } from "preact";
|
|
31
|
-
import { HomePageView } from "../lib/_chrome";
|
|
32
|
-
|
|
33
|
-
export const frontmatter = { title: "Home" };
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// paths() — synchronous (ADR-004)
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
/** Emit one route per non-default locale. */
|
|
40
|
-
export function paths(): Array<{
|
|
41
|
-
params: { locale: string };
|
|
42
|
-
props: { locale: string };
|
|
43
|
-
}> {
|
|
44
|
-
return Object.keys(settings.locales).map((locale) => ({
|
|
45
|
-
params: { locale },
|
|
46
|
-
props: { locale },
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Page component
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
interface PageArgs {
|
|
55
|
-
params: { locale: string };
|
|
56
|
-
props: { locale: string };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export default function LocaleIndexPage({ params }: PageArgs): JSX.Element {
|
|
60
|
-
const locale = params.locale;
|
|
61
|
-
|
|
62
|
-
const { tree, categoryOrder, tagCount } = prepareHomeData(routeContext, locale);
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<HomePageView
|
|
66
|
-
locale={locale}
|
|
67
|
-
tree={tree}
|
|
68
|
-
categoryOrder={categoryOrder}
|
|
69
|
-
tagCount={tagCount}
|
|
70
|
-
/>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// Re-export shim for the ImageEnlarge island.
|
|
4
|
-
//
|
|
5
|
-
// The real component and its SSR fallback now ship in the package at
|
|
6
|
-
// @takazudo/zudo-doc/image-enlarge (epic #2344, S3).
|
|
7
|
-
// pages/lib/_body-end-islands.tsx imports them directly from there; this
|
|
8
|
-
// overlay overwrites the base W6A no-op stub so that any project-local
|
|
9
|
-
// code referencing the @/components/image-enlarge path resolves to the
|
|
10
|
-
// real island when the imageEnlarge feature is enabled.
|
|
11
|
-
export { ImageEnlarge, ImageEnlargeSsrFallback } from "@takazudo/zudo-doc/image-enlarge";
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// Thin re-export shim. DesktopSidebarToggle moved into the package
|
|
2
|
-
// (`@takazudo/zudo-doc/desktop-sidebar-toggle-island`) as part of the
|
|
3
|
-
// package-first migration (#2344, S2). Host code using
|
|
4
|
-
// `@/components/desktop-sidebar-toggle` still resolves correctly;
|
|
5
|
-
// the implementation lives in the package.
|
|
6
|
-
export { DesktopSidebarToggle, SIDEBAR_STORAGE_KEY } from "@takazudo/zudo-doc/desktop-sidebar-toggle-island";
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useRef, useEffect, useCallback } from "preact/hooks";
|
|
4
|
-
import type { FindResult, FindInPage } from "@/utils/find-in-page";
|
|
5
|
-
|
|
6
|
-
interface FindBarProps {
|
|
7
|
-
visible: boolean;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
findInPage: FindInPage;
|
|
10
|
-
containerSelector: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function toMatchInfo(result: FindResult): FindResult | null {
|
|
14
|
-
return result.matches > 0 ? result : null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function FindBar({ visible, onClose, findInPage, containerSelector }: FindBarProps) {
|
|
18
|
-
const [query, setQuery] = useState("");
|
|
19
|
-
const [matchInfo, setMatchInfo] = useState<FindResult | null>(null);
|
|
20
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
21
|
-
// Pin `findInPage` via ref so the visibility-clear effect doesn't
|
|
22
|
-
// re-run when a new `find-in-page` instance is passed; the latest
|
|
23
|
-
// value is read at effect-fire time without entering the deps.
|
|
24
|
-
const findInPageRef = useRef(findInPage);
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
findInPageRef.current = findInPage;
|
|
27
|
-
}, [findInPage]);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (visible) {
|
|
31
|
-
inputRef.current?.focus();
|
|
32
|
-
inputRef.current?.select();
|
|
33
|
-
}
|
|
34
|
-
}, [visible]);
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (!visible) {
|
|
38
|
-
setQuery("");
|
|
39
|
-
setMatchInfo(null);
|
|
40
|
-
findInPageRef.current.stop();
|
|
41
|
-
}
|
|
42
|
-
}, [visible]);
|
|
43
|
-
|
|
44
|
-
const handleFind = useCallback(
|
|
45
|
-
(text: string) => {
|
|
46
|
-
const container = document.querySelector(containerSelector);
|
|
47
|
-
if (!text || !(container instanceof HTMLElement)) {
|
|
48
|
-
setMatchInfo(null);
|
|
49
|
-
findInPage.stop();
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const result = findInPage.find(container, text);
|
|
53
|
-
setMatchInfo(toMatchInfo(result));
|
|
54
|
-
},
|
|
55
|
-
[findInPage, containerSelector],
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
const handleKeyDown = useCallback(
|
|
59
|
-
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
60
|
-
if (e.key === "Escape") {
|
|
61
|
-
onClose();
|
|
62
|
-
} else if (e.key === "Enter") {
|
|
63
|
-
const result = e.shiftKey ? findInPage.prev() : findInPage.next();
|
|
64
|
-
setMatchInfo(toMatchInfo(result));
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
[onClose, findInPage],
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
if (!visible) return null;
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<div className="fixed top-[3.5rem] right-0 z-dropdown flex items-center gap-hsp-sm py-hsp-xs px-hsp-md bg-surface border-b border-l border-muted rounded-bl-lg shadow-md">
|
|
74
|
-
<input
|
|
75
|
-
ref={inputRef}
|
|
76
|
-
className="w-48 py-[4px] px-hsp-sm rounded text-small bg-bg border border-muted text-fg outline-none focus:border-accent"
|
|
77
|
-
type="text"
|
|
78
|
-
value={query}
|
|
79
|
-
placeholder="Find in page..."
|
|
80
|
-
aria-label="Find in page"
|
|
81
|
-
onChange={(e) => {
|
|
82
|
-
setQuery(e.currentTarget.value);
|
|
83
|
-
handleFind(e.currentTarget.value);
|
|
84
|
-
}}
|
|
85
|
-
onKeyDown={handleKeyDown}
|
|
86
|
-
/>
|
|
87
|
-
<span className="text-caption whitespace-nowrap min-w-[3rem] text-center text-fg/60">
|
|
88
|
-
{matchInfo ? `${matchInfo.activeMatchOrdinal}/${matchInfo.matches}` : ""}
|
|
89
|
-
</span>
|
|
90
|
-
<button
|
|
91
|
-
type="button"
|
|
92
|
-
className="py-hsp-2xs px-hsp-sm rounded text-caption bg-bg border border-muted text-fg hover:bg-surface"
|
|
93
|
-
onClick={() => {
|
|
94
|
-
const result = findInPage.prev();
|
|
95
|
-
setMatchInfo(toMatchInfo(result));
|
|
96
|
-
}}
|
|
97
|
-
title="Previous (Shift+Enter)"
|
|
98
|
-
>
|
|
99
|
-
Prev
|
|
100
|
-
</button>
|
|
101
|
-
<button
|
|
102
|
-
type="button"
|
|
103
|
-
className="py-hsp-2xs px-hsp-sm rounded text-caption bg-bg border border-muted text-fg hover:bg-surface"
|
|
104
|
-
onClick={() => {
|
|
105
|
-
const result = findInPage.next();
|
|
106
|
-
setMatchInfo(toMatchInfo(result));
|
|
107
|
-
}}
|
|
108
|
-
title="Next (Enter)"
|
|
109
|
-
>
|
|
110
|
-
Next
|
|
111
|
-
</button>
|
|
112
|
-
<button
|
|
113
|
-
type="button"
|
|
114
|
-
className="py-hsp-2xs px-hsp-sm rounded text-caption bg-bg border border-muted text-fg hover:bg-surface"
|
|
115
|
-
onClick={onClose}
|
|
116
|
-
title="Close (Esc)"
|
|
117
|
-
>
|
|
118
|
-
Close
|
|
119
|
-
</button>
|
|
120
|
-
</div>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, useRef } from "preact/compat";
|
|
4
|
-
import { FindBar } from "./find-bar";
|
|
5
|
-
import { createFindInPage } from "@/utils/find-in-page";
|
|
6
|
-
|
|
7
|
-
const CONTENT_SELECTOR = "article.zd-content";
|
|
8
|
-
|
|
9
|
-
export default function FindInPageInit() {
|
|
10
|
-
const [isTauri, setIsTauri] = useState(false);
|
|
11
|
-
const [visible, setVisible] = useState(false);
|
|
12
|
-
const findInPageRef = useRef(createFindInPage());
|
|
13
|
-
|
|
14
|
-
// Detect Tauri environment
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (typeof window !== "undefined" && "__TAURI_INTERNALS__" in window) {
|
|
17
|
-
setIsTauri(true);
|
|
18
|
-
}
|
|
19
|
-
}, []);
|
|
20
|
-
|
|
21
|
-
// Intercept Cmd/Ctrl+F only in Tauri
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (!isTauri) return;
|
|
24
|
-
|
|
25
|
-
const handler = (e: KeyboardEvent) => {
|
|
26
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
|
|
27
|
-
e.preventDefault();
|
|
28
|
-
setVisible((prev) => !prev);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
document.addEventListener("keydown", handler);
|
|
32
|
-
return () => document.removeEventListener("keydown", handler);
|
|
33
|
-
}, [isTauri]);
|
|
34
|
-
|
|
35
|
-
// Clear search on zfb page navigation. zfb navigates via SPA body swap and
|
|
36
|
-
// fires "zfb:before-preparation" on document before nav — it never fires the
|
|
37
|
-
// native "pagehide" (full-unload) event. The literal is inlined because
|
|
38
|
-
// downstream scaffolds do not depend on @takazudo/zudo-doc as a runtime dep
|
|
39
|
-
// (same reason as the designTokenPanel bootstrap).
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
const handler = () => {
|
|
42
|
-
findInPageRef.current.stop();
|
|
43
|
-
setVisible(false);
|
|
44
|
-
};
|
|
45
|
-
document.addEventListener("zfb:before-preparation", handler);
|
|
46
|
-
return () => document.removeEventListener("zfb:before-preparation", handler);
|
|
47
|
-
}, []);
|
|
48
|
-
|
|
49
|
-
if (!isTauri) return null;
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<FindBar
|
|
53
|
-
visible={visible}
|
|
54
|
-
onClose={() => setVisible(false)}
|
|
55
|
-
findInPage={findInPageRef.current}
|
|
56
|
-
containerSelector={CONTENT_SELECTOR}
|
|
57
|
-
/>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
export interface FindResult {
|
|
2
|
-
matches: number;
|
|
3
|
-
activeMatchOrdinal: number; // 1-based
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface FindInPage {
|
|
7
|
-
find(container: HTMLElement, query: string): FindResult;
|
|
8
|
-
next(): FindResult;
|
|
9
|
-
prev(): FindResult;
|
|
10
|
-
stop(): void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const MATCH_CLASS = "find-match";
|
|
14
|
-
const ACTIVE_CLASS = "find-match-active";
|
|
15
|
-
const MATCH_ATTR = "data-find-match";
|
|
16
|
-
const ACTIVE_ATTR = "data-find-active";
|
|
17
|
-
|
|
18
|
-
const EMPTY_RESULT: FindResult = Object.freeze({ matches: 0, activeMatchOrdinal: 0 });
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a DOM-based find-in-page utility.
|
|
22
|
-
*
|
|
23
|
-
* Limitation: only matches within single text nodes. Cross-element matching
|
|
24
|
-
* (e.g. "Hello world" spanning `<strong>Hello</strong> world`) is not supported.
|
|
25
|
-
*/
|
|
26
|
-
export function createFindInPage(): FindInPage {
|
|
27
|
-
let matchElements: HTMLElement[] = [];
|
|
28
|
-
let activeIndex = -1;
|
|
29
|
-
|
|
30
|
-
function clearMarks(): void {
|
|
31
|
-
const parentsToNormalize = new Set<Node>();
|
|
32
|
-
|
|
33
|
-
for (let i = matchElements.length - 1; i >= 0; i--) {
|
|
34
|
-
const mark = matchElements[i];
|
|
35
|
-
const parent = mark.parentNode;
|
|
36
|
-
if (parent) {
|
|
37
|
-
const textNode = document.createTextNode(mark.textContent || "");
|
|
38
|
-
parent.replaceChild(textNode, mark);
|
|
39
|
-
parentsToNormalize.add(parent);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
for (const parent of parentsToNormalize) {
|
|
43
|
-
(parent as Element).normalize();
|
|
44
|
-
}
|
|
45
|
-
matchElements = [];
|
|
46
|
-
activeIndex = -1;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function setActive(index: number): void {
|
|
50
|
-
if (activeIndex >= 0 && activeIndex < matchElements.length) {
|
|
51
|
-
const prev = matchElements[activeIndex];
|
|
52
|
-
prev.classList.remove(ACTIVE_CLASS);
|
|
53
|
-
prev.removeAttribute(ACTIVE_ATTR);
|
|
54
|
-
}
|
|
55
|
-
activeIndex = index;
|
|
56
|
-
if (activeIndex >= 0 && activeIndex < matchElements.length) {
|
|
57
|
-
const current = matchElements[activeIndex];
|
|
58
|
-
current.classList.add(ACTIVE_CLASS);
|
|
59
|
-
current.setAttribute(ACTIVE_ATTR, "true");
|
|
60
|
-
current.scrollIntoView?.({ block: "center" });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function currentResult(): FindResult {
|
|
65
|
-
if (matchElements.length === 0) {
|
|
66
|
-
return EMPTY_RESULT;
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
matches: matchElements.length,
|
|
70
|
-
activeMatchOrdinal: activeIndex + 1,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function find(container: HTMLElement, query: string): FindResult {
|
|
75
|
-
clearMarks();
|
|
76
|
-
|
|
77
|
-
if (!query) {
|
|
78
|
-
return EMPTY_RESULT;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const lowerQuery = query.toLowerCase();
|
|
82
|
-
|
|
83
|
-
const walker = document.createTreeWalker(
|
|
84
|
-
container,
|
|
85
|
-
NodeFilter.SHOW_TEXT,
|
|
86
|
-
null,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const textNodes: Text[] = [];
|
|
90
|
-
let node: Text | null;
|
|
91
|
-
while ((node = walker.nextNode() as Text | null)) {
|
|
92
|
-
textNodes.push(node);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
for (const textNode of textNodes) {
|
|
96
|
-
const text = textNode.textContent || "";
|
|
97
|
-
const lowerText = text.toLowerCase();
|
|
98
|
-
|
|
99
|
-
const positions: number[] = [];
|
|
100
|
-
let searchFrom = 0;
|
|
101
|
-
while (searchFrom < lowerText.length) {
|
|
102
|
-
const idx = lowerText.indexOf(lowerQuery, searchFrom);
|
|
103
|
-
if (idx === -1) break;
|
|
104
|
-
positions.push(idx);
|
|
105
|
-
searchFrom = idx + lowerQuery.length;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (positions.length === 0) continue;
|
|
109
|
-
|
|
110
|
-
const parent = textNode.parentNode;
|
|
111
|
-
if (!parent) continue;
|
|
112
|
-
|
|
113
|
-
let remainingNode: Text = textNode;
|
|
114
|
-
const nodeMarks: HTMLElement[] = [];
|
|
115
|
-
|
|
116
|
-
for (let i = positions.length - 1; i >= 0; i--) {
|
|
117
|
-
const pos = positions[i];
|
|
118
|
-
const matchLen = query.length;
|
|
119
|
-
|
|
120
|
-
if (pos + matchLen < remainingNode.length) {
|
|
121
|
-
remainingNode.splitText(pos + matchLen);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
let matchNode: Text;
|
|
125
|
-
if (pos > 0) {
|
|
126
|
-
matchNode = remainingNode.splitText(pos);
|
|
127
|
-
} else {
|
|
128
|
-
matchNode = remainingNode;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const mark = document.createElement("mark");
|
|
132
|
-
mark.className = MATCH_CLASS;
|
|
133
|
-
mark.setAttribute(MATCH_ATTR, "true");
|
|
134
|
-
mark.textContent = matchNode.textContent;
|
|
135
|
-
|
|
136
|
-
parent.replaceChild(mark, matchNode);
|
|
137
|
-
nodeMarks.unshift(mark);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
matchElements.push(...nodeMarks);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (matchElements.length === 0) {
|
|
144
|
-
return EMPTY_RESULT;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
setActive(0);
|
|
148
|
-
return currentResult();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function next(): FindResult {
|
|
152
|
-
if (matchElements.length === 0) {
|
|
153
|
-
return EMPTY_RESULT;
|
|
154
|
-
}
|
|
155
|
-
const newIndex = (activeIndex + 1) % matchElements.length;
|
|
156
|
-
setActive(newIndex);
|
|
157
|
-
return currentResult();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function prev(): FindResult {
|
|
161
|
-
if (matchElements.length === 0) {
|
|
162
|
-
return EMPTY_RESULT;
|
|
163
|
-
}
|
|
164
|
-
const newIndex =
|
|
165
|
-
(activeIndex - 1 + matchElements.length) % matchElements.length;
|
|
166
|
-
setActive(newIndex);
|
|
167
|
-
return currentResult();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function stop(): void {
|
|
171
|
-
clearMarks();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return { find, next, prev, stop };
|
|
175
|
-
}
|