livemark 0.15.0 → 0.17.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/build/actions/config/define.d.ts +67 -3
- package/build/actions/config/define.js +3 -3
- package/build/actions/config/load.js +23 -2
- package/build/components/Layout.js +11 -4
- package/build/components/SidebarArticles.js +9 -9
- package/build/components/SidebarSections.js +11 -5
- package/build/content/article.d.ts +10 -10
- package/build/content/article.js +19 -17
- package/build/helpers/article.d.ts +1 -15
- package/build/helpers/article.js +1 -34
- package/build/helpers/resolve-base.d.ts +6 -0
- package/build/helpers/resolve-base.js +13 -0
- package/build/helpers/resolve-base.unit.d.ts +1 -0
- package/build/helpers/resolve-base.unit.js +49 -0
- package/build/helpers/section.d.ts +10 -2
- package/build/helpers/section.js +19 -2
- package/build/helpers/section.unit.js +32 -2
- package/build/models/article.d.ts +0 -9
- package/build/models/article.js +1 -2
- package/build/models/config.d.ts +62 -19
- package/build/models/config.js +9 -11
- package/build/models/patch.d.ts +0 -1
- package/build/models/section.d.ts +29 -6
- package/build/models/section.js +14 -3
- package/package.json +1 -1
- package/source/actions/config/define.ts +4 -6
- package/source/actions/config/load.ts +26 -6
- package/source/components/Layout.tsx +20 -5
- package/source/components/SidebarArticles.tsx +17 -24
- package/source/components/SidebarSections.tsx +23 -3
- package/source/content/article.ts +20 -26
- package/source/helpers/article.ts +1 -39
- package/source/helpers/resolve-base.ts +11 -0
- package/source/helpers/resolve-base.unit.ts +61 -0
- package/source/helpers/section.ts +18 -2
- package/source/helpers/section.unit.ts +43 -1
- package/source/models/article.ts +0 -10
- package/source/models/config.ts +32 -14
- package/source/models/section.ts +18 -2
package/build/models/config.js
CHANGED
|
@@ -5,15 +5,12 @@ import { Section } from "./section.js";
|
|
|
5
5
|
import { CodeThemeDark, CodeThemeLight } from "./theme.js";
|
|
6
6
|
export const UserConfig = z.object({
|
|
7
7
|
site: z.string().optional(),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const trimmed = v.replace(/^\/|\/$/g, "");
|
|
15
|
-
return trimmed ? `/${trimmed}` : undefined;
|
|
16
|
-
}),
|
|
8
|
+
// Stored as `unknown` in the schema (zod can't type a function and
|
|
9
|
+
// string union meaningfully); the public TypeScript surface above
|
|
10
|
+
// lifts it back to `Base` for editor support. The build pipeline
|
|
11
|
+
// resolves it via `resolveBase()` before any runtime consumer sees
|
|
12
|
+
// it, so the runtime `WebsiteConfig.base` is strictly `string`.
|
|
13
|
+
base: z.unknown().optional(),
|
|
17
14
|
favicon: z.string().optional(),
|
|
18
15
|
logo: z.string().optional(),
|
|
19
16
|
title: z.string().default("Livemark"),
|
|
@@ -34,15 +31,16 @@ export const WebsiteConfig = UserConfig.pick({
|
|
|
34
31
|
title: true,
|
|
35
32
|
description: true,
|
|
36
33
|
site: true,
|
|
37
|
-
base: true,
|
|
38
34
|
favicon: true,
|
|
39
35
|
logo: true,
|
|
40
36
|
links: true,
|
|
41
37
|
sections: true,
|
|
38
|
+
}).extend({
|
|
39
|
+
base: z.string().optional(),
|
|
42
40
|
});
|
|
43
41
|
export const SystemConfig = z.object({
|
|
44
42
|
root: z.string(),
|
|
45
43
|
configPath: z.string(),
|
|
46
44
|
});
|
|
47
45
|
export const Config = UserConfig.merge(SystemConfig);
|
|
48
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
46
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc291cmNlL21vZGVscy9jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQTtBQUV2QixPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBQ2hDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDbEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUN0QyxPQUFPLEVBQUUsYUFBYSxFQUFFLGNBQWMsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQStCMUQsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDakMsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsbUVBQW1FO0lBQ25FLGtFQUFrRTtJQUNsRSxpRUFBaUU7SUFDakUsbUVBQW1FO0lBQ25FLGdFQUFnRTtJQUNoRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUM1QixPQUFPLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUM5QixJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUMzQixLQUFLLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7SUFDckMsV0FBVyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMseUJBQXlCLENBQUM7SUFDMUQsT0FBTyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUN0RSxPQUFPLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDOUQsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFDMUQsYUFBYSxFQUFFLGFBQWEsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFDeEQsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQy9CLFFBQVEsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLFFBQVEsRUFBRTtJQUNyQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDbEMsc0VBQXNFO0lBQ3RFLG1FQUFtRTtJQUNuRSxrRUFBa0U7SUFDbEUsSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDN0IsQ0FBQyxDQUFBO0FBTUYsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUM7SUFDM0MsS0FBSyxFQUFFLElBQUk7SUFDWCxXQUFXLEVBQUUsSUFBSTtJQUNqQixJQUFJLEVBQUUsSUFBSTtJQUNWLE9BQU8sRUFBRSxJQUFJO0lBQ2IsSUFBSSxFQUFFLElBQUk7SUFDVixLQUFLLEVBQUUsSUFBSTtJQUNYLFFBQVEsRUFBRSxJQUFJO0NBQ2YsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUNSLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQzVCLENBQUMsQ0FBQTtBQU1GLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ25DLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0lBQ2hCLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0NBQ3ZCLENBQUMsQ0FBQTtBQWNGLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgeiB9IGZyb20gXCJ6b2RcIlxuaW1wb3J0IHR5cGUgeyBDb25maWdFbnYsIFVzZXJDb25maWcgYXMgVml0ZVVzZXJDb25maWcgfSBmcm9tIFwidml0ZVwiXG5pbXBvcnQgeyBMaW5rIH0gZnJvbSBcIi4vbGluay50c1wiXG5pbXBvcnQgeyBQYXRjaCB9IGZyb20gXCIuL3BhdGNoLnRzXCJcbmltcG9ydCB7IFNlY3Rpb24gfSBmcm9tIFwiLi9zZWN0aW9uLnRzXCJcbmltcG9ydCB7IENvZGVUaGVtZURhcmssIENvZGVUaGVtZUxpZ2h0IH0gZnJvbSBcIi4vdGhlbWUudHNcIlxuXG4vKiogRXNjYXBlIGhhdGNoIGZvciBhcmJpdHJhcnkgVml0ZSBjb25maWcg4oCUIGFjY2VwdHMgZWl0aGVyIGEgY29uZmlnXG4gKiAgb2JqZWN0IChzaW1wbGUgb3ZlcnJpZGVzKSBvciBhIGZ1bmN0aW9uIGZvcm0gKGAoZW52KSA9PiBjb25maWdgLFxuICogIG1hdGNoaW5nIFZpdGUncyBvd24gYGRlZmluZUNvbmZpZ2ApLiBNZXJnZWQgb24gdG9wIG9mIGxpdmVtYXJrJ3NcbiAqICBkZWZhdWx0cyB2aWEgYG1lcmdlQ29uZmlnYCwgc28gcGx1Z2luIGFycmF5cyBjb25jYXRlbmF0ZSwgZGVmaW5lc1xuICogIG1lcmdlLCBldGMuIFVzZSB0aGlzIGZvciBkZXBsb3ltZW50LXRhcmdldCB0d2Vha3MgKGUuZy4gQ0YgV29ya2Vyc1xuICogIHZpYSBgZW52aXJvbm1lbnRzLnNzci5yZXNvbHZlLmNvbmRpdGlvbnM6IFtcIndvcmtlcmRcIiwgLi4uXWApLiAqL1xuZXhwb3J0IHR5cGUgVml0ZU92ZXJyaWRlID1cbiAgfCBWaXRlVXNlckNvbmZpZ1xuICB8ICgoZW52OiBDb25maWdFbnYpID0+IFZpdGVVc2VyQ29uZmlnIHwgUHJvbWlzZTxWaXRlVXNlckNvbmZpZz4pXG5cbi8qKiBTdWItcGF0aCBtb3VudC4gU3RyaW5nIGZvcm0gaXMgdXNlZCBhcy1pczsgZnVuY3Rpb24gZm9ybSBpcyBjYWxsZWRcbiAqICB3aXRoIFZpdGUncyBgY29tbWFuZGAgKGBcInNlcnZlXCJgIGluIGRldiwgYFwiYnVpbGRcImAgaW4gcHJvZHVjdGlvbikgc29cbiAqICB0aGUgdXNlciBjYW4gYnJhbmNoICh0eXBpY2FsIGNhc2U6IHNlcnZlIGF0IGAvYCBpbiBkZXYgYnV0IGF0XG4gKiAgYC9yZXBvL2AgaW4gcHJvZHVjdGlvbiBmb3IgR2l0SHViIFBhZ2VzKS4gVGhlIGJ1aWxkIHBpcGVsaW5lXG4gKiAgcmVzb2x2ZXMgaXQgdG8gYSBzdHJpbmcgYmVmb3JlIGFueSBydW50aW1lIHNlZXMgaXQuICovXG5leHBvcnQgdHlwZSBCYXNlID0gc3RyaW5nIHwgKChjb21tYW5kOiBcImJ1aWxkXCIgfCBcInNlcnZlXCIpID0+IHN0cmluZyB8IHVuZGVmaW5lZClcblxuLyoqXG4gKiBVc2VyLXByb3ZpZGVkIGNvbmZpZ3VyYXRpb24gZm9yIExpdmVtYXJrLlxuICpcbiAqIGB2aXRlYCBhbmQgYGJhc2VgIGFyZSBsaWZ0ZWQgZnJvbSBgdW5rbm93bmAgdG8gdGhlaXIgcHVibGljIHR5cGVzIHNvXG4gKiBgZGVmaW5lQ29uZmlnYCBzaG93cyB0aGUgcmlnaHQgc2hhcGUgaW4gZWRpdG9ycy4gVXNlIHRoaXMgdHlwZSBmb3JcbiAqIHRoZSBpbnB1dCB0byBgZGVmaW5lQ29uZmlnYCDigJQgaXQga2VlcHMgZGVmYXVsdGVkIGZpZWxkcyBvcHRpb25hbFxuICogKGJlY2F1c2Ugem9kIGlucHV0IOKJoCBvdXRwdXQgZm9yIGZpZWxkcyB3aXRoIGAuZGVmYXVsdCgpYCkuXG4gKi9cbmV4cG9ydCB0eXBlIFVzZXJDb25maWcgPSBPbWl0PHouaW5wdXQ8dHlwZW9mIFVzZXJDb25maWc+LCBcInZpdGVcIiB8IFwiYmFzZVwiPiAmIHtcbiAgdml0ZT86IFZpdGVPdmVycmlkZVxuICBiYXNlPzogQmFzZVxufVxuZXhwb3J0IGNvbnN0IFVzZXJDb25maWcgPSB6Lm9iamVjdCh7XG4gIHNpdGU6IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgLy8gU3RvcmVkIGFzIGB1bmtub3duYCBpbiB0aGUgc2NoZW1hICh6b2QgY2FuJ3QgdHlwZSBhIGZ1bmN0aW9uIGFuZFxuICAvLyBzdHJpbmcgdW5pb24gbWVhbmluZ2Z1bGx5KTsgdGhlIHB1YmxpYyBUeXBlU2NyaXB0IHN1cmZhY2UgYWJvdmVcbiAgLy8gbGlmdHMgaXQgYmFjayB0byBgQmFzZWAgZm9yIGVkaXRvciBzdXBwb3J0LiBUaGUgYnVpbGQgcGlwZWxpbmVcbiAgLy8gcmVzb2x2ZXMgaXQgdmlhIGByZXNvbHZlQmFzZSgpYCBiZWZvcmUgYW55IHJ1bnRpbWUgY29uc3VtZXIgc2Vlc1xuICAvLyBpdCwgc28gdGhlIHJ1bnRpbWUgYFdlYnNpdGVDb25maWcuYmFzZWAgaXMgc3RyaWN0bHkgYHN0cmluZ2AuXG4gIGJhc2U6IHoudW5rbm93bigpLm9wdGlvbmFsKCksXG4gIGZhdmljb246IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgbG9nbzogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxuICB0aXRsZTogei5zdHJpbmcoKS5kZWZhdWx0KFwiTGl2ZW1hcmtcIiksXG4gIGRlc2NyaXB0aW9uOiB6LnN0cmluZygpLmRlZmF1bHQoXCJNYXJrZG93biBzaXRlIGdlbmVyYXRvclwiKSxcbiAgaW5jbHVkZTogei51bmlvbihbei5zdHJpbmcoKSwgei5hcnJheSh6LnN0cmluZygpKV0pLmRlZmF1bHQoXCIqKi8qLm1kXCIpLFxuICBleGNsdWRlOiB6LnVuaW9uKFt6LnN0cmluZygpLCB6LmFycmF5KHouc3RyaW5nKCkpXSkub3B0aW9uYWwoKSxcbiAgY29kZVRoZW1lTGlnaHQ6IENvZGVUaGVtZUxpZ2h0LmRlZmF1bHQoXCJjYXRwcHVjY2luLWxhdHRlXCIpLFxuICBjb2RlVGhlbWVEYXJrOiBDb2RlVGhlbWVEYXJrLmRlZmF1bHQoXCJjYXRwcHVjY2luLW1vY2hhXCIpLFxuICBsaW5rczogei5hcnJheShMaW5rKS5vcHRpb25hbCgpLFxuICBzZWN0aW9uczogei5hcnJheShTZWN0aW9uKS5vcHRpb25hbCgpLFxuICBwYXRjaGVzOiB6LmFycmF5KFBhdGNoKS5vcHRpb25hbCgpLFxuICAvLyBTdG9yZWQgYXMgYHVua25vd25gIGluIHRoZSBzY2hlbWEgKHpvZCBjYW4ndCB0eXBlIGEgVml0ZSBVc2VyQ29uZmlnXG4gIC8vIG9yIGEgZnVuY3Rpb24gcmV0dXJuaW5nIG9uZSBtZWFuaW5nZnVsbHkpOyB0aGUgcHVibGljIFR5cGVTY3JpcHRcbiAgLy8gc3VyZmFjZSBhYm92ZSBsaWZ0cyBpdCBiYWNrIHRvIFZpdGVPdmVycmlkZSBmb3IgZWRpdG9yIHN1cHBvcnQuXG4gIHZpdGU6IHoudW5rbm93bigpLm9wdGlvbmFsKCksXG59KVxuXG4vKiogV2Vic2l0ZSBjb25maWd1cmF0aW9uIGluamVjdGVkIGF0IGJ1aWxkIHRpbWUgdmlhIFZpdGUgZGVmaW5lLlxuICogIGBiYXNlYCBpcyBzdHJpY3RseSBhIHN0cmluZyBoZXJlIOKAlCB0aGUgYnVpbGQgcGlwZWxpbmUgaGFzIGFscmVhZHlcbiAqICByZXNvbHZlZCBhbnkgZnVuY3Rpb24gZm9ybSB2aWEgYHJlc29sdmVCYXNlKClgLiAqL1xuZXhwb3J0IHR5cGUgV2Vic2l0ZUNvbmZpZyA9IHouaW5mZXI8dHlwZW9mIFdlYnNpdGVDb25maWc+XG5leHBvcnQgY29uc3QgV2Vic2l0ZUNvbmZpZyA9IFVzZXJDb25maWcucGljayh7XG4gIHRpdGxlOiB0cnVlLFxuICBkZXNjcmlwdGlvbjogdHJ1ZSxcbiAgc2l0ZTogdHJ1ZSxcbiAgZmF2aWNvbjogdHJ1ZSxcbiAgbG9nbzogdHJ1ZSxcbiAgbGlua3M6IHRydWUsXG4gIHNlY3Rpb25zOiB0cnVlLFxufSkuZXh0ZW5kKHtcbiAgYmFzZTogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxufSlcblxuLyoqXG4gKiBTeXN0ZW0tY29tcHV0ZWQgY29uZmlndXJhdGlvbiBkZXJpdmVkIGZyb20gdGhlIGNvbmZpZyBmaWxlIGxvY2F0aW9uLlxuICovXG5leHBvcnQgdHlwZSBTeXN0ZW1Db25maWcgPSB6LmluZmVyPHR5cGVvZiBTeXN0ZW1Db25maWc+XG5leHBvcnQgY29uc3QgU3lzdGVtQ29uZmlnID0gei5vYmplY3Qoe1xuICByb290OiB6LnN0cmluZygpLFxuICBjb25maWdQYXRoOiB6LnN0cmluZygpLFxufSlcblxuLyoqXG4gKiBGdWxseSByZXNvbHZlZCBMaXZlbWFyayBjb25maWd1cmF0aW9uLlxuICpcbiAqIGB2aXRlYCBzdGF5cyBgdW5rbm93bmAgYmVjYXVzZSB0aGF0J3Mgd2hhdCB0aGUgcnVudGltZSBzY2hlbWFcbiAqIHByb2R1Y2VzIOKAlCBjb25zdW1lci1mYWNpbmcgdHlwaW5nIGhhcHBlbnMgdmlhIHtAbGluayBVc2VyQ29uZmlnfSBzb1xuICogYGRlZmluZUNvbmZpZ2Agc2hvd3MgdGhlIHJpZ2h0IHNoYXBlIGluIGVkaXRvcnMuIGBiYXNlYCBpcyBsaWZ0ZWQgdG9cbiAqIGBzdHJpbmcgfCB1bmRlZmluZWRgIGJlY2F1c2UgYGxvYWRDb25maWcoKWAgcmVzb2x2ZXMgYW55IGZ1bmN0aW9uXG4gKiBmb3JtIHZpYSBgcmVzb2x2ZUJhc2UoKWAgYmVmb3JlIHJldHVybmluZy5cbiAqL1xuZXhwb3J0IHR5cGUgQ29uZmlnID0gT21pdDx6LmluZmVyPHR5cGVvZiBDb25maWc+LCBcImJhc2VcIj4gJiB7XG4gIGJhc2U/OiBzdHJpbmdcbn1cbmV4cG9ydCBjb25zdCBDb25maWcgPSBVc2VyQ29uZmlnLm1lcmdlKFN5c3RlbUNvbmZpZylcbiJdfQ==
|
package/build/models/patch.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ export declare const Patch: z.ZodObject<{
|
|
|
10
10
|
icon: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
11
11
|
order: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
12
12
|
path: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
13
|
-
group: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
14
13
|
sidebar: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
15
14
|
toc: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
16
15
|
image: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
@@ -4,39 +4,53 @@ export type ArticleSection = z.infer<typeof ArticleSection>;
|
|
|
4
4
|
export declare const ArticleSection: z.ZodObject<{
|
|
5
5
|
icon: z.ZodOptional<z.ZodString>;
|
|
6
6
|
title: z.ZodString;
|
|
7
|
-
prefix: z.ZodString;
|
|
8
7
|
position: z.ZodDefault<z.ZodEnum<{
|
|
9
8
|
header: "header";
|
|
10
9
|
sidebar: "sidebar";
|
|
11
10
|
}>>;
|
|
12
11
|
type: z.ZodLiteral<"article">;
|
|
12
|
+
prefix: z.ZodString;
|
|
13
13
|
}, z.core.$strip>;
|
|
14
14
|
/** Blog section: date-sorted posts with tags and index. */
|
|
15
15
|
export type BlogSection = z.infer<typeof BlogSection>;
|
|
16
16
|
export declare const BlogSection: z.ZodObject<{
|
|
17
17
|
icon: z.ZodOptional<z.ZodString>;
|
|
18
18
|
title: z.ZodString;
|
|
19
|
-
prefix: z.ZodString;
|
|
20
19
|
position: z.ZodDefault<z.ZodEnum<{
|
|
21
20
|
header: "header";
|
|
22
21
|
sidebar: "sidebar";
|
|
23
22
|
}>>;
|
|
24
23
|
type: z.ZodLiteral<"blog">;
|
|
24
|
+
prefix: z.ZodString;
|
|
25
25
|
}, z.core.$strip>;
|
|
26
26
|
/** Changelog section: single file or GitHub releases, split per version. */
|
|
27
27
|
export type ChangelogSection = z.infer<typeof ChangelogSection>;
|
|
28
28
|
export declare const ChangelogSection: z.ZodObject<{
|
|
29
29
|
icon: z.ZodOptional<z.ZodString>;
|
|
30
30
|
title: z.ZodString;
|
|
31
|
-
prefix: z.ZodString;
|
|
32
31
|
position: z.ZodDefault<z.ZodEnum<{
|
|
33
32
|
header: "header";
|
|
34
33
|
sidebar: "sidebar";
|
|
35
34
|
}>>;
|
|
36
35
|
type: z.ZodLiteral<"changelog">;
|
|
36
|
+
prefix: z.ZodString;
|
|
37
37
|
source: z.ZodString;
|
|
38
38
|
version: z.ZodOptional<z.ZodBoolean>;
|
|
39
39
|
}, z.core.$strip>;
|
|
40
|
+
/** Custom section: renders as a link to an arbitrary URL — absolute
|
|
41
|
+
* (`https://…`) or internal (`/changelog`). Internal URLs participate
|
|
42
|
+
* in active-state highlighting like routed sections. */
|
|
43
|
+
export type CustomSection = z.infer<typeof CustomSection>;
|
|
44
|
+
export declare const CustomSection: z.ZodObject<{
|
|
45
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
46
|
+
title: z.ZodString;
|
|
47
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
48
|
+
header: "header";
|
|
49
|
+
sidebar: "sidebar";
|
|
50
|
+
}>>;
|
|
51
|
+
type: z.ZodLiteral<"custom">;
|
|
52
|
+
url: z.ZodString;
|
|
53
|
+
}, z.core.$strip>;
|
|
40
54
|
/**
|
|
41
55
|
* A site section. Discriminated on `type`.
|
|
42
56
|
* When `type` is omitted, it defaults to "article" so plain
|
|
@@ -46,30 +60,39 @@ export type Section = z.infer<typeof Section>;
|
|
|
46
60
|
export declare const Section: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
47
61
|
icon: z.ZodOptional<z.ZodString>;
|
|
48
62
|
title: z.ZodString;
|
|
49
|
-
prefix: z.ZodString;
|
|
50
63
|
position: z.ZodDefault<z.ZodEnum<{
|
|
51
64
|
header: "header";
|
|
52
65
|
sidebar: "sidebar";
|
|
53
66
|
}>>;
|
|
54
67
|
type: z.ZodLiteral<"article">;
|
|
68
|
+
prefix: z.ZodString;
|
|
55
69
|
}, z.core.$strip>, z.ZodObject<{
|
|
56
70
|
icon: z.ZodOptional<z.ZodString>;
|
|
57
71
|
title: z.ZodString;
|
|
58
|
-
prefix: z.ZodString;
|
|
59
72
|
position: z.ZodDefault<z.ZodEnum<{
|
|
60
73
|
header: "header";
|
|
61
74
|
sidebar: "sidebar";
|
|
62
75
|
}>>;
|
|
63
76
|
type: z.ZodLiteral<"blog">;
|
|
77
|
+
prefix: z.ZodString;
|
|
64
78
|
}, z.core.$strip>, z.ZodObject<{
|
|
65
79
|
icon: z.ZodOptional<z.ZodString>;
|
|
66
80
|
title: z.ZodString;
|
|
67
|
-
prefix: z.ZodString;
|
|
68
81
|
position: z.ZodDefault<z.ZodEnum<{
|
|
69
82
|
header: "header";
|
|
70
83
|
sidebar: "sidebar";
|
|
71
84
|
}>>;
|
|
72
85
|
type: z.ZodLiteral<"changelog">;
|
|
86
|
+
prefix: z.ZodString;
|
|
73
87
|
source: z.ZodString;
|
|
74
88
|
version: z.ZodOptional<z.ZodBoolean>;
|
|
89
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
90
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
91
|
+
title: z.ZodString;
|
|
92
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
93
|
+
header: "header";
|
|
94
|
+
sidebar: "sidebar";
|
|
95
|
+
}>>;
|
|
96
|
+
type: z.ZodLiteral<"custom">;
|
|
97
|
+
url: z.ZodString;
|
|
75
98
|
}, z.core.$strip>], "type">>;
|
package/build/models/section.js
CHANGED
|
@@ -2,25 +2,36 @@ import { z } from "zod";
|
|
|
2
2
|
const BaseSection = z.object({
|
|
3
3
|
icon: z.string().optional(),
|
|
4
4
|
title: z.string(),
|
|
5
|
-
prefix: z.string(),
|
|
6
5
|
position: z.enum(["header", "sidebar"]).default("header"),
|
|
7
6
|
});
|
|
8
7
|
export const ArticleSection = BaseSection.extend({
|
|
9
8
|
type: z.literal("article"),
|
|
9
|
+
prefix: z.string(),
|
|
10
10
|
});
|
|
11
11
|
export const BlogSection = BaseSection.extend({
|
|
12
12
|
type: z.literal("blog"),
|
|
13
|
+
prefix: z.string(),
|
|
13
14
|
});
|
|
14
15
|
export const ChangelogSection = BaseSection.extend({
|
|
15
16
|
type: z.literal("changelog"),
|
|
17
|
+
prefix: z.string(),
|
|
16
18
|
source: z.string(),
|
|
17
19
|
version: z.boolean().optional(),
|
|
18
20
|
});
|
|
21
|
+
export const CustomSection = BaseSection.extend({
|
|
22
|
+
type: z.literal("custom"),
|
|
23
|
+
url: z.string(),
|
|
24
|
+
});
|
|
19
25
|
export const Section = z.preprocess(input => {
|
|
20
26
|
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
21
27
|
return input;
|
|
22
28
|
}
|
|
23
29
|
const record = input;
|
|
24
30
|
return "type" in record ? record : { ...record, type: "article" };
|
|
25
|
-
}, z.discriminatedUnion("type", [
|
|
26
|
-
|
|
31
|
+
}, z.discriminatedUnion("type", [
|
|
32
|
+
ArticleSection,
|
|
33
|
+
BlogSection,
|
|
34
|
+
ChangelogSection,
|
|
35
|
+
CustomSection,
|
|
36
|
+
]));
|
|
37
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NvdXJjZS9tb2RlbHMvc2VjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFBO0FBRXZCLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDM0IsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsS0FBSyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDakIsUUFBUSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO0NBQzFELENBQUMsQ0FBQTtBQUlGLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDO0lBQy9DLElBQUksRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUMxQixNQUFNLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRTtDQUNuQixDQUFDLENBQUE7QUFJRixNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUM1QyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7SUFDdkIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7Q0FDbkIsQ0FBQyxDQUFBO0FBSUYsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUNqRCxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUM7SUFDNUIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsT0FBTyxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDaEMsQ0FBQyxDQUFBO0FBTUYsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUM7SUFDOUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO0lBQ3pCLEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0NBQ2hCLENBQUMsQ0FBQTtBQVFGLE1BQU0sQ0FBQyxNQUFNLE9BQU8sR0FBRyxDQUFDLENBQUMsVUFBVSxDQUNqQyxLQUFLLENBQUMsRUFBRTtJQUNOLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hFLE9BQU8sS0FBSyxDQUFBO0lBQ2QsQ0FBQztJQUNELE1BQU0sTUFBTSxHQUFHLEtBQWdDLENBQUE7SUFDL0MsT0FBTyxNQUFNLElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFBO0FBQ25FLENBQUMsRUFDRCxDQUFDLENBQUMsa0JBQWtCLENBQUMsTUFBTSxFQUFFO0lBQzNCLGNBQWM7SUFDZCxXQUFXO0lBQ1gsZ0JBQWdCO0lBQ2hCLGFBQWE7Q0FDZCxDQUFDLENBQ0gsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHogfSBmcm9tIFwiem9kXCJcblxuY29uc3QgQmFzZVNlY3Rpb24gPSB6Lm9iamVjdCh7XG4gIGljb246IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgdGl0bGU6IHouc3RyaW5nKCksXG4gIHBvc2l0aW9uOiB6LmVudW0oW1wiaGVhZGVyXCIsIFwic2lkZWJhclwiXSkuZGVmYXVsdChcImhlYWRlclwiKSxcbn0pXG5cbi8qKiBEZWZhdWx0IHNlY3Rpb246IHJlbmRlcnMgYXJ0aWNsZXMuICovXG5leHBvcnQgdHlwZSBBcnRpY2xlU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIEFydGljbGVTZWN0aW9uPlxuZXhwb3J0IGNvbnN0IEFydGljbGVTZWN0aW9uID0gQmFzZVNlY3Rpb24uZXh0ZW5kKHtcbiAgdHlwZTogei5saXRlcmFsKFwiYXJ0aWNsZVwiKSxcbiAgcHJlZml4OiB6LnN0cmluZygpLFxufSlcblxuLyoqIEJsb2cgc2VjdGlvbjogZGF0ZS1zb3J0ZWQgcG9zdHMgd2l0aCB0YWdzIGFuZCBpbmRleC4gKi9cbmV4cG9ydCB0eXBlIEJsb2dTZWN0aW9uID0gei5pbmZlcjx0eXBlb2YgQmxvZ1NlY3Rpb24+XG5leHBvcnQgY29uc3QgQmxvZ1NlY3Rpb24gPSBCYXNlU2VjdGlvbi5leHRlbmQoe1xuICB0eXBlOiB6LmxpdGVyYWwoXCJibG9nXCIpLFxuICBwcmVmaXg6IHouc3RyaW5nKCksXG59KVxuXG4vKiogQ2hhbmdlbG9nIHNlY3Rpb246IHNpbmdsZSBmaWxlIG9yIEdpdEh1YiByZWxlYXNlcywgc3BsaXQgcGVyIHZlcnNpb24uICovXG5leHBvcnQgdHlwZSBDaGFuZ2Vsb2dTZWN0aW9uID0gei5pbmZlcjx0eXBlb2YgQ2hhbmdlbG9nU2VjdGlvbj5cbmV4cG9ydCBjb25zdCBDaGFuZ2Vsb2dTZWN0aW9uID0gQmFzZVNlY3Rpb24uZXh0ZW5kKHtcbiAgdHlwZTogei5saXRlcmFsKFwiY2hhbmdlbG9nXCIpLFxuICBwcmVmaXg6IHouc3RyaW5nKCksXG4gIHNvdXJjZTogei5zdHJpbmcoKSxcbiAgdmVyc2lvbjogei5ib29sZWFuKCkub3B0aW9uYWwoKSxcbn0pXG5cbi8qKiBDdXN0b20gc2VjdGlvbjogcmVuZGVycyBhcyBhIGxpbmsgdG8gYW4gYXJiaXRyYXJ5IFVSTCDigJQgYWJzb2x1dGVcbiAqICAoYGh0dHBzOi8v4oCmYCkgb3IgaW50ZXJuYWwgKGAvY2hhbmdlbG9nYCkuIEludGVybmFsIFVSTHMgcGFydGljaXBhdGVcbiAqICBpbiBhY3RpdmUtc3RhdGUgaGlnaGxpZ2h0aW5nIGxpa2Ugcm91dGVkIHNlY3Rpb25zLiAqL1xuZXhwb3J0IHR5cGUgQ3VzdG9tU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIEN1c3RvbVNlY3Rpb24+XG5leHBvcnQgY29uc3QgQ3VzdG9tU2VjdGlvbiA9IEJhc2VTZWN0aW9uLmV4dGVuZCh7XG4gIHR5cGU6IHoubGl0ZXJhbChcImN1c3RvbVwiKSxcbiAgdXJsOiB6LnN0cmluZygpLFxufSlcblxuLyoqXG4gKiBBIHNpdGUgc2VjdGlvbi4gRGlzY3JpbWluYXRlZCBvbiBgdHlwZWAuXG4gKiBXaGVuIGB0eXBlYCBpcyBvbWl0dGVkLCBpdCBkZWZhdWx0cyB0byBcImFydGljbGVcIiBzbyBwbGFpblxuICogYHsgdGl0bGUsIHByZWZpeCB9YCBlbnRyaWVzIGtlZXAgd29ya2luZy5cbiAqL1xuZXhwb3J0IHR5cGUgU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIFNlY3Rpb24+XG5leHBvcnQgY29uc3QgU2VjdGlvbiA9IHoucHJlcHJvY2VzcyhcbiAgaW5wdXQgPT4ge1xuICAgIGlmIChpbnB1dCA9PT0gbnVsbCB8fCB0eXBlb2YgaW5wdXQgIT09IFwib2JqZWN0XCIgfHwgQXJyYXkuaXNBcnJheShpbnB1dCkpIHtcbiAgICAgIHJldHVybiBpbnB1dFxuICAgIH1cbiAgICBjb25zdCByZWNvcmQgPSBpbnB1dCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPlxuICAgIHJldHVybiBcInR5cGVcIiBpbiByZWNvcmQgPyByZWNvcmQgOiB7IC4uLnJlY29yZCwgdHlwZTogXCJhcnRpY2xlXCIgfVxuICB9LFxuICB6LmRpc2NyaW1pbmF0ZWRVbmlvbihcInR5cGVcIiwgW1xuICAgIEFydGljbGVTZWN0aW9uLFxuICAgIEJsb2dTZWN0aW9uLFxuICAgIENoYW5nZWxvZ1NlY3Rpb24sXG4gICAgQ3VzdG9tU2VjdGlvbixcbiAgXSksXG4pXG4iXX0=
|
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { UserConfig } from "../../models/config.ts"
|
|
1
|
+
import { UserConfig as UserConfigSchema } from "../../models/config.ts"
|
|
2
|
+
import type { UserConfig } from "../../models/config.ts"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Validate and fill defaults for a partial config object.
|
|
6
6
|
*/
|
|
7
|
-
export function defineConfig(
|
|
8
|
-
|
|
9
|
-
): z.output<typeof UserConfig> {
|
|
10
|
-
return UserConfig.parse(input)
|
|
7
|
+
export function defineConfig(input: UserConfig) {
|
|
8
|
+
return UserConfigSchema.parse(input)
|
|
11
9
|
}
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import { dirname, resolve } from "node:path"
|
|
3
3
|
import { pathToFileURL } from "node:url"
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import { UserConfig } from "../../models/config.ts"
|
|
4
|
+
import type { Config, UserConfig } from "../../models/config.ts"
|
|
5
|
+
import { resolveBase } from "../../helpers/resolve-base.ts"
|
|
7
6
|
import { defineConfig } from "./define.ts"
|
|
8
7
|
|
|
8
|
+
/** Map livemark CLI verbs to the Vite-equivalent `command` string used by
|
|
9
|
+
* `resolveBase()`. `preview` serves the production build's artifacts so
|
|
10
|
+
* it uses the build-mode base. Default to `"build"` for safety when no
|
|
11
|
+
* recognised verb is in argv (the common path is `livemark <verb> ...`). */
|
|
12
|
+
const COMMAND_BY_VERB: Record<string, "build" | "serve"> = {
|
|
13
|
+
start: "serve",
|
|
14
|
+
build: "build",
|
|
15
|
+
preview: "build",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function detectCommand() {
|
|
19
|
+
for (const arg of process.argv.slice(2)) {
|
|
20
|
+
if (arg in COMMAND_BY_VERB) return COMMAND_BY_VERB[arg]!
|
|
21
|
+
}
|
|
22
|
+
return "build" as const
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
26
|
* Load and validate a config file, checking --config/-c CLI arg, then cwd.
|
|
11
27
|
*
|
|
@@ -23,15 +39,19 @@ export async function loadConfig(path?: string): Promise<Config> {
|
|
|
23
39
|
const cliPath = configIndex !== -1 ? process.argv[configIndex + 1] : undefined
|
|
24
40
|
const explicit = path ?? cliPath
|
|
25
41
|
const resolved = resolve(explicit ?? "livemark.config.ts")
|
|
26
|
-
let input:
|
|
42
|
+
let input: UserConfig = {}
|
|
27
43
|
if (existsSync(resolved)) {
|
|
28
44
|
// Cache-bust the ESM import so dev-server restart reloads edits on disk.
|
|
29
45
|
const url = `${pathToFileURL(resolved).href}?t=${Date.now()}`
|
|
30
46
|
const module: Record<string, unknown> = await import(url)
|
|
31
|
-
input = (module.default as
|
|
47
|
+
input = (module.default as UserConfig) ?? {}
|
|
32
48
|
} else if (explicit) {
|
|
33
49
|
throw new Error(`Config file not found: ${resolved}`)
|
|
34
50
|
}
|
|
35
51
|
const validated = defineConfig(input)
|
|
36
|
-
|
|
52
|
+
// Resolve the function form of `base` once, here, using the livemark
|
|
53
|
+
// CLI verb. Downstream consumers (Vite plugin, content-collections,
|
|
54
|
+
// runtime) get a plain string|undefined and never see the function.
|
|
55
|
+
const base = resolveBase(validated.base, detectCommand())
|
|
56
|
+
return { ...validated, base, root: dirname(resolved), configPath: resolved }
|
|
37
57
|
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "../content/article.ts"
|
|
10
10
|
import { DynamicIcon } from "../helpers/dynamic-icon.tsx"
|
|
11
11
|
import { prefixUrl } from "../helpers/prefix-url.ts"
|
|
12
|
-
import { sectionIcon } from "../helpers/section.ts"
|
|
12
|
+
import { customSectionActive, sectionIcon } from "../helpers/section.ts"
|
|
13
13
|
import {
|
|
14
14
|
SidebarInset,
|
|
15
15
|
SidebarProvider,
|
|
@@ -69,6 +69,24 @@ export function Layout(props: {
|
|
|
69
69
|
sections
|
|
70
70
|
.filter(section => section.position === "header")
|
|
71
71
|
.map(section => {
|
|
72
|
+
const icon = sectionIcon(section)
|
|
73
|
+
if (section.type === "custom") {
|
|
74
|
+
const isActive = customSectionActive(section.url, pathname)
|
|
75
|
+
return (
|
|
76
|
+
<a
|
|
77
|
+
key={section.url}
|
|
78
|
+
href={prefixUrl(section.url, config.base)}
|
|
79
|
+
className={`inline-flex items-center gap-1.5 text-foreground ${
|
|
80
|
+
isActive
|
|
81
|
+
? "font-medium border-b-2 border-foreground pb-0.5 -mb-0.5"
|
|
82
|
+
: "opacity-80 hover:opacity-100 transition-opacity"
|
|
83
|
+
}`}
|
|
84
|
+
>
|
|
85
|
+
{icon && <DynamicIcon name={icon} className="size-3.5" />}
|
|
86
|
+
<span>{section.title}</span>
|
|
87
|
+
</a>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
72
90
|
const isActive = activeSection?.prefix === section.prefix
|
|
73
91
|
const target =
|
|
74
92
|
section.type === "blog" || section.type === "changelog"
|
|
@@ -92,10 +110,7 @@ export function Layout(props: {
|
|
|
92
110
|
: "opacity-80 hover:opacity-100 transition-opacity"
|
|
93
111
|
}`}
|
|
94
112
|
>
|
|
95
|
-
<DynamicIcon
|
|
96
|
-
name={sectionIcon(section)}
|
|
97
|
-
className="size-3.5"
|
|
98
|
-
/>
|
|
113
|
+
{icon && <DynamicIcon name={icon} className="size-3.5" />}
|
|
99
114
|
<span>
|
|
100
115
|
{section.title}
|
|
101
116
|
{latestVersion && (
|
|
@@ -2,9 +2,9 @@ import { config } from "livemark:virtual"
|
|
|
2
2
|
import { Link, useLocation } from "@tanstack/react-router"
|
|
3
3
|
import { ChevronRight } from "lucide-react"
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
articleTree,
|
|
6
6
|
currentSection,
|
|
7
|
-
|
|
7
|
+
sectionArticleTrees,
|
|
8
8
|
} from "../content/article.ts"
|
|
9
9
|
import {
|
|
10
10
|
Collapsible,
|
|
@@ -31,30 +31,23 @@ export function SidebarArticles() {
|
|
|
31
31
|
|
|
32
32
|
const configSections = config.sections
|
|
33
33
|
const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
34
|
-
const
|
|
35
|
-
? (
|
|
36
|
-
|
|
37
|
-
: articleGroups
|
|
34
|
+
const tree = configSections?.length
|
|
35
|
+
? (sectionArticleTrees.get(section?.prefix ?? "__default__") ?? articleTree)
|
|
36
|
+
: articleTree
|
|
38
37
|
|
|
39
38
|
return (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
))}
|
|
53
|
-
</SidebarMenu>
|
|
54
|
-
</SidebarGroupContent>
|
|
55
|
-
</SidebarGroup>
|
|
56
|
-
))}
|
|
57
|
-
</>
|
|
39
|
+
<SidebarGroup>
|
|
40
|
+
<SidebarGroupLabel className="uppercase font-mono text-xs tracking-widest">
|
|
41
|
+
Articles
|
|
42
|
+
</SidebarGroupLabel>
|
|
43
|
+
<SidebarGroupContent>
|
|
44
|
+
<SidebarMenu>
|
|
45
|
+
{tree.map(node => (
|
|
46
|
+
<NavNode key={node.path} node={node} currentPath={pathname} />
|
|
47
|
+
))}
|
|
48
|
+
</SidebarMenu>
|
|
49
|
+
</SidebarGroupContent>
|
|
50
|
+
</SidebarGroup>
|
|
58
51
|
)
|
|
59
52
|
}
|
|
60
53
|
|
|
@@ -2,7 +2,8 @@ import { config } from "livemark:virtual"
|
|
|
2
2
|
import { Link, useLocation, useMatch } from "@tanstack/react-router"
|
|
3
3
|
import { currentSection, sectionFirstArticle } from "../content/article.ts"
|
|
4
4
|
import { DynamicIcon } from "../helpers/dynamic-icon.tsx"
|
|
5
|
-
import {
|
|
5
|
+
import { prefixUrl } from "../helpers/prefix-url.ts"
|
|
6
|
+
import { customSectionActive, sectionIcon } from "../helpers/section.ts"
|
|
6
7
|
import {
|
|
7
8
|
SidebarGroup,
|
|
8
9
|
SidebarGroupContent,
|
|
@@ -38,12 +39,31 @@ export function SidebarSections() {
|
|
|
38
39
|
<SidebarGroupContent>
|
|
39
40
|
<SidebarMenu>
|
|
40
41
|
{sections.map(s => {
|
|
42
|
+
const mobileOnly = s.position !== "sidebar"
|
|
43
|
+
const icon = sectionIcon(s)
|
|
44
|
+
if (s.type === "custom") {
|
|
45
|
+
const active = customSectionActive(s.url, pathname)
|
|
46
|
+
return (
|
|
47
|
+
<SidebarMenuItem
|
|
48
|
+
key={s.url}
|
|
49
|
+
className={mobileOnly ? "md:hidden" : undefined}
|
|
50
|
+
>
|
|
51
|
+
<SidebarMenuButton
|
|
52
|
+
isActive={active}
|
|
53
|
+
className={active ? "" : "opacity-75"}
|
|
54
|
+
render={<a href={prefixUrl(s.url, config.base)} />}
|
|
55
|
+
>
|
|
56
|
+
{icon && <DynamicIcon name={icon} className="size-4" />}
|
|
57
|
+
<span>{s.title}</span>
|
|
58
|
+
</SidebarMenuButton>
|
|
59
|
+
</SidebarMenuItem>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
41
62
|
const target =
|
|
42
63
|
s.type === "blog" || s.type === "changelog"
|
|
43
64
|
? s.prefix
|
|
44
65
|
: (sectionFirstArticle.get(s.prefix) ?? s.prefix)
|
|
45
66
|
const active = section?.prefix === s.prefix
|
|
46
|
-
const mobileOnly = s.position !== "sidebar"
|
|
47
67
|
return (
|
|
48
68
|
<SidebarMenuItem
|
|
49
69
|
key={s.prefix}
|
|
@@ -59,7 +79,7 @@ export function SidebarSections() {
|
|
|
59
79
|
/>
|
|
60
80
|
}
|
|
61
81
|
>
|
|
62
|
-
<DynamicIcon name={
|
|
82
|
+
{icon && <DynamicIcon name={icon} className="size-4" />}
|
|
63
83
|
<span>{s.title}</span>
|
|
64
84
|
</SidebarMenuButton>
|
|
65
85
|
</SidebarMenuItem>
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { config } from "livemark:virtual"
|
|
2
2
|
import { allArticles } from "content-collections"
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
buildArticleTree,
|
|
6
|
-
flattenArticleTree,
|
|
7
|
-
groupArticleTree,
|
|
8
|
-
} from "../helpers/article.ts"
|
|
3
|
+
import type { ArticleNode } from "../models/article.ts"
|
|
4
|
+
import { buildArticleTree, flattenArticleTree } from "../helpers/article.ts"
|
|
9
5
|
import { matchSection, partitionBySection } from "../helpers/section.ts"
|
|
10
6
|
|
|
11
7
|
/**
|
|
@@ -20,16 +16,11 @@ export const sortedArticles = [...allArticles].sort(
|
|
|
20
16
|
/** The article whose path is `/`, if any (rendered in place at the root) */
|
|
21
17
|
export const homeArticle = sortedArticles.find(a => a.path === "/")
|
|
22
18
|
|
|
23
|
-
/** Sidebar
|
|
24
|
-
export const
|
|
25
|
-
buildArticleTree(sortedArticles),
|
|
26
|
-
sortedArticles,
|
|
27
|
-
)
|
|
19
|
+
/** Sidebar tree derived from the article path hierarchy */
|
|
20
|
+
export const articleTree = buildArticleTree(sortedArticles)
|
|
28
21
|
|
|
29
|
-
/** Articles in depth-first order across
|
|
30
|
-
export const flatArticles =
|
|
31
|
-
flattenArticleTree(group.nodes),
|
|
32
|
-
)
|
|
22
|
+
/** Articles in depth-first order across the tree (matches sidebar walk) */
|
|
23
|
+
export const flatArticles = flattenArticleTree(articleTree)
|
|
33
24
|
|
|
34
25
|
/** The first article in sidebar order, used as the default landing page */
|
|
35
26
|
export const firstArticle = sortedArticles.find(a => a.path === flatArticles[0])
|
|
@@ -42,8 +33,12 @@ function orderKey(order?: number) {
|
|
|
42
33
|
|
|
43
34
|
const configSections = config.sections
|
|
44
35
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
36
|
+
/** Route-bound sections (everything except `custom`). Custom sections
|
|
37
|
+
* are pure links — they have no `prefix` and don't claim a URL space. */
|
|
38
|
+
const routedSections = configSections?.filter(s => s.type !== "custom") ?? []
|
|
39
|
+
|
|
40
|
+
/** Per-section article tree, keyed by section prefix */
|
|
41
|
+
export const sectionArticleTrees = new Map<string, ArticleNode[]>()
|
|
47
42
|
|
|
48
43
|
/** Per-section flat article lists, keyed by section prefix */
|
|
49
44
|
export const sectionFlatArticles = new Map<string, string[]>()
|
|
@@ -51,9 +46,9 @@ export const sectionFlatArticles = new Map<string, string[]>()
|
|
|
51
46
|
/** Per-section first article path, keyed by section prefix */
|
|
52
47
|
export const sectionFirstArticle = new Map<string, string | undefined>()
|
|
53
48
|
|
|
54
|
-
if (
|
|
55
|
-
const sectionByPrefix = new Map(
|
|
56
|
-
const buckets = partitionBySection(sortedArticles,
|
|
49
|
+
if (routedSections.length) {
|
|
50
|
+
const sectionByPrefix = new Map(routedSections.map(s => [s.prefix, s]))
|
|
51
|
+
const buckets = partitionBySection(sortedArticles, routedSections)
|
|
57
52
|
for (const [key, bucket] of buckets) {
|
|
58
53
|
const section = sectionByPrefix.get(key)
|
|
59
54
|
const sorted =
|
|
@@ -64,16 +59,15 @@ if (configSections?.length) {
|
|
|
64
59
|
)
|
|
65
60
|
: bucket
|
|
66
61
|
const tree = buildArticleTree(sorted)
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
sectionArticleGroups.set(key, groups)
|
|
62
|
+
const flat = flattenArticleTree(tree)
|
|
63
|
+
sectionArticleTrees.set(key, tree)
|
|
70
64
|
sectionFlatArticles.set(key, flat)
|
|
71
65
|
sectionFirstArticle.set(key, flat[0])
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
68
|
|
|
75
|
-
/** Find which config section an article path belongs to */
|
|
69
|
+
/** Find which routed config section an article path belongs to */
|
|
76
70
|
export function currentSection(path: string) {
|
|
77
|
-
if (!
|
|
78
|
-
return matchSection(path,
|
|
71
|
+
if (!routedSections.length) return undefined
|
|
72
|
+
return matchSection(path, routedSections)
|
|
79
73
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ArticleNode } from "../models/article.ts"
|
|
2
2
|
|
|
3
3
|
/** Build a tree from a flat sorted list of articles based on path hierarchy */
|
|
4
4
|
export function buildArticleTree(
|
|
@@ -41,44 +41,6 @@ export function buildArticleTree(
|
|
|
41
41
|
return roots
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
* Partition root nodes into sidebar sections.
|
|
46
|
-
*
|
|
47
|
-
* Walks root nodes in sort order. A root whose article has a `group` field
|
|
48
|
-
* opens a new section; subsequent roots without `group` append to the current
|
|
49
|
-
* section. Roots before the first `group` field land in a single leading
|
|
50
|
-
* section with `name: undefined` (rendered without a label).
|
|
51
|
-
*
|
|
52
|
-
* `group` on non-root articles is ignored — only roots gate section boundaries.
|
|
53
|
-
*/
|
|
54
|
-
export function groupArticleTree(
|
|
55
|
-
tree: ArticleNode[],
|
|
56
|
-
articles: { path: string; group?: string }[],
|
|
57
|
-
): ArticleGroup[] {
|
|
58
|
-
const groupByPathname = new Map<string, string | undefined>()
|
|
59
|
-
for (const article of articles) {
|
|
60
|
-
groupByPathname.set(article.path, article.group)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const sections: ArticleGroup[] = []
|
|
64
|
-
let current: ArticleGroup | undefined
|
|
65
|
-
|
|
66
|
-
for (const node of tree) {
|
|
67
|
-
const group = groupByPathname.get(node.path)
|
|
68
|
-
if (group) {
|
|
69
|
-
current = { name: group, nodes: [node] }
|
|
70
|
-
sections.push(current)
|
|
71
|
-
} else if (current) {
|
|
72
|
-
current.nodes.push(node)
|
|
73
|
-
} else {
|
|
74
|
-
current = { name: undefined, nodes: [node] }
|
|
75
|
-
sections.push(current)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return sections
|
|
80
|
-
}
|
|
81
|
-
|
|
82
44
|
/** Depth-first flatten of an article tree into a list of paths */
|
|
83
45
|
export function flattenArticleTree(nodes: ArticleNode[]) {
|
|
84
46
|
const result: string[] = []
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Resolve a base config field — accepts a string, a function of the
|
|
2
|
+
* form `(command) => string | undefined` (where `command` matches
|
|
3
|
+
* Vite's `ConfigEnv.command`), or undefined. Normalizes the result to
|
|
4
|
+
* either `undefined` (no base) or a leading-slash, no-trailing-slash
|
|
5
|
+
* string (e.g. `"repo"` and `"/repo/"` both → `"/repo"`). */
|
|
6
|
+
export function resolveBase(input: unknown, command: "build" | "serve") {
|
|
7
|
+
const raw = typeof input === "function" ? input(command) : input
|
|
8
|
+
if (typeof raw !== "string" || !raw) return undefined
|
|
9
|
+
const trimmed = raw.replace(/^\/|\/$/g, "")
|
|
10
|
+
return trimmed ? `/${trimmed}` : undefined
|
|
11
|
+
}
|