@takazudo/zudo-doc 4.3.0 → 4.4.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/bin/zudo-doc.mjs +54 -13
- package/dist/auto-logo/index.d.ts +18 -0
- package/dist/auto-logo/index.js +33 -0
- package/dist/auto-logo/shapes.d.ts +32 -0
- package/dist/auto-logo/shapes.js +183 -0
- package/dist/auto-logo/standalone.d.ts +7 -0
- package/dist/auto-logo/standalone.js +35 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +1 -0
- package/dist/content.css +4 -3
- package/dist/doc-content-header/index.js +1 -1
- package/dist/doc-history/index.js +1 -1
- package/dist/doc-page-shell/index.js +1 -1
- package/dist/doc-pager/index.js +2 -2
- package/dist/eject-logo/config-parse.d.ts +25 -0
- package/dist/eject-logo/config-parse.js +68 -0
- package/dist/eject-logo/config-rewriter.d.ts +22 -0
- package/dist/eject-logo/config-rewriter.js +68 -0
- package/dist/eject-logo/eject.d.ts +18 -0
- package/dist/eject-logo/eject.js +66 -0
- package/dist/eject-logo/index.d.ts +4 -0
- package/dist/eject-logo/index.js +16 -0
- package/dist/eject-logo/site-name.d.ts +21 -0
- package/dist/eject-logo/site-name.js +31 -0
- package/dist/header/header.js +14 -6
- package/dist/header/nav-class-tokens.d.ts +12 -0
- package/dist/header/nav-class-tokens.js +80 -0
- package/dist/header/nav-overflow-script.js +33 -16
- package/dist/home-page/index.d.ts +34 -5
- package/dist/home-page/index.js +95 -38
- package/dist/home-page/prepare-home-data.d.ts +9 -0
- package/dist/home-page/prepare-home-data.js +10 -3
- package/dist/i18n-defaults/index.js +3 -0
- package/dist/metainfo/doc-metainfo.js +35 -22
- package/dist/routes/index.js +2 -1
- package/dist/routes/locale-index.js +2 -1
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +7 -0
- package/dist/sidebar-tree-island/index.js +3 -3
- package/dist/theme-cli/config-scanner.js +47 -0
- package/dist/theme-packs/academia/fonts/CourierPrime-400-latin.woff2 +0 -0
- package/dist/theme-packs/academia/fonts/CourierPrime-700-latin.woff2 +0 -0
- package/dist/theme-packs/academia/fonts/OFL.txt +94 -0
- package/dist/theme-packs/academia/fonts/SourceSerif4-italic-latin.woff2 +0 -0
- package/dist/theme-packs/academia/fonts/SourceSerif4-latin.woff2 +0 -0
- package/dist/theme-packs/academia/meta.json +37 -0
- package/dist/theme-packs/academia/pack.css +505 -0
- package/dist/theme-packs/bauhaus/fonts/ArchivoBlack-latin.woff2 +0 -0
- package/dist/theme-packs/bauhaus/fonts/Jost-latin.woff2 +0 -0
- package/dist/theme-packs/bauhaus/fonts/OFL.txt +95 -0
- package/dist/theme-packs/bauhaus/fonts/SpaceMono-400-latin.woff2 +0 -0
- package/dist/theme-packs/bauhaus/fonts/SpaceMono-700-latin.woff2 +0 -0
- package/dist/theme-packs/bauhaus/meta.json +38 -0
- package/dist/theme-packs/bauhaus/pack.css +614 -0
- package/dist/theme-packs/blueprint/fonts/IBMPlexMono-400-latin-ext.woff2 +0 -0
- package/dist/theme-packs/blueprint/fonts/IBMPlexMono-400-latin.woff2 +0 -0
- package/dist/theme-packs/blueprint/fonts/IBMPlexMono-500-latin-ext.woff2 +0 -0
- package/dist/theme-packs/blueprint/fonts/IBMPlexMono-500-latin.woff2 +0 -0
- package/dist/theme-packs/blueprint/fonts/Inter-latin-ext.woff2 +0 -0
- package/dist/theme-packs/blueprint/fonts/Inter-latin.woff2 +0 -0
- package/dist/theme-packs/blueprint/fonts/JosefinSans-latin.woff2 +0 -0
- package/dist/theme-packs/blueprint/fonts/OFL.txt +95 -0
- package/dist/theme-packs/blueprint/meta.json +38 -0
- package/dist/theme-packs/blueprint/pack.css +577 -0
- package/dist/theme-packs/botanica/fonts/CormorantGaramond-latin.woff2 +0 -0
- package/dist/theme-packs/botanica/fonts/CourierPrime-400-latin-ext.woff2 +0 -0
- package/dist/theme-packs/botanica/fonts/CourierPrime-400-latin.woff2 +0 -0
- package/dist/theme-packs/botanica/fonts/CourierPrime-700-latin-ext.woff2 +0 -0
- package/dist/theme-packs/botanica/fonts/CourierPrime-700-latin.woff2 +0 -0
- package/dist/theme-packs/botanica/fonts/CrimsonPro-latin-italic.woff2 +0 -0
- package/dist/theme-packs/botanica/fonts/CrimsonPro-latin.woff2 +0 -0
- package/dist/theme-packs/botanica/fonts/OFL.txt +95 -0
- package/dist/theme-packs/botanica/meta.json +38 -0
- package/dist/theme-packs/botanica/pack.css +546 -0
- package/dist/theme-packs/brutalist/meta.json +1 -1
- package/dist/theme-packs/brutalist/pack.css +10 -9
- package/dist/theme-packs/eink/fonts/CourierPrime-400-latin-ext.woff2 +0 -0
- package/dist/theme-packs/eink/fonts/CourierPrime-400-latin.woff2 +0 -0
- package/dist/theme-packs/eink/fonts/CourierPrime-700-latin-ext.woff2 +0 -0
- package/dist/theme-packs/eink/fonts/CourierPrime-700-latin.woff2 +0 -0
- package/dist/theme-packs/eink/fonts/EBGaramond-latin-italic.woff2 +0 -0
- package/dist/theme-packs/eink/fonts/EBGaramond-latin.woff2 +0 -0
- package/dist/theme-packs/eink/fonts/OFL.txt +94 -0
- package/dist/theme-packs/eink/meta.json +37 -0
- package/dist/theme-packs/eink/pack.css +490 -0
- package/dist/theme-packs/foundry/meta.json +1 -1
- package/dist/theme-packs/foundry/pack.css +2 -2
- package/dist/theme-packs/hearth/meta.json +1 -1
- package/dist/theme-packs/hearth/pack.css +1 -1
- package/dist/theme-packs/nocturne/pack.css +4 -0
- package/dist/theme-packs/observatory/meta.json +1 -1
- package/dist/theme-packs/observatory/pack.css +4 -1
- package/dist/theme-packs/onyx/pack.css +6 -0
- package/dist/theme-packs/phosphor/pack.css +4 -0
- package/dist/theme-packs/riso/fonts/ArchivoBlack-latin.woff2 +0 -0
- package/dist/theme-packs/riso/fonts/OFL.txt +95 -0
- package/dist/theme-packs/riso/fonts/SpaceGrotesk-latin.woff2 +0 -0
- package/dist/theme-packs/riso/fonts/SpaceMono-400-italic-latin.woff2 +0 -0
- package/dist/theme-packs/riso/fonts/SpaceMono-400-latin.woff2 +0 -0
- package/dist/theme-packs/riso/fonts/SpaceMono-700-latin.woff2 +0 -0
- package/dist/theme-packs/riso/meta.json +38 -0
- package/dist/theme-packs/riso/pack.css +602 -0
- package/dist/theme-packs/sakura/fonts/MPlus1Code-400-latin.woff2 +0 -0
- package/dist/theme-packs/sakura/fonts/MPlus1Code-500-latin.woff2 +0 -0
- package/dist/theme-packs/sakura/fonts/Nunito-latin.woff2 +0 -0
- package/dist/theme-packs/sakura/fonts/OFL.txt +95 -0
- package/dist/theme-packs/sakura/fonts/ShipporiMincho-600-latin.woff2 +0 -0
- package/dist/theme-packs/sakura/fonts/ShipporiMincho-700-latin.woff2 +0 -0
- package/dist/theme-packs/sakura/meta.json +38 -0
- package/dist/theme-packs/sakura/pack.css +473 -0
- package/dist/theme-packs/scandi/fonts/Manrope-latin.woff2 +0 -0
- package/dist/theme-packs/scandi/fonts/OFL.txt +94 -0
- package/dist/theme-packs/scandi/fonts/SplineSansMono-latin.woff2 +0 -0
- package/dist/theme-packs/scandi/meta.json +37 -0
- package/dist/theme-packs/scandi/pack.css +370 -0
- package/dist/theme-packs/sumi/meta.json +1 -1
- package/dist/theme-packs/sumi/pack.css +4 -1
- package/dist/theme-packs/swissgrid/pack.css +4 -0
- package/dist/theme-packs/tidepool/fonts/JetBrainsMono-latin-ext.woff2 +0 -0
- package/dist/theme-packs/tidepool/fonts/JetBrainsMono-latin.woff2 +0 -0
- package/dist/theme-packs/tidepool/fonts/OFL.txt +94 -0
- package/dist/theme-packs/tidepool/fonts/SpaceGrotesk-latin.woff2 +0 -0
- package/dist/theme-packs/tidepool/meta.json +37 -0
- package/dist/theme-packs/tidepool/pack.css +425 -0
- package/dist/theme-packs/timberline/fonts/Fraunces-italic-latin.woff2 +0 -0
- package/dist/theme-packs/timberline/fonts/Fraunces-latin.woff2 +0 -0
- package/dist/theme-packs/timberline/fonts/JetBrainsMono-latin.woff2 +0 -0
- package/dist/theme-packs/timberline/fonts/OFL.txt +95 -0
- package/dist/theme-packs/timberline/fonts/SourceSans3-italic-latin.woff2 +0 -0
- package/dist/theme-packs/timberline/fonts/SourceSans3-latin.woff2 +0 -0
- package/dist/theme-packs/timberline/meta.json +38 -0
- package/dist/theme-packs/timberline/pack.css +509 -0
- package/dist/theme-packs/washi/meta.json +1 -1
- package/dist/theme-packs/washi/pack.css +6 -26
- package/eject/doc-history/index.tsx +1 -1
- package/eject/doc-pager/index.tsx +2 -2
- package/eject/header/header.tsx +16 -8
- package/eject/header/nav-class-tokens.ts +115 -0
- package/eject/header/nav-overflow-script.ts +49 -16
- package/eject/sidebar-tree-island/index.tsx +4 -4
- package/package.json +16 -8
- package/routes-src/index.tsx +2 -1
- package/routes-src/locale-index.tsx +2 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const EJECTED_LOGO_VALUE = "/img/logo.svg";
|
|
2
|
+
export interface ApplyLogoFieldToSourceOk {
|
|
3
|
+
ok: true;
|
|
4
|
+
/** The full rewritten file contents. Identical to the input when
|
|
5
|
+
* `changed` is false (idempotent re-run of an already-ejected logo). */
|
|
6
|
+
source: string;
|
|
7
|
+
changed: boolean;
|
|
8
|
+
mode: "replaced" | "inserted-after-site-name" | "inserted-first";
|
|
9
|
+
}
|
|
10
|
+
export interface ApplyLogoFieldToSourceRefusal {
|
|
11
|
+
ok: false;
|
|
12
|
+
/** Human-readable WHY, ending with the manual one-liner to add by hand.
|
|
13
|
+
* The input source is never touched when this is returned. */
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
export type ApplyLogoFieldToSourceResult = ApplyLogoFieldToSourceOk | ApplyLogoFieldToSourceRefusal;
|
|
17
|
+
/**
|
|
18
|
+
* Apply `logo: "/img/logo.svg"` to a `zfb.config.ts` source string. Pure and
|
|
19
|
+
* filesystem-free — callers own reading/writing the actual file (see
|
|
20
|
+
* `eject.ts`).
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyLogoFieldToConfigSource(source: string): ApplyLogoFieldToSourceResult;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isPlainStringLiteralSpan } from "../theme-cli/config-scanner.js";
|
|
2
|
+
import { parseZudoDocConfigMembers } from "./config-parse.js";
|
|
3
|
+
const EJECTED_LOGO_VALUE = "/img/logo.svg";
|
|
4
|
+
function manualHint() {
|
|
5
|
+
return `Add the field manually inside your zudoDoc({ ... }) call: logo: ${JSON.stringify(EJECTED_LOGO_VALUE)},`;
|
|
6
|
+
}
|
|
7
|
+
function refuse(reason) {
|
|
8
|
+
return { ok: false, reason: `${reason} ${manualHint()}` };
|
|
9
|
+
}
|
|
10
|
+
function isPlainFalseLiteralSpan(source, start, end) {
|
|
11
|
+
return source.slice(start, end).trim() === "false";
|
|
12
|
+
}
|
|
13
|
+
function detectIndent(source, members) {
|
|
14
|
+
const ref = members[0];
|
|
15
|
+
const refPos = ref ? ref.keyStart ?? ref.memberStart : null;
|
|
16
|
+
if (refPos !== null && refPos !== void 0) {
|
|
17
|
+
const newlineIdx = source.lastIndexOf("\n", refPos);
|
|
18
|
+
const lineStart = newlineIdx === -1 ? 0 : newlineIdx + 1;
|
|
19
|
+
const line = source.slice(lineStart, refPos);
|
|
20
|
+
if (/^[ \t]*$/.test(line) && line.length > 0) return line;
|
|
21
|
+
}
|
|
22
|
+
return " ";
|
|
23
|
+
}
|
|
24
|
+
function applyLogoFieldToConfigSource(source) {
|
|
25
|
+
const parsed = parseZudoDocConfigMembers(source);
|
|
26
|
+
if (!parsed.ok) return refuse(parsed.reason);
|
|
27
|
+
const { members, braceOpenIdx } = parsed;
|
|
28
|
+
const newValueLiteral = JSON.stringify(EJECTED_LOGO_VALUE);
|
|
29
|
+
const logoMembers = members.filter((m) => m.key === "logo");
|
|
30
|
+
if (logoMembers.length > 1) {
|
|
31
|
+
return refuse(
|
|
32
|
+
'zfb.config.ts declares "logo" more than once inside zudoDoc({ ... }). This CLI only rewrites a single canonical field; remove the duplicate and re-run.'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const logoMember = logoMembers[0];
|
|
36
|
+
if (logoMember) {
|
|
37
|
+
if (logoMember.valueStart === null || logoMember.valueEnd === null) {
|
|
38
|
+
return refuse('the existing "logo" field in zfb.config.ts is not a simple `key: value` property this CLI understands.');
|
|
39
|
+
}
|
|
40
|
+
const isReplaceableLiteral = isPlainStringLiteralSpan(source, logoMember.valueStart, logoMember.valueEnd) || isPlainFalseLiteralSpan(source, logoMember.valueStart, logoMember.valueEnd);
|
|
41
|
+
if (!isReplaceableLiteral) {
|
|
42
|
+
return refuse(
|
|
43
|
+
'the existing "logo" field\'s value is not a plain string literal or `false` \u2014 cannot safely rewrite it without risking corruption.'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const newSource2 = source.slice(0, logoMember.valueStart) + newValueLiteral + source.slice(logoMember.valueEnd);
|
|
47
|
+
return { ok: true, source: newSource2, changed: newSource2 !== source, mode: "replaced" };
|
|
48
|
+
}
|
|
49
|
+
const siteNameMember = members.find((m) => m.key === "siteName");
|
|
50
|
+
const indent = detectIndent(source, members);
|
|
51
|
+
if (siteNameMember) {
|
|
52
|
+
const insertAt2 = siteNameMember.commaIndex !== null ? siteNameMember.commaIndex + 1 : siteNameMember.memberEnd;
|
|
53
|
+
const prefix = siteNameMember.commaIndex !== null ? "" : ",";
|
|
54
|
+
const insertText2 = `${prefix}
|
|
55
|
+
${indent}logo: ${newValueLiteral},`;
|
|
56
|
+
const newSource2 = source.slice(0, insertAt2) + insertText2 + source.slice(insertAt2);
|
|
57
|
+
return { ok: true, source: newSource2, changed: true, mode: "inserted-after-site-name" };
|
|
58
|
+
}
|
|
59
|
+
const insertAt = braceOpenIdx + 1;
|
|
60
|
+
const insertText = `
|
|
61
|
+
${indent}logo: ${newValueLiteral},`;
|
|
62
|
+
const newSource = source.slice(0, insertAt) + insertText + source.slice(insertAt);
|
|
63
|
+
return { ok: true, source: newSource, changed: true, mode: "inserted-first" };
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
EJECTED_LOGO_VALUE,
|
|
67
|
+
applyLogoFieldToConfigSource
|
|
68
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface EjectLogoOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
/** Explicit seed override (already validated non-empty by the CLI arg
|
|
4
|
+
* parser in bin/zudo-doc.mjs). When omitted, the seed is resolved from
|
|
5
|
+
* zfb.config.ts's siteName field. */
|
|
6
|
+
seed?: string;
|
|
7
|
+
force?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface EjectLogoResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
/** Human-readable outcome message (success confirmation, or a refusal
|
|
12
|
+
* explaining WHY). */
|
|
13
|
+
message: string;
|
|
14
|
+
/** True when the SVG was written but the config rewrite was refused — a
|
|
15
|
+
* nonzero-exit partial success, not a total failure. */
|
|
16
|
+
partial?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function ejectLogo(options?: EjectLogoOptions): Promise<EjectLogoResult>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { renderAutoLogoStandaloneSvg } from "../auto-logo/standalone.js";
|
|
4
|
+
import { applyLogoFieldToConfigSource } from "./config-rewriter.js";
|
|
5
|
+
import { resolveSiteNameFromConfigSource } from "./site-name.js";
|
|
6
|
+
const ZFB_CONFIG_FILENAME = "zfb.config.ts";
|
|
7
|
+
const LOGO_RELATIVE_PATH = path.join("public", "img", "logo.svg");
|
|
8
|
+
const DEFAULT_SEED = "Docs";
|
|
9
|
+
async function ejectLogo(options = {}) {
|
|
10
|
+
const cwd = options.cwd ?? process.cwd();
|
|
11
|
+
const force = options.force ?? false;
|
|
12
|
+
const configPath = path.join(cwd, ZFB_CONFIG_FILENAME);
|
|
13
|
+
if (!await fs.pathExists(configPath)) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
message: `zfb.config.ts not found at ${configPath}.
|
|
17
|
+
Run this command from your project root.`
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const source = await fs.readFile(configPath, "utf8");
|
|
21
|
+
let seed;
|
|
22
|
+
if (options.seed) {
|
|
23
|
+
seed = options.seed;
|
|
24
|
+
} else {
|
|
25
|
+
const resolution = resolveSiteNameFromConfigSource(source);
|
|
26
|
+
if (resolution.kind === "unresolvable") {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
message: `Could not determine a logo seed from zfb.config.ts: ${resolution.reason}
|
|
30
|
+
Pass --seed <name> to specify the logo seed explicitly.`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
seed = resolution.kind === "literal" ? resolution.value : DEFAULT_SEED;
|
|
34
|
+
}
|
|
35
|
+
const rewriteResult = applyLogoFieldToConfigSource(source);
|
|
36
|
+
const svgPath = path.join(cwd, LOGO_RELATIVE_PATH);
|
|
37
|
+
if (!force && await fs.pathExists(svgPath)) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
message: `${LOGO_RELATIVE_PATH} already exists.
|
|
41
|
+
Pass --force to overwrite it.`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const svg = renderAutoLogoStandaloneSvg(seed);
|
|
45
|
+
await fs.ensureDir(path.dirname(svgPath));
|
|
46
|
+
await fs.writeFile(svgPath, svg, "utf8");
|
|
47
|
+
if (!rewriteResult.ok) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
partial: true,
|
|
51
|
+
message: `Wrote ${LOGO_RELATIVE_PATH}, but could not update zfb.config.ts: ${rewriteResult.reason}`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (rewriteResult.changed) {
|
|
55
|
+
await fs.writeFile(configPath, rewriteResult.source, "utf8");
|
|
56
|
+
}
|
|
57
|
+
const verb = rewriteResult.changed ? "Ejected logo" : "Ejected logo (field already set)";
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
message: `${verb} \u2192 ${LOGO_RELATIVE_PATH}
|
|
61
|
+
Updated zfb.config.ts's logo field.`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
ejectLogo
|
|
66
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ejectLogo, type EjectLogoOptions, type EjectLogoResult } from "./eject.js";
|
|
2
|
+
export { applyLogoFieldToConfigSource, EJECTED_LOGO_VALUE, type ApplyLogoFieldToSourceOk, type ApplyLogoFieldToSourceRefusal, type ApplyLogoFieldToSourceResult, } from "./config-rewriter.js";
|
|
3
|
+
export { resolveSiteNameFromConfigSource, type SiteNameResolution } from "./site-name.js";
|
|
4
|
+
export { parseZudoDocConfigMembers, type ParsedZudoDocConfig, type ParsedZudoDocConfigRefusal, type ParseZudoDocConfigResult, } from "./config-parse.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ejectLogo } from "./eject.js";
|
|
2
|
+
import {
|
|
3
|
+
applyLogoFieldToConfigSource,
|
|
4
|
+
EJECTED_LOGO_VALUE
|
|
5
|
+
} from "./config-rewriter.js";
|
|
6
|
+
import { resolveSiteNameFromConfigSource } from "./site-name.js";
|
|
7
|
+
import {
|
|
8
|
+
parseZudoDocConfigMembers
|
|
9
|
+
} from "./config-parse.js";
|
|
10
|
+
export {
|
|
11
|
+
EJECTED_LOGO_VALUE,
|
|
12
|
+
applyLogoFieldToConfigSource,
|
|
13
|
+
ejectLogo,
|
|
14
|
+
parseZudoDocConfigMembers,
|
|
15
|
+
resolveSiteNameFromConfigSource
|
|
16
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type SiteNameResolution = {
|
|
2
|
+
kind: "literal";
|
|
3
|
+
value: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: "absent";
|
|
6
|
+
} | {
|
|
7
|
+
kind: "unresolvable";
|
|
8
|
+
reason: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Resolve `siteName` from a `zfb.config.ts` source string.
|
|
12
|
+
*
|
|
13
|
+
* - Literal `siteName: "..."` → `{ kind: "literal", value }`.
|
|
14
|
+
* - Canonical literal config with no `siteName` field → `{ kind: "absent" }`
|
|
15
|
+
* (the runtime default `"Docs"` genuinely applies — this IS a confirmed
|
|
16
|
+
* reading, the caller decides the default value).
|
|
17
|
+
* - Anything else (no/duplicate zudoDoc call, spread argument, computed or
|
|
18
|
+
* duplicate `siteName`, non-string value) → `{ kind: "unresolvable",
|
|
19
|
+
* reason }`; the caller must require `--seed`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveSiteNameFromConfigSource(source: string): SiteNameResolution;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isPlainStringLiteralSpan, unquoteStringLiteralSpan } from "../theme-cli/config-scanner.js";
|
|
2
|
+
import { parseZudoDocConfigMembers } from "./config-parse.js";
|
|
3
|
+
function resolveSiteNameFromConfigSource(source) {
|
|
4
|
+
const parsed = parseZudoDocConfigMembers(source);
|
|
5
|
+
if (!parsed.ok) return { kind: "unresolvable", reason: parsed.reason };
|
|
6
|
+
const siteNameMembers = parsed.members.filter((m) => m.key === "siteName");
|
|
7
|
+
if (siteNameMembers.length > 1) {
|
|
8
|
+
return {
|
|
9
|
+
kind: "unresolvable",
|
|
10
|
+
reason: 'zfb.config.ts declares "siteName" more than once inside zudoDoc({ ... }) \u2014 cannot safely determine which one the runtime reads.'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const member = siteNameMembers[0];
|
|
14
|
+
if (!member) return { kind: "absent" };
|
|
15
|
+
if (member.valueStart === null || member.valueEnd === null) {
|
|
16
|
+
return {
|
|
17
|
+
kind: "unresolvable",
|
|
18
|
+
reason: 'the "siteName" field in zfb.config.ts is not a simple `key: value` property this CLI understands.'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (!isPlainStringLiteralSpan(source, member.valueStart, member.valueEnd)) {
|
|
22
|
+
return {
|
|
23
|
+
kind: "unresolvable",
|
|
24
|
+
reason: `the "siteName" field's value in zfb.config.ts is not a plain string literal.`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { kind: "literal", value: unquoteStringLiteralSpan(source, member.valueStart, member.valueEnd) };
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
resolveSiteNameFromConfigSource
|
|
31
|
+
};
|
package/dist/header/header.js
CHANGED
|
@@ -6,6 +6,14 @@ import {
|
|
|
6
6
|
pathForMatch
|
|
7
7
|
} from "./nav-active.js";
|
|
8
8
|
import { NAV_OVERFLOW_SCRIPT } from "./nav-overflow-script.js";
|
|
9
|
+
import {
|
|
10
|
+
NAV_CHEVRON_ACTIVE,
|
|
11
|
+
NAV_CHEVRON_INACTIVE,
|
|
12
|
+
NAV_CHILD_ACTIVE,
|
|
13
|
+
NAV_CHILD_INACTIVE,
|
|
14
|
+
NAV_TOP_ACTIVE,
|
|
15
|
+
NAV_TOP_INACTIVE
|
|
16
|
+
} from "./nav-class-tokens.js";
|
|
9
17
|
import { LANGUAGE_SWITCHER_INIT_SCRIPT } from "../i18n-version/language-switcher.js";
|
|
10
18
|
import { VERSION_SWITCHER_REWIRE_SCRIPT } from "../i18n-version/version-switcher.js";
|
|
11
19
|
import { GitHub as GitHubIcon } from "../icons/index.js";
|
|
@@ -81,7 +89,7 @@ function Header(props) {
|
|
|
81
89
|
"button",
|
|
82
90
|
{
|
|
83
91
|
type: "button",
|
|
84
|
-
class: "px-hsp-md py-vsp-2xs text-small font-medium text-muted hover:underline cursor-pointer",
|
|
92
|
+
class: "px-hsp-md py-vsp-2xs text-small font-medium text-muted hover:text-accent hover:underline focus-visible:text-accent focus-visible:underline cursor-pointer",
|
|
85
93
|
"data-nav-more-toggle": true,
|
|
86
94
|
"aria-expanded": "false",
|
|
87
95
|
children: "\xB7\xB7\xB7"
|
|
@@ -177,7 +185,7 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
|
|
|
177
185
|
"aria-expanded": "false",
|
|
178
186
|
class: [
|
|
179
187
|
"flex items-center gap-x-hsp-xs px-hsp-md py-vsp-2xs text-small font-medium transition-colors",
|
|
180
|
-
isActive ? "
|
|
188
|
+
isActive ? NAV_TOP_ACTIVE.join(" ") : NAV_TOP_INACTIVE.join(" ")
|
|
181
189
|
].join(" "),
|
|
182
190
|
children: [
|
|
183
191
|
label,
|
|
@@ -186,7 +194,7 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
|
|
|
186
194
|
{
|
|
187
195
|
class: [
|
|
188
196
|
"h-[0.5rem] w-[0.5rem] shrink-0",
|
|
189
|
-
isActive ? "
|
|
197
|
+
isActive ? NAV_CHEVRON_ACTIVE.join(" ") : NAV_CHEVRON_INACTIVE.join(" ")
|
|
190
198
|
].join(" "),
|
|
191
199
|
fill: "none",
|
|
192
200
|
viewBox: "0 0 24 24",
|
|
@@ -216,8 +224,8 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
|
|
|
216
224
|
href: childHref,
|
|
217
225
|
"data-active": childActive ? "" : void 0,
|
|
218
226
|
class: [
|
|
219
|
-
"block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline",
|
|
220
|
-
childActive ? "
|
|
227
|
+
"block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline focus-visible:underline",
|
|
228
|
+
childActive ? NAV_CHILD_ACTIVE.join(" ") : NAV_CHILD_INACTIVE.join(" ")
|
|
221
229
|
].join(" "),
|
|
222
230
|
children: childLabel
|
|
223
231
|
}
|
|
@@ -235,7 +243,7 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
|
|
|
235
243
|
"data-nav-item": true,
|
|
236
244
|
class: [
|
|
237
245
|
"px-hsp-md py-vsp-2xs text-small font-medium transition-colors shrink-0",
|
|
238
|
-
isActive ? "
|
|
246
|
+
isActive ? NAV_TOP_ACTIVE.join(" ") : NAV_TOP_INACTIVE.join(" ")
|
|
239
247
|
].join(" "),
|
|
240
248
|
children: label
|
|
241
249
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const NAV_TOP_ACTIVE: readonly string[];
|
|
2
|
+
export declare const NAV_TOP_INACTIVE: readonly string[];
|
|
3
|
+
export declare const NAV_CHEVRON_ACTIVE: readonly string[];
|
|
4
|
+
export declare const NAV_CHEVRON_INACTIVE: readonly string[];
|
|
5
|
+
export declare const NAV_CHILD_ACTIVE: readonly string[];
|
|
6
|
+
export declare const NAV_CHILD_INACTIVE: readonly string[];
|
|
7
|
+
export declare const NAV_MENU_PARENT: readonly string[];
|
|
8
|
+
export declare const NAV_MENU_PARENT_ACTIVE_SUFFIX: readonly string[];
|
|
9
|
+
export declare const NAV_MENU_PLAIN: readonly string[];
|
|
10
|
+
export declare const NAV_MENU_PLAIN_ACTIVE_SUFFIX: readonly string[];
|
|
11
|
+
export declare const NAV_MENU_CHILD_ACTIVE: readonly string[];
|
|
12
|
+
export declare const NAV_MENU_CHILD_INACTIVE: readonly string[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const NAV_TOP_ACTIVE = ["bg-fg", "text-bg"];
|
|
2
|
+
const NAV_TOP_INACTIVE = [
|
|
3
|
+
"text-muted",
|
|
4
|
+
"hover:text-accent",
|
|
5
|
+
"hover:underline",
|
|
6
|
+
"focus:underline",
|
|
7
|
+
"focus:text-accent"
|
|
8
|
+
];
|
|
9
|
+
const NAV_CHEVRON_ACTIVE = ["text-bg"];
|
|
10
|
+
const NAV_CHEVRON_INACTIVE = ["text-muted"];
|
|
11
|
+
const NAV_CHILD_ACTIVE = ["font-bold", "text-accent"];
|
|
12
|
+
const NAV_CHILD_INACTIVE = [
|
|
13
|
+
"text-fg",
|
|
14
|
+
"hover:text-accent",
|
|
15
|
+
"focus-visible:text-accent"
|
|
16
|
+
];
|
|
17
|
+
const MENU_ROW_BASE = [
|
|
18
|
+
"block",
|
|
19
|
+
"px-hsp-md",
|
|
20
|
+
"py-vsp-2xs",
|
|
21
|
+
"text-small"
|
|
22
|
+
];
|
|
23
|
+
const MENU_CHILD_BASE = [
|
|
24
|
+
"block",
|
|
25
|
+
"pl-hsp-xl",
|
|
26
|
+
"pr-hsp-md",
|
|
27
|
+
"py-vsp-2xs",
|
|
28
|
+
"text-small"
|
|
29
|
+
];
|
|
30
|
+
const MENU_ROW_RESTING = [
|
|
31
|
+
"hover:bg-accent/10",
|
|
32
|
+
"hover:underline",
|
|
33
|
+
"focus-visible:underline",
|
|
34
|
+
"focus-visible:text-accent",
|
|
35
|
+
"text-fg",
|
|
36
|
+
"hover:text-accent"
|
|
37
|
+
];
|
|
38
|
+
const NAV_MENU_PARENT = [
|
|
39
|
+
...MENU_ROW_BASE,
|
|
40
|
+
"font-bold",
|
|
41
|
+
...MENU_ROW_RESTING
|
|
42
|
+
];
|
|
43
|
+
const NAV_MENU_PARENT_ACTIVE_SUFFIX = ["text-accent"];
|
|
44
|
+
const NAV_MENU_PLAIN = [
|
|
45
|
+
...MENU_ROW_BASE,
|
|
46
|
+
...MENU_ROW_RESTING
|
|
47
|
+
];
|
|
48
|
+
const NAV_MENU_PLAIN_ACTIVE_SUFFIX = [
|
|
49
|
+
...NAV_CHILD_ACTIVE
|
|
50
|
+
];
|
|
51
|
+
const NAV_MENU_CHILD_ACTIVE = [
|
|
52
|
+
...MENU_CHILD_BASE,
|
|
53
|
+
...NAV_CHILD_ACTIVE,
|
|
54
|
+
"hover:bg-accent/10",
|
|
55
|
+
"hover:underline",
|
|
56
|
+
"focus-visible:underline"
|
|
57
|
+
];
|
|
58
|
+
const NAV_MENU_CHILD_INACTIVE = [
|
|
59
|
+
...MENU_CHILD_BASE,
|
|
60
|
+
"text-fg",
|
|
61
|
+
"hover:bg-accent/10",
|
|
62
|
+
"hover:text-accent",
|
|
63
|
+
"hover:underline",
|
|
64
|
+
"focus-visible:underline",
|
|
65
|
+
"focus-visible:text-accent"
|
|
66
|
+
];
|
|
67
|
+
export {
|
|
68
|
+
NAV_CHEVRON_ACTIVE,
|
|
69
|
+
NAV_CHEVRON_INACTIVE,
|
|
70
|
+
NAV_CHILD_ACTIVE,
|
|
71
|
+
NAV_CHILD_INACTIVE,
|
|
72
|
+
NAV_MENU_CHILD_ACTIVE,
|
|
73
|
+
NAV_MENU_CHILD_INACTIVE,
|
|
74
|
+
NAV_MENU_PARENT,
|
|
75
|
+
NAV_MENU_PARENT_ACTIVE_SUFFIX,
|
|
76
|
+
NAV_MENU_PLAIN,
|
|
77
|
+
NAV_MENU_PLAIN_ACTIVE_SUFFIX,
|
|
78
|
+
NAV_TOP_ACTIVE,
|
|
79
|
+
NAV_TOP_INACTIVE
|
|
80
|
+
};
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
|
|
2
|
+
import {
|
|
3
|
+
NAV_CHEVRON_ACTIVE,
|
|
4
|
+
NAV_CHEVRON_INACTIVE,
|
|
5
|
+
NAV_CHILD_ACTIVE,
|
|
6
|
+
NAV_CHILD_INACTIVE,
|
|
7
|
+
NAV_MENU_CHILD_ACTIVE,
|
|
8
|
+
NAV_MENU_CHILD_INACTIVE,
|
|
9
|
+
NAV_MENU_PARENT,
|
|
10
|
+
NAV_MENU_PARENT_ACTIVE_SUFFIX,
|
|
11
|
+
NAV_MENU_PLAIN,
|
|
12
|
+
NAV_MENU_PLAIN_ACTIVE_SUFFIX,
|
|
13
|
+
NAV_TOP_ACTIVE,
|
|
14
|
+
NAV_TOP_INACTIVE
|
|
15
|
+
} from "./nav-class-tokens.js";
|
|
16
|
+
const clsArgs = (tokens) => tokens.map((token) => JSON.stringify(token)).join(", ");
|
|
17
|
+
const clsLiteral = (tokens) => JSON.stringify(tokens.join(" "));
|
|
18
|
+
const clsAppend = (tokens) => JSON.stringify(" " + tokens.join(" "));
|
|
2
19
|
const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
3
20
|
var cleanupNavOverflow = null;
|
|
4
21
|
|
|
@@ -56,12 +73,12 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
|
56
73
|
function setTopActive(a, active) {
|
|
57
74
|
if (!a) return;
|
|
58
75
|
if (active) {
|
|
59
|
-
a.classList.add(
|
|
60
|
-
a.classList.remove(
|
|
76
|
+
a.classList.add(${clsArgs(NAV_TOP_ACTIVE)});
|
|
77
|
+
a.classList.remove(${clsArgs(NAV_TOP_INACTIVE)});
|
|
61
78
|
a.setAttribute("aria-current", "page");
|
|
62
79
|
} else {
|
|
63
|
-
a.classList.remove(
|
|
64
|
-
a.classList.add(
|
|
80
|
+
a.classList.remove(${clsArgs(NAV_TOP_ACTIVE)});
|
|
81
|
+
a.classList.add(${clsArgs(NAV_TOP_INACTIVE)});
|
|
65
82
|
a.removeAttribute("aria-current");
|
|
66
83
|
}
|
|
67
84
|
}
|
|
@@ -79,19 +96,19 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
|
79
96
|
if (childActive) {
|
|
80
97
|
anyChild = true;
|
|
81
98
|
c.setAttribute("data-active", "");
|
|
82
|
-
c.classList.add(
|
|
83
|
-
c.classList.remove(
|
|
99
|
+
c.classList.add(${clsArgs(NAV_CHILD_ACTIVE)});
|
|
100
|
+
c.classList.remove(${clsArgs(NAV_CHILD_INACTIVE)});
|
|
84
101
|
} else {
|
|
85
102
|
c.removeAttribute("data-active");
|
|
86
|
-
c.classList.remove(
|
|
87
|
-
c.classList.add(
|
|
103
|
+
c.classList.remove(${clsArgs(NAV_CHILD_ACTIVE)});
|
|
104
|
+
c.classList.add(${clsArgs(NAV_CHILD_INACTIVE)});
|
|
88
105
|
}
|
|
89
106
|
});
|
|
90
107
|
topActive = parentMatch || anyChild;
|
|
91
108
|
var svg = topA ? topA.querySelector("svg") : null;
|
|
92
109
|
if (svg) {
|
|
93
|
-
if (topActive) { svg.classList.add(
|
|
94
|
-
else { svg.classList.add(
|
|
110
|
+
if (topActive) { svg.classList.add(${clsArgs(NAV_CHEVRON_ACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_INACTIVE)}); }
|
|
111
|
+
else { svg.classList.add(${clsArgs(NAV_CHEVRON_INACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_ACTIVE)}); }
|
|
95
112
|
}
|
|
96
113
|
} else {
|
|
97
114
|
topActive = activePath !== "" && navPathname(topA) === activePath;
|
|
@@ -173,9 +190,9 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
|
173
190
|
a.href = parentLink.href;
|
|
174
191
|
var parentText = parentLink.textContent ? parentLink.textContent.trim().replace(/\\s+/g, " ") : "";
|
|
175
192
|
a.textContent = parentText;
|
|
176
|
-
a.className =
|
|
193
|
+
a.className = ${clsLiteral(NAV_MENU_PARENT)};
|
|
177
194
|
if (parentLink.getAttribute("aria-current") === "page") {
|
|
178
|
-
a.className +=
|
|
195
|
+
a.className += ${clsAppend(NAV_MENU_PARENT_ACTIVE_SUFFIX)};
|
|
179
196
|
}
|
|
180
197
|
li.appendChild(a);
|
|
181
198
|
moreMenu.appendChild(li);
|
|
@@ -187,8 +204,8 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
|
187
204
|
a.textContent = child.textContent ? child.textContent.trim() : "";
|
|
188
205
|
var isChildActive = child.hasAttribute("data-active");
|
|
189
206
|
a.className = isChildActive
|
|
190
|
-
?
|
|
191
|
-
:
|
|
207
|
+
? ${clsLiteral(NAV_MENU_CHILD_ACTIVE)}
|
|
208
|
+
: ${clsLiteral(NAV_MENU_CHILD_INACTIVE)};
|
|
192
209
|
li.appendChild(a);
|
|
193
210
|
moreMenu.appendChild(li);
|
|
194
211
|
});
|
|
@@ -198,9 +215,9 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
|
198
215
|
var a2 = document.createElement("a");
|
|
199
216
|
a2.href = anchor.href;
|
|
200
217
|
a2.textContent = anchor.textContent ? anchor.textContent.trim() : "";
|
|
201
|
-
a2.className =
|
|
218
|
+
a2.className = ${clsLiteral(NAV_MENU_PLAIN)};
|
|
202
219
|
if (anchor.getAttribute("aria-current") === "page") {
|
|
203
|
-
a2.className +=
|
|
220
|
+
a2.className += ${clsAppend(NAV_MENU_PLAIN_ACTIVE_SUFFIX)};
|
|
204
221
|
}
|
|
205
222
|
li2.appendChild(a2);
|
|
206
223
|
moreMenu.appendChild(li2);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @jsxRuntime automatic */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
3
|
import type { ComponentChildren, JSX } from "preact";
|
|
4
|
+
import type { TagItem } from "../nav-indexing/types.js";
|
|
4
5
|
import type { DocNavNode } from "../doc-page-props/index.js";
|
|
5
6
|
import type { ChromeContext } from "../factory-context/index.js";
|
|
6
7
|
import type { Settings } from "../settings.js";
|
|
@@ -12,9 +13,10 @@ export interface HomePageViewProps {
|
|
|
12
13
|
* `hostBindings.homeExtras({ locale })` call when `extras` is absent. */
|
|
13
14
|
locale: string;
|
|
14
15
|
/**
|
|
15
|
-
* Extra content rendered
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Extra content rendered INLINE at the end of the links row, `/`-separated
|
|
17
|
+
* from whatever precedes it (the primary link and/or the GitHub link). A
|
|
18
|
+
* VALUE (already-rendered children) — the host-PAGE path. Takes precedence
|
|
19
|
+
* over `ctx.hostBindings.homeExtras` when both are present.
|
|
18
20
|
*
|
|
19
21
|
* This value-vs-renderer asymmetry (prop = value, `hostBindings.homeExtras`
|
|
20
22
|
* = renderer) is INTENTIONAL, not an oversight to "unify": a host page
|
|
@@ -24,6 +26,17 @@ export interface HomePageViewProps {
|
|
|
24
26
|
* locale })`.
|
|
25
27
|
*/
|
|
26
28
|
extras?: ComponentChildren;
|
|
29
|
+
/**
|
|
30
|
+
* Overrides the default first hero link, which is otherwise
|
|
31
|
+
* `settings.headerNav[0]` + `t("nav.overview", locale)`. `path` is
|
|
32
|
+
* locale-relative (the view prefixes it with the locale-URL prefix and
|
|
33
|
+
* resolves it through `withBase`, same as the default); `labelKey`
|
|
34
|
+
* resolves through `t(labelKey, locale)`.
|
|
35
|
+
*/
|
|
36
|
+
heroLink?: {
|
|
37
|
+
path: string;
|
|
38
|
+
labelKey: string;
|
|
39
|
+
};
|
|
27
40
|
/** Prepared nav tree for the `SiteTreeNav` grid (already grouped via
|
|
28
41
|
* `groupSatelliteNodes` by the caller — the two routes group their own
|
|
29
42
|
* locale-specific tree before handing it here). */
|
|
@@ -32,9 +45,25 @@ export interface HomePageViewProps {
|
|
|
32
45
|
categoryOrder: string[];
|
|
33
46
|
/** Root-category slugs that should start collapsed in `SiteTreeNav`. */
|
|
34
47
|
initiallyCollapsedCategorySlugs?: string[];
|
|
35
|
-
/** Unique tag count for the current locale — gates the "
|
|
36
|
-
* together with `settings.docTags`.
|
|
48
|
+
/** Unique tag count for the current locale — gates the "Tags" section
|
|
49
|
+
* together with `settings.docTags`. Part of the documented public
|
|
50
|
+
* `@takazudo/zudo-doc/home-page` props shape (kept for back-compat). */
|
|
37
51
|
tagCount: number;
|
|
52
|
+
/**
|
|
53
|
+
* Alphabetically-sorted tag list (tag + doc count + pre-resolved href) for
|
|
54
|
+
* the current locale. When provided (and non-empty) the section renders the
|
|
55
|
+
* tag chips (first {@link HomePageViewProps.tagLimit}) plus a "see all tags"
|
|
56
|
+
* nav. When omitted — legacy callers that pass only `tagCount` — the section
|
|
57
|
+
* falls back to the pre-#3027 single "All Tags" link, so existing
|
|
58
|
+
* `@takazudo/zudo-doc/home-page` consumers are not broken.
|
|
59
|
+
*/
|
|
60
|
+
tags?: TagItem[];
|
|
61
|
+
/**
|
|
62
|
+
* Max number of tag chips rendered in the home "Tags" section before a "…"
|
|
63
|
+
* overflow indicator is shown. The full set stays reachable via the
|
|
64
|
+
* "see all tags" link. @default 30
|
|
65
|
+
*/
|
|
66
|
+
tagLimit?: number;
|
|
38
67
|
/**
|
|
39
68
|
* Opt the home page into the **wide** content layout (full-viewport
|
|
40
69
|
* category grid instead of the standard capped reading column). Threaded to
|