@takazudo/zudo-doc 5.20.0 → 5.22.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/CHANGELOG.md +23 -0
- package/dist/compiled.css +72 -0
- package/dist/config.d.ts +12 -1
- package/dist/config.js +3 -1
- package/dist/content.css +55 -0
- package/dist/doc-history-area/index.d.ts +2 -0
- package/dist/doc-history-area/index.js +5 -4
- package/dist/doc-metainfo-area/index.d.ts +2 -0
- package/dist/doc-metainfo-area/index.js +8 -3
- package/dist/factory-context/index.d.ts +2 -0
- package/dist/home-intro/index.d.ts +10 -0
- package/dist/home-intro/index.js +20 -0
- package/dist/home-intro/prepare.d.ts +9 -0
- package/dist/home-intro/prepare.js +93 -0
- package/dist/home-intro/resolve.d.ts +5 -0
- package/dist/home-intro/resolve.js +10 -0
- package/dist/home-intro/types.d.ts +10 -0
- package/dist/home-intro/types.js +0 -0
- package/dist/home-page/index.js +42 -22
- package/dist/i18n-defaults/index.js +2 -0
- package/dist/plugins/doc-history.js +7 -3
- package/dist/plugins/img-src-check.d.ts +3 -0
- package/dist/plugins/img-src-check.js +21 -0
- package/dist/plugins/internal/claude-resources/generate.js +2 -2
- package/dist/plugins/internal/doc-history/index.d.ts +6 -1
- package/dist/plugins/internal/doc-history/index.js +1 -0
- package/dist/plugins/internal/img-src-check/index.d.ts +65 -0
- package/dist/plugins/internal/img-src-check/index.js +179 -0
- package/dist/plugins/internal/resource-docs-shared/index.d.ts +1 -1
- package/dist/plugins/internal/resource-docs-shared/index.js +6 -1
- package/dist/plugins/internal/resource-docs-shared/links.d.ts +6 -1
- package/dist/plugins/internal/resource-docs-shared/links.js +21 -3
- package/dist/plugins/internal/resource-docs-shared/skills.js +27 -5
- package/dist/plugins/routes.js +4 -1
- package/dist/preset.d.ts +9 -1
- package/dist/preset.js +16 -2
- package/dist/route-context/index.js +1 -0
- package/dist/route-context-payload/index.d.ts +2 -0
- package/dist/route-context-payload/index.js +2 -1
- package/dist/route-context-payload/types.d.ts +2 -0
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +12 -0
- package/package.json +15 -2
- package/routes-src/_virtual.d.ts +3 -1
- package/virtual-modules.d.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,29 @@ The format is based on Keep a Changelog, and release notes are generated from th
|
|
|
8
8
|
|
|
9
9
|
No unreleased changes.
|
|
10
10
|
|
|
11
|
+
## [5.22.0] - 2026-09-11
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- Added `docMetainfoFields` to select the metadata fields displayed on documentation pages. It defaults to all fields; an empty array hides them all, and `["updated"]` displays Updated even when Created has the same formatted date. (`06044fa30`, `3832e08af`)
|
|
16
|
+
- Added `docHistoryUi: false` to retain generated git metadata while disabling the history viewer, history JSON generation, and development proxy. Configured view-source links remain available. (`84cd63af5`)
|
|
17
|
+
- Added a post-build check for missing site-absolute `<img src>` files, using the existing `onBrokenMarkdownLinks` severity setting. (`9cc259d65`)
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
- Mirrored Claude commands and agents and shared Claude/Codex skills now render repository-relative links as labels when their targets are not emitted. Code examples and links to emitted skill-body sub-pages are preserved. (`bb6d0390c`)
|
|
22
|
+
|
|
23
|
+
## [5.21.0] - 2026-09-09
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
- Added compact Markdown introductions to the home pages. `home.introMarkdown` renders serializable Markdown below the home identity on `/`, and each non-default locale can override it with `LocaleConfig.introMarkdown`. Empty values keep the previous layout, so existing sites are unaffected. (`77ed96b3f`, `8e10ecbc1`)
|
|
28
|
+
- Added `home.sitemapHeading` and `LocaleConfig.sitemapHeading` to override the home sitemap section title. A blank value restores the translated package default. (`77ed96b3f`)
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
- Rendered the home sitemap and tag section headings through one shared component, so both align with the full-width content band instead of the sitemap heading sitting on the narrower inner column. (`849e04a57`)
|
|
33
|
+
|
|
11
34
|
## [5.20.0] - 2026-09-09
|
|
12
35
|
|
|
13
36
|
### Features
|
package/dist/compiled.css
CHANGED
|
@@ -2155,6 +2155,11 @@
|
|
|
2155
2155
|
max-width: 52.5rem;
|
|
2156
2156
|
}
|
|
2157
2157
|
}
|
|
2158
|
+
.lg\:flex-1 {
|
|
2159
|
+
@media (width >= 1024px) {
|
|
2160
|
+
flex: 1;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2158
2163
|
.lg\:grid-cols-3 {
|
|
2159
2164
|
@media (width >= 1024px) {
|
|
2160
2165
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
@@ -2727,6 +2732,73 @@ a.text-accent.underline {
|
|
|
2727
2732
|
border-radius: var(--zdc-admonition-radius, 0 var(--radius-DEFAULT) var(--radius-DEFAULT) 0);
|
|
2728
2733
|
border-left-width: var(--zdc-admonition-border-width, 4px);
|
|
2729
2734
|
}
|
|
2735
|
+
.zd-home-inner {
|
|
2736
|
+
inline-size: 100%;
|
|
2737
|
+
max-inline-size: 60rem;
|
|
2738
|
+
min-inline-size: 0;
|
|
2739
|
+
margin-inline: auto;
|
|
2740
|
+
overflow-wrap: anywhere;
|
|
2741
|
+
}
|
|
2742
|
+
.zd-home-copy, .zd-home-links {
|
|
2743
|
+
min-inline-size: 0;
|
|
2744
|
+
}
|
|
2745
|
+
.zd-home-links {
|
|
2746
|
+
overflow-wrap: anywhere;
|
|
2747
|
+
}
|
|
2748
|
+
.zd-home-rule {
|
|
2749
|
+
inline-size: 100%;
|
|
2750
|
+
margin-block: var(--spacing-vsp-xl);
|
|
2751
|
+
margin-inline: 0;
|
|
2752
|
+
border: 0;
|
|
2753
|
+
border-block-start: 1px solid var(--color-muted);
|
|
2754
|
+
}
|
|
2755
|
+
.zd-compact-prose {
|
|
2756
|
+
min-inline-size: 0;
|
|
2757
|
+
text-align: left;
|
|
2758
|
+
font-size: 1rem;
|
|
2759
|
+
line-height: 1.75;
|
|
2760
|
+
overflow-wrap: anywhere;
|
|
2761
|
+
}
|
|
2762
|
+
.zd-compact-prose > :where(* + *) {
|
|
2763
|
+
margin-top: var(--flow-space, 1rem);
|
|
2764
|
+
}
|
|
2765
|
+
.zd-compact-prose :where(h2, h3, h4, h5, h6) {
|
|
2766
|
+
font-weight: var(--font-weight-bold, 700);
|
|
2767
|
+
line-height: 1.4;
|
|
2768
|
+
padding: 0;
|
|
2769
|
+
border: 0;
|
|
2770
|
+
background: none;
|
|
2771
|
+
}
|
|
2772
|
+
.zd-compact-prose :where(h2) {
|
|
2773
|
+
font-size: 1.3rem;
|
|
2774
|
+
--flow-space: 1.75rem;
|
|
2775
|
+
}
|
|
2776
|
+
.zd-compact-prose :where(h3) {
|
|
2777
|
+
font-size: 1.125rem;
|
|
2778
|
+
--flow-space: 1.5rem;
|
|
2779
|
+
}
|
|
2780
|
+
.zd-compact-prose :where(h4) {
|
|
2781
|
+
font-size: 1rem;
|
|
2782
|
+
--flow-space: 1.25rem;
|
|
2783
|
+
}
|
|
2784
|
+
.zd-compact-prose :where(h5, h6) {
|
|
2785
|
+
font-size: .9375rem;
|
|
2786
|
+
--flow-space: 1.25rem;
|
|
2787
|
+
}
|
|
2788
|
+
.zd-compact-prose > :where(h2, h3, h4, h5, h6) + * {
|
|
2789
|
+
margin-top: .5rem;
|
|
2790
|
+
}
|
|
2791
|
+
.zd-compact-prose > :first-child {
|
|
2792
|
+
margin-top: 0;
|
|
2793
|
+
}
|
|
2794
|
+
.zd-compact-prose :where(pre) {
|
|
2795
|
+
max-inline-size: 100%;
|
|
2796
|
+
overflow-x: auto;
|
|
2797
|
+
}
|
|
2798
|
+
.zd-compact-prose :where(img) {
|
|
2799
|
+
max-inline-size: 100%;
|
|
2800
|
+
block-size: auto;
|
|
2801
|
+
}
|
|
2730
2802
|
.page-loading-overlay {
|
|
2731
2803
|
position: fixed;
|
|
2732
2804
|
inset: 0;
|
package/dist/config.d.ts
CHANGED
|
@@ -143,7 +143,7 @@ export interface ZudoDocConfig {
|
|
|
143
143
|
/**
|
|
144
144
|
* Package-owned home-page layout. Set `wide: true` to let the category grid
|
|
145
145
|
* fill most of the viewport on both `/` and locale home routes.
|
|
146
|
-
* @default { wide: false }
|
|
146
|
+
* @default { wide: false, introMarkdown: "", sitemapHeading: "" }
|
|
147
147
|
*/
|
|
148
148
|
home?: HomeConfig;
|
|
149
149
|
/**
|
|
@@ -249,6 +249,11 @@ export interface ZudoDocConfig {
|
|
|
249
249
|
* @default false
|
|
250
250
|
*/
|
|
251
251
|
docMetainfo?: boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Metadata fields to show in the doc metadata area.
|
|
254
|
+
* @default ["created", "updated", "author"]
|
|
255
|
+
*/
|
|
256
|
+
docMetainfoFields?: Array<"created" | "updated" | "author">;
|
|
252
257
|
/**
|
|
253
258
|
* Enable the `/docs/tags` + `/docs/tags/[tag]` tag index routes.
|
|
254
259
|
* @default false
|
|
@@ -378,6 +383,12 @@ export interface ZudoDocConfig {
|
|
|
378
383
|
* @default false
|
|
379
384
|
*/
|
|
380
385
|
docHistory?: boolean;
|
|
386
|
+
/**
|
|
387
|
+
* Enable the doc history dropdown UI, history JSON generation, and dev
|
|
388
|
+
* history proxy while retaining the history metadata manifest.
|
|
389
|
+
* @default true
|
|
390
|
+
*/
|
|
391
|
+
docHistoryUi?: boolean;
|
|
381
392
|
/**
|
|
382
393
|
* Glob patterns matched against the doc slug (path minus extension, `/index`
|
|
383
394
|
* stripped, root = `index`) that exclude matching pages from git-history
|
package/dist/config.js
CHANGED
|
@@ -45,7 +45,7 @@ const DEFAULT_SETTINGS = {
|
|
|
45
45
|
favicon: void 0,
|
|
46
46
|
base: "/",
|
|
47
47
|
trailingSlash: false,
|
|
48
|
-
home: { wide: false },
|
|
48
|
+
home: { wide: false, introMarkdown: "", sitemapHeading: "" },
|
|
49
49
|
siteTreeNavIgnore: [],
|
|
50
50
|
minifyHtml: true,
|
|
51
51
|
docsDir: "src/content/docs",
|
|
@@ -68,6 +68,7 @@ const DEFAULT_SETTINGS = {
|
|
|
68
68
|
},
|
|
69
69
|
sitemap: false,
|
|
70
70
|
docMetainfo: false,
|
|
71
|
+
docMetainfoFields: ["created", "updated", "author"],
|
|
71
72
|
docTags: false,
|
|
72
73
|
tagPlacement: "after-title",
|
|
73
74
|
tagGovernance: "off",
|
|
@@ -94,6 +95,7 @@ const DEFAULT_SETTINGS = {
|
|
|
94
95
|
dynamicPageTransition: false,
|
|
95
96
|
frontmatterPreview: false,
|
|
96
97
|
docHistory: false,
|
|
98
|
+
docHistoryUi: true,
|
|
97
99
|
docHistoryExclude: [],
|
|
98
100
|
assetViewer: false,
|
|
99
101
|
assetViewerDir: "assets",
|
package/dist/content.css
CHANGED
|
@@ -533,3 +533,58 @@ a.text-accent.underline {
|
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
/* END --zdc-* component tokens */
|
|
536
|
+
|
|
537
|
+
/* Compact homepage Markdown uses the same non-heading components and tokens.
|
|
538
|
+
* Shared width primitive is also available to the home identity group. */
|
|
539
|
+
.zd-home-inner {
|
|
540
|
+
inline-size: 100%;
|
|
541
|
+
max-inline-size: 60rem;
|
|
542
|
+
min-inline-size: 0;
|
|
543
|
+
margin-inline: auto;
|
|
544
|
+
overflow-wrap: anywhere;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.zd-home-copy,
|
|
548
|
+
.zd-home-links {
|
|
549
|
+
min-inline-size: 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.zd-home-links {
|
|
553
|
+
overflow-wrap: anywhere;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.zd-home-rule {
|
|
557
|
+
inline-size: 100%;
|
|
558
|
+
margin-block: var(--spacing-vsp-xl);
|
|
559
|
+
margin-inline: 0;
|
|
560
|
+
border: 0;
|
|
561
|
+
border-block-start: 1px solid var(--color-muted);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.zd-compact-prose {
|
|
565
|
+
min-inline-size: 0;
|
|
566
|
+
text-align: left;
|
|
567
|
+
font-size: 1rem;
|
|
568
|
+
line-height: 1.75;
|
|
569
|
+
overflow-wrap: anywhere;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.zd-compact-prose > :where(* + *) {
|
|
573
|
+
margin-top: var(--flow-space, 1rem);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.zd-compact-prose :where(h2, h3, h4, h5, h6) {
|
|
577
|
+
font-weight: var(--font-weight-bold, 700);
|
|
578
|
+
line-height: 1.4;
|
|
579
|
+
padding: 0;
|
|
580
|
+
border: 0;
|
|
581
|
+
background: none;
|
|
582
|
+
}
|
|
583
|
+
.zd-compact-prose :where(h2) { font-size: 1.3rem; --flow-space: 1.75rem; }
|
|
584
|
+
.zd-compact-prose :where(h3) { font-size: 1.125rem; --flow-space: 1.5rem; }
|
|
585
|
+
.zd-compact-prose :where(h4) { font-size: 1rem; --flow-space: 1.25rem; }
|
|
586
|
+
.zd-compact-prose :where(h5, h6) { font-size: .9375rem; --flow-space: 1.25rem; }
|
|
587
|
+
.zd-compact-prose > :where(h2, h3, h4, h5, h6) + * { margin-top: .5rem; }
|
|
588
|
+
.zd-compact-prose > :first-child { margin-top: 0; }
|
|
589
|
+
.zd-compact-prose :where(pre) { max-inline-size: 100%; overflow-x: auto; }
|
|
590
|
+
.zd-compact-prose :where(img) { max-inline-size: 100%; block-size: auto; }
|
|
@@ -14,6 +14,8 @@ export interface DocHistoryMetaEntry {
|
|
|
14
14
|
/** Settings subset read by the DocHistoryArea factory. */
|
|
15
15
|
export interface DocHistoryAreaSettings {
|
|
16
16
|
docHistory: boolean;
|
|
17
|
+
/** Keep the metadata manifest while suppressing the history island. */
|
|
18
|
+
docHistoryUi?: boolean;
|
|
17
19
|
docHistoryExclude?: string[];
|
|
18
20
|
bodyFootUtilArea: {
|
|
19
21
|
viewSourceLink?: boolean;
|
|
@@ -31,7 +31,8 @@ function createDocHistoryArea(ctx) {
|
|
|
31
31
|
}) {
|
|
32
32
|
if (!settings.docHistory) return null;
|
|
33
33
|
const historySlug = toHistorySlug(slug);
|
|
34
|
-
|
|
34
|
+
const showDocHistoryUi = settings.docHistoryUi !== false;
|
|
35
|
+
if (isHistoryExcluded(historySlug) && showDocHistoryUi) return null;
|
|
35
36
|
const effectiveHistoryLocale = isFallback ? defaultLocale : locale;
|
|
36
37
|
const composedSlug = effectiveHistoryLocale === defaultLocale ? historySlug : `${effectiveHistoryLocale}/${historySlug}`;
|
|
37
38
|
const meta = docHistoryMeta[composedSlug];
|
|
@@ -56,7 +57,7 @@ function createDocHistoryArea(ctx) {
|
|
|
56
57
|
updatedDate ? `: ${updatedDate}` : ""
|
|
57
58
|
] })
|
|
58
59
|
] });
|
|
59
|
-
const docHistoryIsland = Island({
|
|
60
|
+
const docHistoryIsland = showDocHistoryUi ? Island({
|
|
60
61
|
when: "idle",
|
|
61
62
|
ssrFallback: fallback,
|
|
62
63
|
children: /* @__PURE__ */ jsx(
|
|
@@ -69,12 +70,12 @@ function createDocHistoryArea(ctx) {
|
|
|
69
70
|
dateFormats: docHistoryDateFormats
|
|
70
71
|
}
|
|
71
72
|
)
|
|
72
|
-
});
|
|
73
|
+
}) : null;
|
|
74
|
+
void historyLabel;
|
|
73
75
|
const utilSettings = settings.bodyFootUtilArea;
|
|
74
76
|
const sourceExt = meta ? meta.ext : sourceFileExt;
|
|
75
77
|
const sourceUrl = utilSettings && utilSettings.viewSourceLink && entrySlug && sourceExt && contentDir ? buildGitHubSourceUrl(contentDir, entrySlug + sourceExt) : null;
|
|
76
78
|
const viewSourceLabel = t("doc.viewSource", locale);
|
|
77
|
-
void historyLabel;
|
|
78
79
|
return /* @__PURE__ */ jsx(
|
|
79
80
|
BodyFootUtilArea,
|
|
80
81
|
{
|
|
@@ -13,6 +13,8 @@ export interface DocHistoryMetaEntry {
|
|
|
13
13
|
/** Settings subset read by the DocMetainfoArea factory. */
|
|
14
14
|
export interface DocMetainfoAreaSettings {
|
|
15
15
|
docMetainfo: boolean;
|
|
16
|
+
/** Metadata fields shown in the doc metadata area; omitted means all fields. */
|
|
17
|
+
docMetainfoFields?: Array<"created" | "updated" | "author">;
|
|
16
18
|
}
|
|
17
19
|
export interface DocMetainfoAreaProps {
|
|
18
20
|
/** Page slug, e.g. "getting-started/intro". */
|
|
@@ -13,6 +13,11 @@ function createDocMetainfoArea(ctx) {
|
|
|
13
13
|
const dateFormatsFor = deriveDateFormats(ctx);
|
|
14
14
|
function DocMetainfoArea({ slug, locale, isFallback }) {
|
|
15
15
|
if (!settings.docMetainfo) return null;
|
|
16
|
+
const fields = settings.docMetainfoFields;
|
|
17
|
+
const showCreated = fields === void 0 || fields.includes("created");
|
|
18
|
+
const showUpdated = fields === void 0 || fields.includes("updated");
|
|
19
|
+
const showAuthor = fields === void 0 || fields.includes("author");
|
|
20
|
+
if (!showCreated && !showUpdated && !showAuthor) return null;
|
|
16
21
|
const historySlug = toHistorySlug(slug);
|
|
17
22
|
const effectiveHistoryLocale = isFallback ? defaultLocale : locale;
|
|
18
23
|
const composedSlug = effectiveHistoryLocale === defaultLocale ? historySlug : `${effectiveHistoryLocale}/${historySlug}`;
|
|
@@ -21,9 +26,9 @@ function createDocMetainfoArea(ctx) {
|
|
|
21
26
|
return /* @__PURE__ */ jsx(
|
|
22
27
|
DocMetainfo,
|
|
23
28
|
{
|
|
24
|
-
createdAt: meta.createdDate ? formatDate(meta.createdDate, locale, dateFormatsFor(locale).full) : null,
|
|
25
|
-
updatedAt: meta.updatedDate ? formatDate(meta.updatedDate, locale, dateFormatsFor(locale).full) : null,
|
|
26
|
-
author: meta.author || null,
|
|
29
|
+
createdAt: showCreated && meta.createdDate ? formatDate(meta.createdDate, locale, dateFormatsFor(locale).full) : null,
|
|
30
|
+
updatedAt: showUpdated && meta.updatedDate ? formatDate(meta.updatedDate, locale, dateFormatsFor(locale).full) : null,
|
|
31
|
+
author: showAuthor ? meta.author || null : null,
|
|
27
32
|
createdLabel: t("doc.created", locale),
|
|
28
33
|
updatedLabel: t("doc.updated", locale)
|
|
29
34
|
}
|
|
@@ -124,6 +124,8 @@ export interface RouteContext<S = Settings> extends UrlHelpers, NavSourceDocsAPI
|
|
|
124
124
|
themePackRegistry: ThemePackRegistry | null;
|
|
125
125
|
/** Author-facing asset index, or `null` when the viewer is disabled. */
|
|
126
126
|
assetManifest: AssetManifest | null;
|
|
127
|
+
/** Prepared, serializable homepage introductions. */
|
|
128
|
+
homeIntros: import("../home-intro/types.js").PreparedHomeIntros;
|
|
127
129
|
/** The reconstructed i18n surface. */
|
|
128
130
|
i18n: FactoryI18n;
|
|
129
131
|
/** Default locale code (un-prefixed `/docs/...`). */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import { h } from "preact";
|
|
4
|
+
import type { PreparedHomeIntro } from "./types.js";
|
|
5
|
+
export type { IntroNode, PreparedHomeIntro, PreparedHomeIntros } from "./types.js";
|
|
6
|
+
export { resolveHomeIntro } from "./resolve.js";
|
|
7
|
+
/** Synchronous SSR view of output produced by home-intro/prepare. No raw HTML sink. */
|
|
8
|
+
export declare function CompactProse({ intro }: {
|
|
9
|
+
intro: PreparedHomeIntro | null | undefined;
|
|
10
|
+
}): h.JSX.Element | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx } from "preact/jsx-runtime";
|
|
2
|
+
import { h } from "preact";
|
|
3
|
+
import { defaultComponents } from "../content/index.js";
|
|
4
|
+
import { makeAdmonition } from "../content-admonition/index.js";
|
|
5
|
+
import { resolveHomeIntro } from "./resolve.js";
|
|
6
|
+
const { h2: _h2, h3: _h3, h4: _h4, ...typography } = defaultComponents;
|
|
7
|
+
const components = { ...typography };
|
|
8
|
+
for (const variant of ["note", "tip", "info", "warning", "danger", "caution", "important"]) components[variant] = makeAdmonition(variant);
|
|
9
|
+
function renderNode(node) {
|
|
10
|
+
if (typeof node === "string") return node;
|
|
11
|
+
return h(components[node.tag] ?? node.tag, node.attrs, node.children.map(renderNode));
|
|
12
|
+
}
|
|
13
|
+
function CompactProse({ intro }) {
|
|
14
|
+
if (!intro?.nodes.length) return null;
|
|
15
|
+
return /* @__PURE__ */ jsx("div", { class: "zd-content zd-compact-prose", children: intro.nodes.map(renderNode) });
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
CompactProse,
|
|
19
|
+
resolveHomeIntro
|
|
20
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Settings } from "../settings.js";
|
|
2
|
+
import type { PreparedHomeIntro, PreparedHomeIntros } from "./types.js";
|
|
3
|
+
export type HomeIntroSettings = Pick<Settings, "home" | "locales" | "defaultLocale" | "base" | "mermaid" | "transclude">;
|
|
4
|
+
/** URL context is the localized homepage, never an invented Markdown file. */
|
|
5
|
+
export declare function resolveIntroUrl(value: string, settings: Pick<HomeIntroSettings, "base" | "defaultLocale">, locale: string, image?: boolean): string;
|
|
6
|
+
/** Async preparation seam. No author JavaScript is ever compiled or evaluated. */
|
|
7
|
+
export declare function prepareHomeIntro(source: string, settings: HomeIntroSettings, locale: string): Promise<PreparedHomeIntro | null>;
|
|
8
|
+
/** Prepare every configured locale once in the async virtual-context loader. */
|
|
9
|
+
export declare function prepareHomeIntros(settings: HomeIntroSettings): Promise<PreparedHomeIntros>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { parseFragment } from "parse5";
|
|
2
|
+
import { buildMarkdownFeatures } from "../preset.js";
|
|
3
|
+
import { resolveHomeIntro } from "./resolve.js";
|
|
4
|
+
const origin = "https://home-intro.invalid";
|
|
5
|
+
function resolveIntroUrl(value, settings, locale, image = false) {
|
|
6
|
+
const url = value.trim();
|
|
7
|
+
if (/[\u0000-\u001f\u007f\\]/u.test(url)) throw new Error("home.introMarkdown: URL contains controls or backslashes");
|
|
8
|
+
if (url.startsWith("#")) return url;
|
|
9
|
+
if (url.startsWith("//")) throw new Error("home.introMarkdown: protocol-relative URLs are unsupported");
|
|
10
|
+
const base = `/${settings.base.split("/").filter(Boolean).join("/")}`;
|
|
11
|
+
const prefix = base === "/" ? "" : base;
|
|
12
|
+
const home = `${prefix}/${locale === settings.defaultLocale ? "" : `${locale}/`}`;
|
|
13
|
+
if (url.startsWith("/")) {
|
|
14
|
+
const based = prefix && url !== prefix && !url.startsWith(`${prefix}/`) && !url.startsWith(`${prefix}?`) && !url.startsWith(`${prefix}#`) ? `${prefix}${url}` : url;
|
|
15
|
+
const parsed2 = new URL(based, origin);
|
|
16
|
+
return `${parsed2.pathname}${parsed2.search}${parsed2.hash}`;
|
|
17
|
+
}
|
|
18
|
+
const parsed = new URL(url, `${origin}${home}`);
|
|
19
|
+
const allowed = image ? ["https:", "http:"] : ["https:", "http:", "mailto:", "tel:"];
|
|
20
|
+
if (!allowed.includes(parsed.protocol)) throw new Error(`home.introMarkdown: unsafe URL scheme ${parsed.protocol}`);
|
|
21
|
+
if (!/^[a-z][a-z0-9+.-]*:/iu.test(url)) return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
22
|
+
return url;
|
|
23
|
+
}
|
|
24
|
+
function inspect(node, settings, locale, source = "") {
|
|
25
|
+
if (node.type === "mdxTextExpression" && (source.slice(node.position.end.offset, node.position.end.offset + 2) === "^{" || source[node.position.start.offset - 1] === "^")) return;
|
|
26
|
+
if (node.type === "html" || node.type.startsWith("mdx") || node.type === "yaml" || node.type.endsWith("Directive")) {
|
|
27
|
+
throw new Error(`home.introMarkdown: unsupported ${node.type} at line ${node.position.start.line}; use plain Markdown, without HTML, JSX, imports, frontmatter or directives`);
|
|
28
|
+
}
|
|
29
|
+
if (node.type === "paragraph" && Array.isArray(node.children) && node.children[0]?.type === "text") {
|
|
30
|
+
const first = node.children[0].value.trimStart();
|
|
31
|
+
if (["import", "export"].includes(first.split(/\s+/u)[0] ?? "")) {
|
|
32
|
+
throw new Error("home.introMarkdown: import/export statements are unsupported");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if ("url" in node && typeof node.url === "string") resolveIntroUrl(node.url, settings, locale, node.type === "image");
|
|
36
|
+
if ("children" in node && Array.isArray(node.children)) for (const child of node.children) inspect(child, settings, locale, source);
|
|
37
|
+
}
|
|
38
|
+
const allowedTags = new Set("p h1 h2 h3 h4 h5 h6 a img em strong del ul ol li blockquote pre code span br hr table thead tbody tr th td input div ruby rb rt rp note tip info warning danger caution important".split(" "));
|
|
39
|
+
const plainAttrs = /* @__PURE__ */ new Set(["title", "alt", "class", "id", "start", "align", "width", "height", "data-mermaid", "data-lang"]);
|
|
40
|
+
function prepareNode(node, settings, locale) {
|
|
41
|
+
if (node.nodeName === "#text") return node.value;
|
|
42
|
+
if (!("tagName" in node)) return null;
|
|
43
|
+
if (!allowedTags.has(node.tagName) || node.namespaceURI !== "http://www.w3.org/1999/xhtml") throw new Error(`home.introMarkdown: unsupported rendered element ${node.tagName}`);
|
|
44
|
+
if (node.tagName === "a" && node.attrs.some((attr) => attr.name === "class" && attr.value.split(" ").includes("hash-link"))) return null;
|
|
45
|
+
const attrs = {};
|
|
46
|
+
for (const { name, value } of node.attrs) {
|
|
47
|
+
if (name === "href" && node.tagName === "a") attrs.href = resolveIntroUrl(value, settings, locale);
|
|
48
|
+
else if (name === "src" && node.tagName === "img") attrs.src = resolveIntroUrl(value, settings, locale, true);
|
|
49
|
+
else if (name === "style" && ["th", "td"].includes(node.tagName) && ["text-align: left", "text-align: center", "text-align: right"].includes(value)) attrs.style = value;
|
|
50
|
+
else if (plainAttrs.has(name)) attrs[name === "class" ? "className" : name] = value;
|
|
51
|
+
else if (node.tagName === "input" && name === "type" && value === "checkbox") attrs.type = value;
|
|
52
|
+
else if (node.tagName === "input" && (name === "checked" || name === "disabled")) attrs[name] = true;
|
|
53
|
+
else throw new Error(`home.introMarkdown: unsupported rendered attribute ${name}`);
|
|
54
|
+
}
|
|
55
|
+
if (node.tagName === "input") {
|
|
56
|
+
attrs.type = "checkbox";
|
|
57
|
+
attrs.disabled = true;
|
|
58
|
+
}
|
|
59
|
+
return { tag: node.tagName === "h1" ? "h2" : node.tagName, attrs, children: node.childNodes.map((child) => prepareNode(child, settings, locale)).filter((child) => child !== null) };
|
|
60
|
+
}
|
|
61
|
+
async function prepareHomeIntro(source, settings, locale) {
|
|
62
|
+
if (!source.trim()) return null;
|
|
63
|
+
const [{ parseToAst }, { renderHtml }] = await Promise.all([
|
|
64
|
+
import("@takazudo/zfb-md-wasm/parse"),
|
|
65
|
+
import("@takazudo/zfb-md-wasm/render")
|
|
66
|
+
]).catch((cause) => {
|
|
67
|
+
throw new Error("home.introMarkdown: the public @takazudo/zfb-md-wasm parse/render peer is required for nonblank introductions", { cause });
|
|
68
|
+
});
|
|
69
|
+
const parsed = await parseToAst(source, { dialect: "markdown", directives: true, frontmatter: "node" });
|
|
70
|
+
if (!parsed.ast) throw new Error(`home.introMarkdown: ${parsed.diagnostics.map((d) => d.message).join("; ")}`);
|
|
71
|
+
inspect(parsed.ast, settings, locale);
|
|
72
|
+
const mdx = await parseToAst(source, { dialect: "mdx", frontmatter: "none" });
|
|
73
|
+
if (mdx.ast) inspect(mdx.ast, settings, locale, source);
|
|
74
|
+
const features = buildMarkdownFeatures(settings, {});
|
|
75
|
+
for (const feature of ["transclude", "imageDimensions", "linkValidation", "codeTabs", "codeEnrichment", "tocExport", "readingTime", "headingMarkerToc"]) delete features[feature];
|
|
76
|
+
const rendered = await renderHtml(source, { dialect: "markdown", pipeline: { gfm: { taskListItem: true, strikethrough: true, table: true, autolinkLiteral: true }, codeHighlight: { mode: "class" }, features } });
|
|
77
|
+
if (rendered.html === null || rendered.diagnostics.length) throw new Error(`home.introMarkdown: ${rendered.diagnostics.map((d) => d.message).join("; ")}`);
|
|
78
|
+
const nodes = parseFragment(rendered.html).childNodes.map((node) => prepareNode(node, settings, locale)).filter((node) => node !== null);
|
|
79
|
+
return nodes.length ? { nodes } : null;
|
|
80
|
+
}
|
|
81
|
+
async function prepareHomeIntros(settings) {
|
|
82
|
+
const result = {};
|
|
83
|
+
if (!settings.home?.introMarkdown?.trim() && !Object.values(settings.locales ?? {}).some((locale) => locale.introMarkdown?.trim())) return result;
|
|
84
|
+
for (const locale of /* @__PURE__ */ new Set([settings.defaultLocale, ...Object.keys(settings.locales ?? {})])) {
|
|
85
|
+
result[locale] = await prepareHomeIntro(resolveHomeIntro(settings, locale, "").introMarkdown, settings, locale);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
prepareHomeIntro,
|
|
91
|
+
prepareHomeIntros,
|
|
92
|
+
resolveIntroUrl
|
|
93
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function resolveHomeIntro(settings, locale, translatedSitemapHeading) {
|
|
2
|
+
const localized = settings.locales[locale];
|
|
3
|
+
return {
|
|
4
|
+
introMarkdown: localized?.introMarkdown ?? settings.home?.introMarkdown ?? "",
|
|
5
|
+
sitemapHeading: (localized?.sitemapHeading ?? settings.home?.sitemapHeading)?.trim() || translatedSitemapHeading
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
resolveHomeIntro
|
|
10
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Safe, serializable production-renderer output. Only the preparation helper creates it. */
|
|
2
|
+
export type IntroNode = string | {
|
|
3
|
+
tag: string;
|
|
4
|
+
attrs: Record<string, string | boolean>;
|
|
5
|
+
children: IntroNode[];
|
|
6
|
+
};
|
|
7
|
+
export interface PreparedHomeIntro {
|
|
8
|
+
nodes: IntroNode[];
|
|
9
|
+
}
|
|
10
|
+
export type PreparedHomeIntros = Record<string, PreparedHomeIntro | null>;
|
|
File without changes
|
package/dist/home-page/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Fragment as Fragment2 } from "preact";
|
|
|
3
3
|
import { Island } from "@takazudo/zfb";
|
|
4
4
|
import { AutoLogo } from "../auto-logo/index.js";
|
|
5
5
|
import { DocLayoutWithDefaults } from "../doclayout/index.js";
|
|
6
|
+
import { CompactProse, resolveHomeIntro } from "../home-intro/index.js";
|
|
6
7
|
import { SiteTreeNav } from "../site-tree-nav-island/index.js";
|
|
7
8
|
import { TagNav } from "../nav-indexing/tag-nav.js";
|
|
8
9
|
import { CategoryLinkIcon } from "../tree-nav-shared/index.js";
|
|
@@ -12,6 +13,9 @@ import { deriveComposeMetaTitle, deriveBodyEndIslands, deriveDateFormats } from
|
|
|
12
13
|
import { derivePrimaryChromeSlots } from "../chrome/primary-slots.js";
|
|
13
14
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
14
15
|
import { prepareHomeData } from "./prepare-home-data.js";
|
|
16
|
+
function HomeSectionHeading({ children }) {
|
|
17
|
+
return /* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-md", children });
|
|
18
|
+
}
|
|
15
19
|
function cssUrl(url) {
|
|
16
20
|
return `url("${url.replace(/[\\"]/g, "\\$&")}")`;
|
|
17
21
|
}
|
|
@@ -28,6 +32,7 @@ function createHomePageView(ctx) {
|
|
|
28
32
|
const { Header: HeaderWithDefaults, Footer: FooterWithDefaults } = derivePrimaryChromeSlots(ctx);
|
|
29
33
|
const BodyEndIslands = deriveBodyEndIslands(ctx);
|
|
30
34
|
const homeExtras = ctx.hostBindings.homeExtras;
|
|
35
|
+
const homeIntros = ctx.homeIntros;
|
|
31
36
|
const dataThemePack = resolveThemePackSsrSlug(
|
|
32
37
|
ctx.themePackRegistry,
|
|
33
38
|
ctx.settings
|
|
@@ -49,11 +54,18 @@ function createHomePageView(ctx) {
|
|
|
49
54
|
const primary = heroLink ? { href: withBase(`${prefix}${heroLink.path}`), label: t(heroLink.labelKey, locale) } : ctaNav ? { href: withBase(`${prefix}${ctaNav.path}`), label: t("nav.overview", locale) } : null;
|
|
50
55
|
const logoSetting = settings.logo ?? "auto";
|
|
51
56
|
const resolvedExtras = extras ?? homeExtras?.({ locale });
|
|
57
|
+
const intro = homeIntros?.[locale] ?? null;
|
|
58
|
+
const hasIntro = Boolean(intro?.nodes.length);
|
|
59
|
+
const sitemapHeading = resolveHomeIntro(
|
|
60
|
+
settings,
|
|
61
|
+
locale,
|
|
62
|
+
t("home.sitemapHeading", locale)
|
|
63
|
+
).sitemapHeading;
|
|
52
64
|
const hasExtras = resolvedExtras != null && typeof resolvedExtras !== "boolean";
|
|
53
65
|
const rowItems = [];
|
|
54
66
|
if (primary) {
|
|
55
67
|
rowItems.push(
|
|
56
|
-
/* @__PURE__ */ jsx("a", { href: primary.href, class: "
|
|
68
|
+
/* @__PURE__ */ jsx("a", { href: primary.href, class: "text-fg underline hover:text-accent", children: primary.label })
|
|
57
69
|
);
|
|
58
70
|
}
|
|
59
71
|
if (settings.githubUrl) {
|
|
@@ -62,7 +74,7 @@ function createHomePageView(ctx) {
|
|
|
62
74
|
"a",
|
|
63
75
|
{
|
|
64
76
|
href: settings.githubUrl,
|
|
65
|
-
class: "inline-flex items-center gap-[0.3em]
|
|
77
|
+
class: "inline-flex items-center gap-[0.3em] text-fg underline hover:text-accent",
|
|
66
78
|
target: "_blank",
|
|
67
79
|
rel: "noopener noreferrer",
|
|
68
80
|
children: [
|
|
@@ -99,7 +111,7 @@ function createHomePageView(ctx) {
|
|
|
99
111
|
bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }),
|
|
100
112
|
enableClientRouter: settings.dynamicPageTransition,
|
|
101
113
|
children: [
|
|
102
|
-
/* @__PURE__ */ jsx("div", { class: "
|
|
114
|
+
/* @__PURE__ */ jsx("div", { class: "zd-home-hero mb-vsp-xl", children: /* @__PURE__ */ jsxs("div", { class: "zd-home-inner flex flex-col items-center justify-center text-center gap-hsp-md lg:flex-row lg:text-left lg:gap-hsp-xl", children: [
|
|
103
115
|
logoSetting === "auto" ? /* @__PURE__ */ jsx(
|
|
104
116
|
AutoLogo,
|
|
105
117
|
{
|
|
@@ -117,32 +129,40 @@ function createHomePageView(ctx) {
|
|
|
117
129
|
"aria-hidden": "true"
|
|
118
130
|
}
|
|
119
131
|
) : null,
|
|
120
|
-
/* @__PURE__ */ jsxs("div", { class: "min-w-0", children: [
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { class: "zd-home-copy min-w-0 lg:flex-1", children: [
|
|
121
133
|
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-2xs break-words", children: settings.siteName }),
|
|
122
134
|
/* @__PURE__ */ jsx("p", { class: "text-muted text-small mb-vsp-sm", children: settings.locales[locale]?.description ?? settings.siteDescription }),
|
|
123
|
-
/* @__PURE__ */ jsx("div", { class: "flex flex-wrap items-center justify-center lg:justify-start gap-hsp-md text-small", children: rowItems.map((item, index) => /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
135
|
+
/* @__PURE__ */ jsx("div", { class: "zd-home-links flex flex-wrap items-center justify-center lg:justify-start gap-hsp-md text-small", children: rowItems.map((item, index) => /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
124
136
|
index > 0 && /* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" }),
|
|
125
137
|
item
|
|
126
138
|
] }, index)) })
|
|
127
139
|
] })
|
|
128
140
|
] }) }),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
children: /* @__PURE__ */ jsx(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
hasIntro && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
142
|
+
/* @__PURE__ */ jsx("hr", { class: "zd-home-rule", "data-home-rule": "upper" }),
|
|
143
|
+
/* @__PURE__ */ jsx("div", { class: "zd-home-intro", children: /* @__PURE__ */ jsx("div", { class: "zd-home-inner", children: /* @__PURE__ */ jsx(CompactProse, { intro }) }) })
|
|
144
|
+
] }),
|
|
145
|
+
/* @__PURE__ */ jsx("hr", { class: "zd-home-rule", "data-home-rule": "lower" }),
|
|
146
|
+
/* @__PURE__ */ jsxs("section", { class: "zd-home-sitemap", children: [
|
|
147
|
+
/* @__PURE__ */ jsx(HomeSectionHeading, { children: sitemapHeading }),
|
|
148
|
+
Island({
|
|
149
|
+
when: "idle",
|
|
150
|
+
children: /* @__PURE__ */ jsx(
|
|
151
|
+
SiteTreeNav,
|
|
152
|
+
{
|
|
153
|
+
tree,
|
|
154
|
+
categoryOrder,
|
|
155
|
+
categoryIgnore,
|
|
156
|
+
initiallyCollapsedCategorySlugs,
|
|
157
|
+
locale,
|
|
158
|
+
updatedLabel: t("doc.updated", locale),
|
|
159
|
+
dateFormats: dateFormatsFor(locale)
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
] }),
|
|
144
164
|
settings.docTags && tagCount > 0 && /* @__PURE__ */ jsx("section", { class: "mt-vsp-xl", children: tags && tags.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
145
|
-
/* @__PURE__ */ jsx(
|
|
165
|
+
/* @__PURE__ */ jsx(HomeSectionHeading, { children: t("doc.tags", locale) }),
|
|
146
166
|
/* @__PURE__ */ jsx(
|
|
147
167
|
TagNav,
|
|
148
168
|
{
|
|
@@ -171,7 +191,7 @@ function createHomePageView(ctx) {
|
|
|
171
191
|
// reproduce the pre-#3027 single "All Tags" link so existing
|
|
172
192
|
// `@takazudo/zudo-doc/home-page` consumers are unaffected.
|
|
173
193
|
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
174
|
-
/* @__PURE__ */ jsx(
|
|
194
|
+
/* @__PURE__ */ jsx(HomeSectionHeading, { children: t("doc.allTags", locale) }),
|
|
175
195
|
/* @__PURE__ */ jsx(
|
|
176
196
|
"a",
|
|
177
197
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const defaultTranslations = {
|
|
2
2
|
en: {
|
|
3
|
+
"home.sitemapHeading": "Explore the documentation",
|
|
3
4
|
"nav.gettingStarted": "Getting Started",
|
|
4
5
|
"nav.learn": "Learn",
|
|
5
6
|
"nav.guides": "Guides",
|
|
@@ -128,6 +129,7 @@ const defaultTranslations = {
|
|
|
128
129
|
"version.page.docs": "Docs"
|
|
129
130
|
},
|
|
130
131
|
ja: {
|
|
132
|
+
"home.sitemapHeading": "\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3092\u63A2\u3059",
|
|
131
133
|
"nav.gettingStarted": "\u306F\u3058\u3081\u306B",
|
|
132
134
|
"nav.learn": "\u5B66\u3076",
|
|
133
135
|
"nav.guides": "\u30AC\u30A4\u30C9",
|