create-zudo-doc 0.1.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/LICENSE +21 -0
- package/README.md +146 -0
- package/bin/create-zudo-doc.js +2 -0
- package/dist/api.d.ts +20 -0
- package/dist/api.js +13 -0
- package/dist/claude-md-gen.d.ts +2 -0
- package/dist/claude-md-gen.js +113 -0
- package/dist/cli.d.ts +39 -0
- package/dist/cli.js +157 -0
- package/dist/compose.d.ts +95 -0
- package/dist/compose.js +206 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.js +224 -0
- package/dist/features/body-foot-util.d.ts +10 -0
- package/dist/features/body-foot-util.js +12 -0
- package/dist/features/claude-resources.d.ts +2 -0
- package/dist/features/claude-resources.js +6 -0
- package/dist/features/design-token-panel.d.ts +14 -0
- package/dist/features/design-token-panel.js +27 -0
- package/dist/features/doc-history.d.ts +9 -0
- package/dist/features/doc-history.js +11 -0
- package/dist/features/doc-tags.d.ts +19 -0
- package/dist/features/doc-tags.js +33 -0
- package/dist/features/footer-taglist.d.ts +14 -0
- package/dist/features/footer-taglist.js +17 -0
- package/dist/features/footer.d.ts +8 -0
- package/dist/features/footer.js +10 -0
- package/dist/features/i18n.d.ts +22 -0
- package/dist/features/i18n.js +41 -0
- package/dist/features/image-enlarge.d.ts +11 -0
- package/dist/features/image-enlarge.js +13 -0
- package/dist/features/index.d.ts +15 -0
- package/dist/features/index.js +53 -0
- package/dist/features/llms-txt.d.ts +11 -0
- package/dist/features/llms-txt.js +13 -0
- package/dist/features/search.d.ts +9 -0
- package/dist/features/search.js +11 -0
- package/dist/features/sidebar-resizer.d.ts +14 -0
- package/dist/features/sidebar-resizer.js +16 -0
- package/dist/features/sidebar-toggle.d.ts +13 -0
- package/dist/features/sidebar-toggle.js +15 -0
- package/dist/features/tag-governance.d.ts +14 -0
- package/dist/features/tag-governance.js +16 -0
- package/dist/features/tauri-dev.d.ts +2 -0
- package/dist/features/tauri-dev.js +25 -0
- package/dist/features/tauri.d.ts +11 -0
- package/dist/features/tauri.js +52 -0
- package/dist/features/versioning.d.ts +27 -0
- package/dist/features/versioning.js +43 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +150 -0
- package/dist/preset.d.ts +37 -0
- package/dist/preset.js +156 -0
- package/dist/prompts.d.ts +32 -0
- package/dist/prompts.js +248 -0
- package/dist/scaffold.d.ts +4 -0
- package/dist/scaffold.js +344 -0
- package/dist/settings-gen.d.ts +2 -0
- package/dist/settings-gen.js +237 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +34 -0
- package/dist/zfb-config-gen.d.ts +19 -0
- package/dist/zfb-config-gen.js +222 -0
- package/package.json +65 -0
- package/templates/base/.htmlvalidate.json +5 -0
- package/templates/base/.zfb/doc-history-meta.json +1 -0
- package/templates/base/pages/404.tsx +55 -0
- package/templates/base/pages/_data.ts +179 -0
- package/templates/base/pages/_mdx-components.ts +249 -0
- package/templates/base/pages/docs/[...slug].tsx +448 -0
- package/templates/base/pages/index.tsx +158 -0
- package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
- package/templates/base/pages/lib/_category-nav.tsx +148 -0
- package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
- package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
- package/templates/base/pages/lib/_details.tsx +30 -0
- package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
- package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
- package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
- package/templates/base/pages/lib/_extract-headings.ts +81 -0
- package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
- package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
- package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
- package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
- package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
- package/templates/base/pages/lib/_math-block.tsx +63 -0
- package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
- package/templates/base/pages/lib/_preset-generator.tsx +81 -0
- package/templates/base/pages/lib/_search-widget-script.ts +388 -0
- package/templates/base/pages/lib/_search-widget.tsx +196 -0
- package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
- package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
- package/templates/base/pages/lib/locale-merge.ts +58 -0
- package/templates/base/pages/lib/route-enumerators.ts +302 -0
- package/templates/base/pages/sitemap.xml.tsx +51 -0
- package/templates/base/plugins/connect-adapter.mjs +144 -0
- package/templates/base/plugins/copy-public-plugin.mjs +50 -0
- package/templates/base/plugins/search-index-plugin.mjs +54 -0
- package/templates/base/scripts/run-b4push.sh +102 -0
- package/templates/base/src/components/ai-chat-modal.tsx +15 -0
- package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
- package/templates/base/src/components/content/component-map.ts +25 -0
- package/templates/base/src/components/content/content-blockquote.tsx +16 -0
- package/templates/base/src/components/content/content-code.tsx +117 -0
- package/templates/base/src/components/content/content-link.tsx +83 -0
- package/templates/base/src/components/content/content-ol.tsx +19 -0
- package/templates/base/src/components/content/content-paragraph.tsx +10 -0
- package/templates/base/src/components/content/content-strong.tsx +16 -0
- package/templates/base/src/components/content/content-table.tsx +18 -0
- package/templates/base/src/components/content/content-ul.tsx +18 -0
- package/templates/base/src/components/content/heading-h2.tsx +26 -0
- package/templates/base/src/components/content/heading-h3.tsx +26 -0
- package/templates/base/src/components/content/heading-h4.tsx +26 -0
- package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
- package/templates/base/src/components/doc-history.tsx +18 -0
- package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
- package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
- package/templates/base/src/components/html-preview/preflight.ts +112 -0
- package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
- package/templates/base/src/components/image-enlarge.tsx +19 -0
- package/templates/base/src/components/mobile-toc.tsx +94 -0
- package/templates/base/src/components/preset-generator.tsx +14 -0
- package/templates/base/src/components/sidebar-toggle.tsx +98 -0
- package/templates/base/src/components/sidebar-tree.tsx +543 -0
- package/templates/base/src/components/site-tree-nav.tsx +233 -0
- package/templates/base/src/components/theme-toggle.tsx +93 -0
- package/templates/base/src/components/toc.tsx +63 -0
- package/templates/base/src/components/tree-nav-shared.tsx +71 -0
- package/templates/base/src/config/color-scheme-utils.ts +182 -0
- package/templates/base/src/config/color-schemes.ts +128 -0
- package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
- package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
- package/templates/base/src/config/i18n.ts +225 -0
- package/templates/base/src/config/settings-types.ts +162 -0
- package/templates/base/src/config/sidebars.ts +66 -0
- package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
- package/templates/base/src/config/tag-vocabulary.ts +20 -0
- package/templates/base/src/hooks/use-active-heading.ts +133 -0
- package/templates/base/src/plugins/docs-source-map.ts +103 -0
- package/templates/base/src/plugins/hast-utils.ts +10 -0
- package/templates/base/src/plugins/rehype-code-title.ts +50 -0
- package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
- package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
- package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
- package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
- package/templates/base/src/plugins/remark-admonitions.ts +99 -0
- package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
- package/templates/base/src/plugins/url-utils.ts +4 -0
- package/templates/base/src/styles/global.css +1066 -0
- package/templates/base/src/types/docs-entry.ts +39 -0
- package/templates/base/src/types/heading.ts +5 -0
- package/templates/base/src/types/locale.ts +10 -0
- package/templates/base/src/utils/base.ts +139 -0
- package/templates/base/src/utils/content-files.ts +106 -0
- package/templates/base/src/utils/dedent.ts +24 -0
- package/templates/base/src/utils/docs.ts +335 -0
- package/templates/base/src/utils/git-info.ts +70 -0
- package/templates/base/src/utils/github.ts +19 -0
- package/templates/base/src/utils/header-right-items.ts +38 -0
- package/templates/base/src/utils/nav-scope.ts +63 -0
- package/templates/base/src/utils/sidebar.ts +104 -0
- package/templates/base/src/utils/slug.ts +10 -0
- package/templates/base/src/utils/smart-break.tsx +126 -0
- package/templates/base/src/utils/tags.ts +126 -0
- package/templates/base/tsconfig.json +36 -0
- package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
- package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
- package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
- package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
- package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
- package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
- package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
- package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
- package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
- package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
- package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
- package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
- package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
- package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
- package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
- package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
- package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
- package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
- package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
- package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
- package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
- package/templates/features/tauri/files/src-tauri/build.rs +3 -0
- package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
- package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
- package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
- package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
- package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
- package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
- package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
- package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
- package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
- package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
- package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
- package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
- package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
- package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
- package/templates/features/versioning/files/pages/v/[version]/ja/docs/[...slug].tsx +490 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# b4push — local quality gate run before pushing.
|
|
5
|
+
#
|
|
6
|
+
# Step order (cheap → expensive):
|
|
7
|
+
# 1. Type checking (zfb check)
|
|
8
|
+
# 2. Build (zfb build)
|
|
9
|
+
# 3. HTML validation (html-validate dist/**/*.html)
|
|
10
|
+
# 4. Manual interactive smoke (operator-driven)
|
|
11
|
+
#
|
|
12
|
+
# Env overrides for non-interactive use:
|
|
13
|
+
# B4PUSH_SKIP_HTML_VALIDATE=1 — skip HTML validation (step 3)
|
|
14
|
+
# B4PUSH_SKIP_MANUAL_SMOKE=1 — skip the manual interactive smoke
|
|
15
|
+
|
|
16
|
+
START_TIME=$(date +%s)
|
|
17
|
+
FAILURES=()
|
|
18
|
+
TOTAL_STEPS=4
|
|
19
|
+
CURRENT_STEP=0
|
|
20
|
+
|
|
21
|
+
step() {
|
|
22
|
+
CURRENT_STEP=$((CURRENT_STEP + 1))
|
|
23
|
+
echo ""
|
|
24
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
25
|
+
echo "▶ Step $CURRENT_STEP/$TOTAL_STEPS: $1"
|
|
26
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pass() { echo "✅ $1"; }
|
|
30
|
+
fail() { echo "❌ $1"; FAILURES+=("$1"); }
|
|
31
|
+
skip() { echo "⏭ $1 (skipped)"; }
|
|
32
|
+
|
|
33
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
34
|
+
|
|
35
|
+
# ── Step 1: Type checking ─────────────────────────────
|
|
36
|
+
step "Type checking (zfb check)"
|
|
37
|
+
if (cd "$ROOT_DIR" && pnpm check); then
|
|
38
|
+
pass "Type checking passed"
|
|
39
|
+
else
|
|
40
|
+
fail "Type checking"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# ── Step 2: Build ─────────────────────────────────────
|
|
44
|
+
step "Build (zfb build)"
|
|
45
|
+
if (cd "$ROOT_DIR" && pnpm build); then
|
|
46
|
+
pass "Build passed"
|
|
47
|
+
else
|
|
48
|
+
fail "Build"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# ── Step 3: HTML validation ───────────────────────────
|
|
52
|
+
step "HTML validation (html-validate)"
|
|
53
|
+
if [[ "${B4PUSH_SKIP_HTML_VALIDATE:-}" == "1" ]]; then
|
|
54
|
+
skip "HTML validation (B4PUSH_SKIP_HTML_VALIDATE=1)"
|
|
55
|
+
else
|
|
56
|
+
if (cd "$ROOT_DIR" && pnpm check:html); then
|
|
57
|
+
pass "HTML validation passed"
|
|
58
|
+
else
|
|
59
|
+
fail "HTML validation"
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# ── Step 4: Manual interactive smoke ─────────────────
|
|
64
|
+
step "Manual interactive smoke"
|
|
65
|
+
if [[ "${B4PUSH_SKIP_MANUAL_SMOKE:-}" == "1" ]]; then
|
|
66
|
+
skip "Manual smoke (B4PUSH_SKIP_MANUAL_SMOKE=1)"
|
|
67
|
+
else
|
|
68
|
+
cat <<'MANUAL'
|
|
69
|
+
Run `pnpm preview` in another terminal and exercise:
|
|
70
|
+
• theme toggle (light/dark)
|
|
71
|
+
• mobile menu (narrow viewport)
|
|
72
|
+
• search dropdown (header search)
|
|
73
|
+
• code-block syntax highlighting
|
|
74
|
+
|
|
75
|
+
Press [Enter] when all flows look healthy, or Ctrl-C to abort.
|
|
76
|
+
MANUAL
|
|
77
|
+
if read -r _; then
|
|
78
|
+
pass "Manual smoke acknowledged"
|
|
79
|
+
else
|
|
80
|
+
fail "Manual smoke (aborted)"
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ── Summary ──────────────────────────────────────────
|
|
85
|
+
END_TIME=$(date +%s)
|
|
86
|
+
DURATION=$((END_TIME - START_TIME))
|
|
87
|
+
|
|
88
|
+
echo ""
|
|
89
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
90
|
+
echo " SUMMARY (${DURATION}s)"
|
|
91
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
92
|
+
|
|
93
|
+
if [ ${#FAILURES[@]} -eq 0 ]; then
|
|
94
|
+
echo "✅ All $TOTAL_STEPS checks passed (or skipped). Safe to push."
|
|
95
|
+
exit 0
|
|
96
|
+
else
|
|
97
|
+
echo "❌ ${#FAILURES[@]} check(s) failed:"
|
|
98
|
+
for f in "${FAILURES[@]}"; do
|
|
99
|
+
echo " - $f"
|
|
100
|
+
done
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// W6A stub — no-op default export.
|
|
2
|
+
//
|
|
3
|
+
// The host (zudo-doc showcase) ships a full AI-chat modal island here. In
|
|
4
|
+
// generated downstream projects the AI assistant is gated behind a future
|
|
5
|
+
// settings flag; for now the file exists so unconditional page imports
|
|
6
|
+
// (`pages/lib/_body-end-islands.tsx`) resolve, and the component renders
|
|
7
|
+
// nothing. Wire a real implementation by replacing this file.
|
|
8
|
+
import type { JSX } from "preact";
|
|
9
|
+
|
|
10
|
+
function AiChatModal(): JSX.Element | null {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
AiChatModal.displayName = "AiChatModal";
|
|
14
|
+
|
|
15
|
+
export default AiChatModal;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// W6A stub — no-op default export.
|
|
2
|
+
//
|
|
3
|
+
// The host installs a tiny client-side router bootstrap that re-runs
|
|
4
|
+
// island lifecycle code across zfb navigations. Generated projects ship
|
|
5
|
+
// the no-op so unconditional page imports (`pages/lib/_body-end-islands`)
|
|
6
|
+
// resolve without dragging the routing bridge into every scaffold.
|
|
7
|
+
import type { JSX } from "preact";
|
|
8
|
+
|
|
9
|
+
function ClientRouterBootstrap(): JSX.Element | null {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
ClientRouterBootstrap.displayName = "ClientRouterBootstrap";
|
|
13
|
+
|
|
14
|
+
export default ClientRouterBootstrap;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HeadingH2 } from './heading-h2';
|
|
2
|
+
import { HeadingH3 } from './heading-h3';
|
|
3
|
+
import { HeadingH4 } from './heading-h4';
|
|
4
|
+
import { ContentParagraph } from './content-paragraph';
|
|
5
|
+
import { ContentLink } from './content-link';
|
|
6
|
+
import { ContentStrong } from './content-strong';
|
|
7
|
+
import { ContentBlockquote } from './content-blockquote';
|
|
8
|
+
import { ContentUl } from './content-ul';
|
|
9
|
+
import { ContentOl } from './content-ol';
|
|
10
|
+
import { ContentTable } from './content-table';
|
|
11
|
+
import { ContentCode } from './content-code';
|
|
12
|
+
|
|
13
|
+
export const htmlOverrides = {
|
|
14
|
+
h2: HeadingH2,
|
|
15
|
+
h3: HeadingH3,
|
|
16
|
+
h4: HeadingH4,
|
|
17
|
+
p: ContentParagraph,
|
|
18
|
+
a: ContentLink,
|
|
19
|
+
strong: ContentStrong,
|
|
20
|
+
blockquote: ContentBlockquote,
|
|
21
|
+
ul: ContentUl,
|
|
22
|
+
ol: ContentOl,
|
|
23
|
+
table: ContentTable,
|
|
24
|
+
code: ContentCode,
|
|
25
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
className?: string;
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function ContentBlockquote({ children, className, ...rest }: Props) {
|
|
8
|
+
return (
|
|
9
|
+
<blockquote
|
|
10
|
+
className={`border-l-[3px] border-muted pl-hsp-lg text-muted italic${className ? ` ${className}` : ''}`}
|
|
11
|
+
{...rest}
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</blockquote>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { SmartBreak as SmartBreakImpl } from '@/utils/smart-break';
|
|
2
|
+
|
|
3
|
+
// Preact VNode vs React.ReactNode type mismatch under compat mode; cast so the
|
|
4
|
+
// content override type-checks. Runtime is fine since @astrojs/preact compat is on.
|
|
5
|
+
const SmartBreak = SmartBreakImpl as unknown as (props: {
|
|
6
|
+
children?: unknown;
|
|
7
|
+
}) => any;
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Override for inline `<code>` in MDX. Wraps text-only inline code with
|
|
17
|
+
* <SmartBreak> so that path/URL-like strings (e.g. `src/foo/bar.ts`) can
|
|
18
|
+
* break at delimiters on narrow viewports.
|
|
19
|
+
*
|
|
20
|
+
* Block code inside a fenced block is processed by Shiki, which emits a
|
|
21
|
+
* <code> element with class "language-*" containing a tree of <span>
|
|
22
|
+
* nodes (not a plain string). We detect those and render untouched so
|
|
23
|
+
* syntax highlighting is never disturbed.
|
|
24
|
+
*
|
|
25
|
+
* Astro 6 passes pure-text MDX children wrapped in a `StaticHtml` Preact
|
|
26
|
+
* component whose text lives in `props.value`, not as a string child.
|
|
27
|
+
* `extractText` unwraps that so the heuristic works regardless of MDX's
|
|
28
|
+
* internal wrapping.
|
|
29
|
+
*/
|
|
30
|
+
export function ContentCode({ children, className, ...rest }: Props) {
|
|
31
|
+
const isShikiBlock =
|
|
32
|
+
typeof className === 'string' && /(^|\s)language-/.test(className);
|
|
33
|
+
|
|
34
|
+
const textFromChildren = isShikiBlock ? null : extractText(children);
|
|
35
|
+
|
|
36
|
+
if (textFromChildren === null) {
|
|
37
|
+
return (
|
|
38
|
+
<code className={className} {...rest}>
|
|
39
|
+
{children}
|
|
40
|
+
</code>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<code className={className} {...rest}>
|
|
46
|
+
<SmartBreak>{textFromChildren}</SmartBreak>
|
|
47
|
+
</code>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Walk a React/Preact children value and return a concatenated plain
|
|
53
|
+
* string when the entire subtree is text-only. Returns null if any
|
|
54
|
+
* non-text node (other than the StaticHtml wrapper Astro uses for pure
|
|
55
|
+
* text MDX content) is found — that signals inline markup inside the
|
|
56
|
+
* code span and means we must not inject <wbr>.
|
|
57
|
+
*/
|
|
58
|
+
function extractText(children: unknown): string | null {
|
|
59
|
+
if (typeof children === 'string') return children;
|
|
60
|
+
if (typeof children === 'number') return String(children);
|
|
61
|
+
// Only accept a single VNode whose `props.value` is text (the Astro
|
|
62
|
+
// StaticHtml wrapper used for pure-text MDX children). Deliberately do
|
|
63
|
+
// NOT recurse into arbitrary VNode trees — that would match Shiki's
|
|
64
|
+
// <span>-based block code output and inject <wbr> into syntax-highlighted
|
|
65
|
+
// tokens, breaking block code rendering.
|
|
66
|
+
if (children && typeof children === 'object' && !Array.isArray(children)) {
|
|
67
|
+
const v = children as { props?: { value?: unknown } };
|
|
68
|
+
if (v.props && v.props.value != null) {
|
|
69
|
+
if (
|
|
70
|
+
typeof v.props.value === 'string' ||
|
|
71
|
+
v.props.value instanceof String ||
|
|
72
|
+
(typeof v.props.value === 'object' && typeof (v.props.value as object).toString === 'function')
|
|
73
|
+
) {
|
|
74
|
+
const s = String(v.props.value);
|
|
75
|
+
if (!s || s.startsWith('[object')) return null;
|
|
76
|
+
// Shiki block-code output also arrives as a StaticHtml wrapper, but
|
|
77
|
+
// its value is escaped HTML (e.g. "<span class=...>"). Inline
|
|
78
|
+
// code's value is the plain text of the backtick span (no HTML
|
|
79
|
+
// markup). If the value looks like HTML, refuse to unwrap — the
|
|
80
|
+
// block path below falls through to a plain <code> passthrough.
|
|
81
|
+
if (looksLikeHtmlMarkup(s)) return null;
|
|
82
|
+
return decodeEntities(s);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Detect whether a string is the HTML-escaped rendering of a Shiki block
|
|
91
|
+
* (with nested <span> tokens) rather than the plain text of an inline
|
|
92
|
+
* `<code>` span. The presence of an escaped HTML-tag opener (`<span`,
|
|
93
|
+
* `<code`, etc.) is a reliable signal; plain URL/path inline code
|
|
94
|
+
* never contains those escaped sequences.
|
|
95
|
+
*/
|
|
96
|
+
function looksLikeHtmlMarkup(s: string): boolean {
|
|
97
|
+
return /<(span|code|pre|div|br|i|b|em|strong)\b/i.test(s) || /<(span|code|pre)\s/i.test(s);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Minimal HTML entity decoder for the set MDX produces for inline text
|
|
102
|
+
* (`&`, `<`, `>`, `"`, `'`, numeric). Keeps the
|
|
103
|
+
* result visually identical to the authored source so downstream
|
|
104
|
+
* SmartBreak operates on the original characters.
|
|
105
|
+
*/
|
|
106
|
+
function decodeEntities(s: string): string {
|
|
107
|
+
return s
|
|
108
|
+
.replace(/</g, '<')
|
|
109
|
+
.replace(/>/g, '>')
|
|
110
|
+
.replace(/"/g, '"')
|
|
111
|
+
.replace(/'/g, "'")
|
|
112
|
+
.replace(/'/gi, "'")
|
|
113
|
+
.replace(/ /g, ' ')
|
|
114
|
+
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
|
|
115
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16)))
|
|
116
|
+
.replace(/&/g, '&');
|
|
117
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { SmartBreak as SmartBreakBase } from '../../utils/smart-break';
|
|
2
|
+
|
|
3
|
+
// SmartBreak is defined with Preact's VNode return type, but content
|
|
4
|
+
// components type children as React.ReactNode. Cast to a React-compatible
|
|
5
|
+
// signature; at runtime Preact compat unifies the two.
|
|
6
|
+
const SmartBreak = SmartBreakBase as unknown as (props: {
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
}) => React.ReactElement;
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
href?: string;
|
|
12
|
+
className?: string;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function ContentLink({ href, className, children, ...rest }: Props) {
|
|
18
|
+
// Block links and hash-links (heading anchors) should render without content link styling
|
|
19
|
+
const classes = className ? className.split(' ') : [];
|
|
20
|
+
if (classes.includes('block') || classes.includes('hash-link')) {
|
|
21
|
+
return (
|
|
22
|
+
<a href={href} className={className} {...rest}>
|
|
23
|
+
{children}
|
|
24
|
+
</a>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Astro 6 wraps pure-text MDX children in a `StaticHtml` Preact component
|
|
29
|
+
// whose text lives in `props.value`, not as a direct string child. Unwrap
|
|
30
|
+
// when possible so path-like text gets smart-break treatment.
|
|
31
|
+
const textFromChildren = extractText(children);
|
|
32
|
+
const content =
|
|
33
|
+
textFromChildren !== null ? (
|
|
34
|
+
<SmartBreak>{textFromChildren}</SmartBreak>
|
|
35
|
+
) : (
|
|
36
|
+
children
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<a
|
|
41
|
+
href={href}
|
|
42
|
+
className={`text-accent underline hover:text-accent-hover${className ? ` ${className}` : ''}`}
|
|
43
|
+
{...rest}
|
|
44
|
+
>
|
|
45
|
+
{content}
|
|
46
|
+
</a>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractText(children: unknown): string | null {
|
|
51
|
+
if (typeof children === 'string') return children;
|
|
52
|
+
if (typeof children === 'number') return String(children);
|
|
53
|
+
// Only accept a single StaticHtml-like VNode (Astro's wrapper for pure-text
|
|
54
|
+
// MDX children). Arrays or VNodes with inline markup indicate mixed content
|
|
55
|
+
// that must not be flattened through SmartBreak.
|
|
56
|
+
if (children && typeof children === 'object' && !Array.isArray(children)) {
|
|
57
|
+
const v = children as { props?: { value?: unknown } };
|
|
58
|
+
if (v.props && v.props.value != null) {
|
|
59
|
+
if (
|
|
60
|
+
typeof v.props.value === 'string' ||
|
|
61
|
+
v.props.value instanceof String ||
|
|
62
|
+
(typeof v.props.value === 'object' && typeof (v.props.value as object).toString === 'function')
|
|
63
|
+
) {
|
|
64
|
+
const s = String(v.props.value);
|
|
65
|
+
if (s && !s.startsWith('[object')) return decodeEntities(s);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function decodeEntities(s: string): string {
|
|
73
|
+
return s
|
|
74
|
+
.replace(/</g, '<')
|
|
75
|
+
.replace(/>/g, '>')
|
|
76
|
+
.replace(/"/g, '"')
|
|
77
|
+
.replace(/'/g, "'")
|
|
78
|
+
.replace(/'/gi, "'")
|
|
79
|
+
.replace(/ /g, ' ')
|
|
80
|
+
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
|
|
81
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16)))
|
|
82
|
+
.replace(/&/g, '&');
|
|
83
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
className?: string;
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// 2em indent: enough room for 2-digit markers like "66." (#244)
|
|
8
|
+
// Inline style — Tailwind v4 does not generate arbitrary values from these TSX files
|
|
9
|
+
export function ContentOl({ children, className, ...rest }: Props) {
|
|
10
|
+
return (
|
|
11
|
+
<ol
|
|
12
|
+
{...rest}
|
|
13
|
+
className={className || undefined}
|
|
14
|
+
style={{ paddingLeft: '2em', listStyleType: 'decimal' }}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</ol>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
[key: string]: any;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// Passthrough: no custom styles needed for <p> (inherits from .zd-content base).
|
|
7
|
+
// Override claimed to enable future Tailwind utilities without CSS cascade conflicts.
|
|
8
|
+
export function ContentParagraph({ children, ...rest }: Props) {
|
|
9
|
+
return <p {...rest}>{children}</p>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
className?: string;
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function ContentStrong({ children, className, ...rest }: Props) {
|
|
8
|
+
return (
|
|
9
|
+
<strong
|
|
10
|
+
className={`font-bold text-fg${className ? ` ${className}` : ''}`}
|
|
11
|
+
{...rest}
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</strong>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
className?: string;
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function ContentTable({ children, className, ...rest }: Props) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="overflow-x-auto">
|
|
10
|
+
<table
|
|
11
|
+
className={`w-full border-collapse text-small${className ? ` ${className}` : ''}`}
|
|
12
|
+
{...rest}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</table>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
className?: string;
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// 2em indent via inline style — Tailwind v4 does not generate arbitrary values from these TSX files (#244)
|
|
8
|
+
export function ContentUl({ children, className, ...rest }: Props) {
|
|
9
|
+
return (
|
|
10
|
+
<ul
|
|
11
|
+
{...rest}
|
|
12
|
+
className={className || undefined}
|
|
13
|
+
style={{ paddingLeft: '2em', listStyleType: 'disc' }}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</ul>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { ComponentChildren } from 'preact';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
id?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
children?: ComponentChildren;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function HeadingH2({ id, children, className, ...rest }: Props) {
|
|
12
|
+
return (
|
|
13
|
+
<h2
|
|
14
|
+
id={id}
|
|
15
|
+
className={`text-title font-bold leading-tight pt-vsp-sm border-t-[3px] border-transparent${className ? ` ${className}` : ''}`}
|
|
16
|
+
style={
|
|
17
|
+
{
|
|
18
|
+
borderImage: 'linear-gradient(to right, var(--color-fg), transparent) 1',
|
|
19
|
+
} as CSSProperties
|
|
20
|
+
}
|
|
21
|
+
{...rest}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</h2>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { ComponentChildren } from 'preact';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
id?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
children?: ComponentChildren;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function HeadingH3({ id, children, className, ...rest }: Props) {
|
|
12
|
+
return (
|
|
13
|
+
<h3
|
|
14
|
+
id={id}
|
|
15
|
+
className={`text-body font-bold leading-snug pt-vsp-xs border-t-[2px] border-transparent${className ? ` ${className}` : ''}`}
|
|
16
|
+
style={
|
|
17
|
+
{
|
|
18
|
+
borderImage: 'linear-gradient(to right, var(--color-muted), transparent) 1',
|
|
19
|
+
} as CSSProperties
|
|
20
|
+
}
|
|
21
|
+
{...rest}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</h3>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { ComponentChildren } from 'preact';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
id?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
children?: ComponentChildren;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function HeadingH4({ id, children, className, ...rest }: Props) {
|
|
12
|
+
return (
|
|
13
|
+
<h4
|
|
14
|
+
id={id}
|
|
15
|
+
className={`text-body font-semibold leading-snug pt-vsp-xs border-t border-transparent${className ? ` ${className}` : ''}`}
|
|
16
|
+
style={
|
|
17
|
+
{
|
|
18
|
+
borderImage: 'linear-gradient(to right, var(--color-muted), transparent) 1',
|
|
19
|
+
} as CSSProperties
|
|
20
|
+
}
|
|
21
|
+
{...rest}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</h4>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// W6A stub — no-op default Preact wrapper.
|
|
2
|
+
//
|
|
3
|
+
// When the designTokenPanel feature is enabled, the feature template
|
|
4
|
+
// overwrites this file with the real wrapper that side-effect-imports
|
|
5
|
+
// `@/lib/design-token-panel-bootstrap` (which calls configurePanel and
|
|
6
|
+
// mounts zdtp). Generated projects without the feature ship the no-op
|
|
7
|
+
// so the unconditional `pages/lib/_body-end-islands` import resolves.
|
|
8
|
+
import type { JSX } from "preact";
|
|
9
|
+
|
|
10
|
+
function DesignTokenPanelBootstrap(): JSX.Element | null {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
DesignTokenPanelBootstrap.displayName = "DesignTokenPanelBootstrap";
|
|
14
|
+
|
|
15
|
+
export default DesignTokenPanelBootstrap;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// W6A stub — no-op default export.
|
|
2
|
+
//
|
|
3
|
+
// When the sidebarToggle feature is enabled, the feature template
|
|
4
|
+
// overwrites this file with the real desktop sidebar-toggle island.
|
|
5
|
+
// Generated projects without the feature ship the no-op so the
|
|
6
|
+
// unconditional `pages/lib/_body-end-islands` (or sidebar wrapper)
|
|
7
|
+
// import resolves at typecheck time.
|
|
8
|
+
import type { JSX } from "preact";
|
|
9
|
+
|
|
10
|
+
function DesktopSidebarToggle(): JSX.Element | null {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
DesktopSidebarToggle.displayName = "DesktopSidebarToggle";
|
|
14
|
+
|
|
15
|
+
export default DesktopSidebarToggle;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// W6A stub — no-op default + DocHistory named exports.
|
|
2
|
+
//
|
|
3
|
+
// When the docHistory feature is enabled, the feature template
|
|
4
|
+
// overwrites this file with the real island. Generated projects
|
|
5
|
+
// without the feature ship the no-op so the unconditional
|
|
6
|
+
// `pages/lib/_doc-history-area` import resolves. The host module also
|
|
7
|
+
// exposes `DocHistory` as a named export, so the stub mirrors both
|
|
8
|
+
// shapes to keep the import surface stable.
|
|
9
|
+
import type { JSX } from "preact";
|
|
10
|
+
|
|
11
|
+
function DocHistoryComponent(): JSX.Element | null {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
DocHistoryComponent.displayName = "DocHistory";
|
|
15
|
+
|
|
16
|
+
export default DocHistoryComponent;
|
|
17
|
+
|
|
18
|
+
export const DocHistory = DocHistoryComponent;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useEffect, useState } from "preact/compat";
|
|
2
|
+
import type { HighlighterCore } from "shiki";
|
|
3
|
+
|
|
4
|
+
let highlighterPromise: Promise<HighlighterCore> | null = null;
|
|
5
|
+
|
|
6
|
+
function getHighlighter(): Promise<HighlighterCore> {
|
|
7
|
+
if (!highlighterPromise) {
|
|
8
|
+
highlighterPromise = import("shiki")
|
|
9
|
+
.then(({ createHighlighter }) =>
|
|
10
|
+
createHighlighter({
|
|
11
|
+
themes: ["catppuccin-latte", "vitesse-dark"],
|
|
12
|
+
langs: ["html", "css", "javascript"],
|
|
13
|
+
}),
|
|
14
|
+
)
|
|
15
|
+
.catch((err) => {
|
|
16
|
+
// Clear cached rejection so next call retries
|
|
17
|
+
highlighterPromise = null;
|
|
18
|
+
throw err;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return highlighterPromise;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface HighlightedCodeProps {
|
|
25
|
+
code: string;
|
|
26
|
+
language: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function HighlightedCode({
|
|
30
|
+
code,
|
|
31
|
+
language,
|
|
32
|
+
}: HighlightedCodeProps) {
|
|
33
|
+
const [html, setHtml] = useState<string | null>(null);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
let cancelled = false;
|
|
37
|
+
getHighlighter()
|
|
38
|
+
.then((highlighter) => {
|
|
39
|
+
if (cancelled) return;
|
|
40
|
+
const lang = highlighter.getLoadedLanguages().includes(language)
|
|
41
|
+
? language
|
|
42
|
+
: "text";
|
|
43
|
+
const result = highlighter.codeToHtml(code, {
|
|
44
|
+
lang,
|
|
45
|
+
themes: { light: "catppuccin-latte", dark: "vitesse-dark" },
|
|
46
|
+
defaultColor: false,
|
|
47
|
+
});
|
|
48
|
+
setHtml(result);
|
|
49
|
+
})
|
|
50
|
+
.catch(() => {
|
|
51
|
+
// Shiki failed to load — keep showing the plain-text fallback
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
};
|
|
56
|
+
}, [code, language]);
|
|
57
|
+
|
|
58
|
+
if (!html) {
|
|
59
|
+
return (
|
|
60
|
+
<pre className="m-0 p-hsp-md bg-code-bg text-caption leading-relaxed overflow-x-auto">
|
|
61
|
+
<code className="font-mono whitespace-pre">
|
|
62
|
+
{code}
|
|
63
|
+
</code>
|
|
64
|
+
</pre>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className="zd-html-preview-code"
|
|
71
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|