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.
Files changed (39) hide show
  1. package/build/actions/config/define.d.ts +67 -3
  2. package/build/actions/config/define.js +3 -3
  3. package/build/actions/config/load.js +23 -2
  4. package/build/components/Layout.js +11 -4
  5. package/build/components/SidebarArticles.js +9 -9
  6. package/build/components/SidebarSections.js +11 -5
  7. package/build/content/article.d.ts +10 -10
  8. package/build/content/article.js +19 -17
  9. package/build/helpers/article.d.ts +1 -15
  10. package/build/helpers/article.js +1 -34
  11. package/build/helpers/resolve-base.d.ts +6 -0
  12. package/build/helpers/resolve-base.js +13 -0
  13. package/build/helpers/resolve-base.unit.d.ts +1 -0
  14. package/build/helpers/resolve-base.unit.js +49 -0
  15. package/build/helpers/section.d.ts +10 -2
  16. package/build/helpers/section.js +19 -2
  17. package/build/helpers/section.unit.js +32 -2
  18. package/build/models/article.d.ts +0 -9
  19. package/build/models/article.js +1 -2
  20. package/build/models/config.d.ts +62 -19
  21. package/build/models/config.js +9 -11
  22. package/build/models/patch.d.ts +0 -1
  23. package/build/models/section.d.ts +29 -6
  24. package/build/models/section.js +14 -3
  25. package/package.json +1 -1
  26. package/source/actions/config/define.ts +4 -6
  27. package/source/actions/config/load.ts +26 -6
  28. package/source/components/Layout.tsx +20 -5
  29. package/source/components/SidebarArticles.tsx +17 -24
  30. package/source/components/SidebarSections.tsx +23 -3
  31. package/source/content/article.ts +20 -26
  32. package/source/helpers/article.ts +1 -39
  33. package/source/helpers/resolve-base.ts +11 -0
  34. package/source/helpers/resolve-base.unit.ts +61 -0
  35. package/source/helpers/section.ts +18 -2
  36. package/source/helpers/section.unit.ts +43 -1
  37. package/source/models/article.ts +0 -10
  38. package/source/models/config.ts +32 -14
  39. package/source/models/section.ts +18 -2
@@ -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
- base: z
9
- .string()
10
- .optional()
11
- .transform(v => {
12
- if (!v)
13
- return undefined;
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc291cmNlL21vZGVscy9jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQTtBQUV2QixPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBQ2hDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDbEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUN0QyxPQUFPLEVBQUUsYUFBYSxFQUFFLGNBQWMsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQWtCMUQsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDakMsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsSUFBSSxFQUFFLENBQUM7U0FDSixNQUFNLEVBQUU7U0FDUixRQUFRLEVBQUU7U0FDVixTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUU7UUFDYixJQUFJLENBQUMsQ0FBQztZQUFFLE9BQU8sU0FBUyxDQUFBO1FBQ3hCLE1BQU0sT0FBTyxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQ3pDLE9BQU8sT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUE7SUFDNUMsQ0FBQyxDQUFDO0lBQ0osT0FBTyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDOUIsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsS0FBSyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDO0lBQ3JDLFdBQVcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsT0FBTyxDQUFDLHlCQUF5QixDQUFDO0lBQzFELE9BQU8sRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUM7SUFDdEUsT0FBTyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQzlELGNBQWMsRUFBRSxjQUFjLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDO0lBQzFELGFBQWEsRUFBRSxhQUFhLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDO0lBQ3hELEtBQUssRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsRUFBRTtJQUMvQixRQUFRLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDckMsT0FBTyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQ2xDLHNFQUFzRTtJQUN0RSxtRUFBbUU7SUFDbkUsa0VBQWtFO0lBQ2xFLElBQUksRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQzdCLENBQUMsQ0FBQTtBQUlGLE1BQU0sQ0FBQyxNQUFNLGFBQWEsR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDO0lBQzNDLEtBQUssRUFBRSxJQUFJO0lBQ1gsV0FBVyxFQUFFLElBQUk7SUFDakIsSUFBSSxFQUFFLElBQUk7SUFDVixJQUFJLEVBQUUsSUFBSTtJQUNWLE9BQU8sRUFBRSxJQUFJO0lBQ2IsSUFBSSxFQUFFLElBQUk7SUFDVixLQUFLLEVBQUUsSUFBSTtJQUNYLFFBQVEsRUFBRSxJQUFJO0NBQ2YsQ0FBQyxDQUFBO0FBTUYsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDbkMsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDaEIsVUFBVSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7Q0FDdkIsQ0FBQyxDQUFBO0FBVUYsTUFBTSxDQUFDLE1BQU0sTUFBTSxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyB6IH0gZnJvbSBcInpvZFwiXG5pbXBvcnQgdHlwZSB7IENvbmZpZ0VudiwgVXNlckNvbmZpZyBhcyBWaXRlVXNlckNvbmZpZyB9IGZyb20gXCJ2aXRlXCJcbmltcG9ydCB7IExpbmsgfSBmcm9tIFwiLi9saW5rLnRzXCJcbmltcG9ydCB7IFBhdGNoIH0gZnJvbSBcIi4vcGF0Y2gudHNcIlxuaW1wb3J0IHsgU2VjdGlvbiB9IGZyb20gXCIuL3NlY3Rpb24udHNcIlxuaW1wb3J0IHsgQ29kZVRoZW1lRGFyaywgQ29kZVRoZW1lTGlnaHQgfSBmcm9tIFwiLi90aGVtZS50c1wiXG5cbi8qKiBFc2NhcGUgaGF0Y2ggZm9yIGFyYml0cmFyeSBWaXRlIGNvbmZpZyDigJQgYWNjZXB0cyBlaXRoZXIgYSBjb25maWdcbiAqICBvYmplY3QgKHNpbXBsZSBvdmVycmlkZXMpIG9yIGEgZnVuY3Rpb24gZm9ybSAoYChlbnYpID0+IGNvbmZpZ2AsXG4gKiAgbWF0Y2hpbmcgVml0ZSdzIG93biBgZGVmaW5lQ29uZmlnYCkuIE1lcmdlZCBvbiB0b3Agb2YgbGl2ZW1hcmsnc1xuICogIGRlZmF1bHRzIHZpYSBgbWVyZ2VDb25maWdgLCBzbyBwbHVnaW4gYXJyYXlzIGNvbmNhdGVuYXRlLCBkZWZpbmVzXG4gKiAgbWVyZ2UsIGV0Yy4gVXNlIHRoaXMgZm9yIGRlcGxveW1lbnQtdGFyZ2V0IHR3ZWFrcyAoZS5nLiBDRiBXb3JrZXJzXG4gKiAgdmlhIGBlbnZpcm9ubWVudHMuc3NyLnJlc29sdmUuY29uZGl0aW9uczogW1wid29ya2VyZFwiLCAuLi5dYCkuICovXG5leHBvcnQgdHlwZSBWaXRlT3ZlcnJpZGUgPVxuICB8IFZpdGVVc2VyQ29uZmlnXG4gIHwgKChlbnY6IENvbmZpZ0VudikgPT4gVml0ZVVzZXJDb25maWcgfCBQcm9taXNlPFZpdGVVc2VyQ29uZmlnPilcblxuLyoqXG4gKiBVc2VyLXByb3ZpZGVkIGNvbmZpZ3VyYXRpb24gZm9yIExpdmVtYXJrLlxuICovXG5leHBvcnQgdHlwZSBVc2VyQ29uZmlnID0gT21pdDx6LmluZmVyPHR5cGVvZiBVc2VyQ29uZmlnPiwgXCJ2aXRlXCI+ICYge1xuICB2aXRlPzogVml0ZU92ZXJyaWRlXG59XG5leHBvcnQgY29uc3QgVXNlckNvbmZpZyA9IHoub2JqZWN0KHtcbiAgc2l0ZTogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxuICBiYXNlOiB6XG4gICAgLnN0cmluZygpXG4gICAgLm9wdGlvbmFsKClcbiAgICAudHJhbnNmb3JtKHYgPT4ge1xuICAgICAgaWYgKCF2KSByZXR1cm4gdW5kZWZpbmVkXG4gICAgICBjb25zdCB0cmltbWVkID0gdi5yZXBsYWNlKC9eXFwvfFxcLyQvZywgXCJcIilcbiAgICAgIHJldHVybiB0cmltbWVkID8gYC8ke3RyaW1tZWR9YCA6IHVuZGVmaW5lZFxuICAgIH0pLFxuICBmYXZpY29uOiB6LnN0cmluZygpLm9wdGlvbmFsKCksXG4gIGxvZ286IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgdGl0bGU6IHouc3RyaW5nKCkuZGVmYXVsdChcIkxpdmVtYXJrXCIpLFxuICBkZXNjcmlwdGlvbjogei5zdHJpbmcoKS5kZWZhdWx0KFwiTWFya2Rvd24gc2l0ZSBnZW5lcmF0b3JcIiksXG4gIGluY2x1ZGU6IHoudW5pb24oW3ouc3RyaW5nKCksIHouYXJyYXkoei5zdHJpbmcoKSldKS5kZWZhdWx0KFwiKiovKi5tZFwiKSxcbiAgZXhjbHVkZTogei51bmlvbihbei5zdHJpbmcoKSwgei5hcnJheSh6LnN0cmluZygpKV0pLm9wdGlvbmFsKCksXG4gIGNvZGVUaGVtZUxpZ2h0OiBDb2RlVGhlbWVMaWdodC5kZWZhdWx0KFwiY2F0cHB1Y2Npbi1sYXR0ZVwiKSxcbiAgY29kZVRoZW1lRGFyazogQ29kZVRoZW1lRGFyay5kZWZhdWx0KFwiY2F0cHB1Y2Npbi1tb2NoYVwiKSxcbiAgbGlua3M6IHouYXJyYXkoTGluaykub3B0aW9uYWwoKSxcbiAgc2VjdGlvbnM6IHouYXJyYXkoU2VjdGlvbikub3B0aW9uYWwoKSxcbiAgcGF0Y2hlczogei5hcnJheShQYXRjaCkub3B0aW9uYWwoKSxcbiAgLy8gU3RvcmVkIGFzIGB1bmtub3duYCBpbiB0aGUgc2NoZW1hICh6b2QgY2FuJ3QgdHlwZSBhIFZpdGUgVXNlckNvbmZpZ1xuICAvLyBvciBhIGZ1bmN0aW9uIHJldHVybmluZyBvbmUgbWVhbmluZ2Z1bGx5KTsgdGhlIHB1YmxpYyBUeXBlU2NyaXB0XG4gIC8vIHN1cmZhY2UgYWJvdmUgbGlmdHMgaXQgYmFjayB0byBWaXRlT3ZlcnJpZGUgZm9yIGVkaXRvciBzdXBwb3J0LlxuICB2aXRlOiB6LnVua25vd24oKS5vcHRpb25hbCgpLFxufSlcblxuLyoqIFdlYnNpdGUgY29uZmlndXJhdGlvbiBpbmplY3RlZCBhdCBidWlsZCB0aW1lIHZpYSBWaXRlIGRlZmluZSAqL1xuZXhwb3J0IHR5cGUgV2Vic2l0ZUNvbmZpZyA9IHouaW5mZXI8dHlwZW9mIFdlYnNpdGVDb25maWc+XG5leHBvcnQgY29uc3QgV2Vic2l0ZUNvbmZpZyA9IFVzZXJDb25maWcucGljayh7XG4gIHRpdGxlOiB0cnVlLFxuICBkZXNjcmlwdGlvbjogdHJ1ZSxcbiAgc2l0ZTogdHJ1ZSxcbiAgYmFzZTogdHJ1ZSxcbiAgZmF2aWNvbjogdHJ1ZSxcbiAgbG9nbzogdHJ1ZSxcbiAgbGlua3M6IHRydWUsXG4gIHNlY3Rpb25zOiB0cnVlLFxufSlcblxuLyoqXG4gKiBTeXN0ZW0tY29tcHV0ZWQgY29uZmlndXJhdGlvbiBkZXJpdmVkIGZyb20gdGhlIGNvbmZpZyBmaWxlIGxvY2F0aW9uLlxuICovXG5leHBvcnQgdHlwZSBTeXN0ZW1Db25maWcgPSB6LmluZmVyPHR5cGVvZiBTeXN0ZW1Db25maWc+XG5leHBvcnQgY29uc3QgU3lzdGVtQ29uZmlnID0gei5vYmplY3Qoe1xuICByb290OiB6LnN0cmluZygpLFxuICBjb25maWdQYXRoOiB6LnN0cmluZygpLFxufSlcblxuLyoqXG4gKiBGdWxseSByZXNvbHZlZCBMaXZlbWFyayBjb25maWd1cmF0aW9uLlxuICpcbiAqIGB2aXRlYCBzdGF5cyBgdW5rbm93bmAgaGVyZSBiZWNhdXNlIHRoYXQncyB3aGF0IHRoZSBydW50aW1lIHNjaGVtYVxuICogcHJvZHVjZXMg4oCUIGNvbnN1bWVyLWZhY2luZyB0eXBpbmcgaGFwcGVucyB2aWEge0BsaW5rIFVzZXJDb25maWd9IHNvXG4gKiBgZGVmaW5lQ29uZmlnYCBzaG93cyB0aGUgcmlnaHQgc2hhcGUgaW4gZWRpdG9ycy5cbiAqL1xuZXhwb3J0IHR5cGUgQ29uZmlnID0gei5pbmZlcjx0eXBlb2YgQ29uZmlnPlxuZXhwb3J0IGNvbnN0IENvbmZpZyA9IFVzZXJDb25maWcubWVyZ2UoU3lzdGVtQ29uZmlnKVxuIl19
46
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc291cmNlL21vZGVscy9jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQTtBQUV2QixPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBQ2hDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDbEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUN0QyxPQUFPLEVBQUUsYUFBYSxFQUFFLGNBQWMsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQStCMUQsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDakMsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsbUVBQW1FO0lBQ25FLGtFQUFrRTtJQUNsRSxpRUFBaUU7SUFDakUsbUVBQW1FO0lBQ25FLGdFQUFnRTtJQUNoRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUM1QixPQUFPLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUM5QixJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUMzQixLQUFLLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7SUFDckMsV0FBVyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMseUJBQXlCLENBQUM7SUFDMUQsT0FBTyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUN0RSxPQUFPLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDOUQsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFDMUQsYUFBYSxFQUFFLGFBQWEsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFDeEQsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQy9CLFFBQVEsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLFFBQVEsRUFBRTtJQUNyQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDbEMsc0VBQXNFO0lBQ3RFLG1FQUFtRTtJQUNuRSxrRUFBa0U7SUFDbEUsSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDN0IsQ0FBQyxDQUFBO0FBTUYsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUM7SUFDM0MsS0FBSyxFQUFFLElBQUk7SUFDWCxXQUFXLEVBQUUsSUFBSTtJQUNqQixJQUFJLEVBQUUsSUFBSTtJQUNWLE9BQU8sRUFBRSxJQUFJO0lBQ2IsSUFBSSxFQUFFLElBQUk7SUFDVixLQUFLLEVBQUUsSUFBSTtJQUNYLFFBQVEsRUFBRSxJQUFJO0NBQ2YsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUNSLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQzVCLENBQUMsQ0FBQTtBQU1GLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ25DLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0lBQ2hCLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0NBQ3ZCLENBQUMsQ0FBQTtBQWNGLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgeiB9IGZyb20gXCJ6b2RcIlxuaW1wb3J0IHR5cGUgeyBDb25maWdFbnYsIFVzZXJDb25maWcgYXMgVml0ZVVzZXJDb25maWcgfSBmcm9tIFwidml0ZVwiXG5pbXBvcnQgeyBMaW5rIH0gZnJvbSBcIi4vbGluay50c1wiXG5pbXBvcnQgeyBQYXRjaCB9IGZyb20gXCIuL3BhdGNoLnRzXCJcbmltcG9ydCB7IFNlY3Rpb24gfSBmcm9tIFwiLi9zZWN0aW9uLnRzXCJcbmltcG9ydCB7IENvZGVUaGVtZURhcmssIENvZGVUaGVtZUxpZ2h0IH0gZnJvbSBcIi4vdGhlbWUudHNcIlxuXG4vKiogRXNjYXBlIGhhdGNoIGZvciBhcmJpdHJhcnkgVml0ZSBjb25maWcg4oCUIGFjY2VwdHMgZWl0aGVyIGEgY29uZmlnXG4gKiAgb2JqZWN0IChzaW1wbGUgb3ZlcnJpZGVzKSBvciBhIGZ1bmN0aW9uIGZvcm0gKGAoZW52KSA9PiBjb25maWdgLFxuICogIG1hdGNoaW5nIFZpdGUncyBvd24gYGRlZmluZUNvbmZpZ2ApLiBNZXJnZWQgb24gdG9wIG9mIGxpdmVtYXJrJ3NcbiAqICBkZWZhdWx0cyB2aWEgYG1lcmdlQ29uZmlnYCwgc28gcGx1Z2luIGFycmF5cyBjb25jYXRlbmF0ZSwgZGVmaW5lc1xuICogIG1lcmdlLCBldGMuIFVzZSB0aGlzIGZvciBkZXBsb3ltZW50LXRhcmdldCB0d2Vha3MgKGUuZy4gQ0YgV29ya2Vyc1xuICogIHZpYSBgZW52aXJvbm1lbnRzLnNzci5yZXNvbHZlLmNvbmRpdGlvbnM6IFtcIndvcmtlcmRcIiwgLi4uXWApLiAqL1xuZXhwb3J0IHR5cGUgVml0ZU92ZXJyaWRlID1cbiAgfCBWaXRlVXNlckNvbmZpZ1xuICB8ICgoZW52OiBDb25maWdFbnYpID0+IFZpdGVVc2VyQ29uZmlnIHwgUHJvbWlzZTxWaXRlVXNlckNvbmZpZz4pXG5cbi8qKiBTdWItcGF0aCBtb3VudC4gU3RyaW5nIGZvcm0gaXMgdXNlZCBhcy1pczsgZnVuY3Rpb24gZm9ybSBpcyBjYWxsZWRcbiAqICB3aXRoIFZpdGUncyBgY29tbWFuZGAgKGBcInNlcnZlXCJgIGluIGRldiwgYFwiYnVpbGRcImAgaW4gcHJvZHVjdGlvbikgc29cbiAqICB0aGUgdXNlciBjYW4gYnJhbmNoICh0eXBpY2FsIGNhc2U6IHNlcnZlIGF0IGAvYCBpbiBkZXYgYnV0IGF0XG4gKiAgYC9yZXBvL2AgaW4gcHJvZHVjdGlvbiBmb3IgR2l0SHViIFBhZ2VzKS4gVGhlIGJ1aWxkIHBpcGVsaW5lXG4gKiAgcmVzb2x2ZXMgaXQgdG8gYSBzdHJpbmcgYmVmb3JlIGFueSBydW50aW1lIHNlZXMgaXQuICovXG5leHBvcnQgdHlwZSBCYXNlID0gc3RyaW5nIHwgKChjb21tYW5kOiBcImJ1aWxkXCIgfCBcInNlcnZlXCIpID0+IHN0cmluZyB8IHVuZGVmaW5lZClcblxuLyoqXG4gKiBVc2VyLXByb3ZpZGVkIGNvbmZpZ3VyYXRpb24gZm9yIExpdmVtYXJrLlxuICpcbiAqIGB2aXRlYCBhbmQgYGJhc2VgIGFyZSBsaWZ0ZWQgZnJvbSBgdW5rbm93bmAgdG8gdGhlaXIgcHVibGljIHR5cGVzIHNvXG4gKiBgZGVmaW5lQ29uZmlnYCBzaG93cyB0aGUgcmlnaHQgc2hhcGUgaW4gZWRpdG9ycy4gVXNlIHRoaXMgdHlwZSBmb3JcbiAqIHRoZSBpbnB1dCB0byBgZGVmaW5lQ29uZmlnYCDigJQgaXQga2VlcHMgZGVmYXVsdGVkIGZpZWxkcyBvcHRpb25hbFxuICogKGJlY2F1c2Ugem9kIGlucHV0IOKJoCBvdXRwdXQgZm9yIGZpZWxkcyB3aXRoIGAuZGVmYXVsdCgpYCkuXG4gKi9cbmV4cG9ydCB0eXBlIFVzZXJDb25maWcgPSBPbWl0PHouaW5wdXQ8dHlwZW9mIFVzZXJDb25maWc+LCBcInZpdGVcIiB8IFwiYmFzZVwiPiAmIHtcbiAgdml0ZT86IFZpdGVPdmVycmlkZVxuICBiYXNlPzogQmFzZVxufVxuZXhwb3J0IGNvbnN0IFVzZXJDb25maWcgPSB6Lm9iamVjdCh7XG4gIHNpdGU6IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgLy8gU3RvcmVkIGFzIGB1bmtub3duYCBpbiB0aGUgc2NoZW1hICh6b2QgY2FuJ3QgdHlwZSBhIGZ1bmN0aW9uIGFuZFxuICAvLyBzdHJpbmcgdW5pb24gbWVhbmluZ2Z1bGx5KTsgdGhlIHB1YmxpYyBUeXBlU2NyaXB0IHN1cmZhY2UgYWJvdmVcbiAgLy8gbGlmdHMgaXQgYmFjayB0byBgQmFzZWAgZm9yIGVkaXRvciBzdXBwb3J0LiBUaGUgYnVpbGQgcGlwZWxpbmVcbiAgLy8gcmVzb2x2ZXMgaXQgdmlhIGByZXNvbHZlQmFzZSgpYCBiZWZvcmUgYW55IHJ1bnRpbWUgY29uc3VtZXIgc2Vlc1xuICAvLyBpdCwgc28gdGhlIHJ1bnRpbWUgYFdlYnNpdGVDb25maWcuYmFzZWAgaXMgc3RyaWN0bHkgYHN0cmluZ2AuXG4gIGJhc2U6IHoudW5rbm93bigpLm9wdGlvbmFsKCksXG4gIGZhdmljb246IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgbG9nbzogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxuICB0aXRsZTogei5zdHJpbmcoKS5kZWZhdWx0KFwiTGl2ZW1hcmtcIiksXG4gIGRlc2NyaXB0aW9uOiB6LnN0cmluZygpLmRlZmF1bHQoXCJNYXJrZG93biBzaXRlIGdlbmVyYXRvclwiKSxcbiAgaW5jbHVkZTogei51bmlvbihbei5zdHJpbmcoKSwgei5hcnJheSh6LnN0cmluZygpKV0pLmRlZmF1bHQoXCIqKi8qLm1kXCIpLFxuICBleGNsdWRlOiB6LnVuaW9uKFt6LnN0cmluZygpLCB6LmFycmF5KHouc3RyaW5nKCkpXSkub3B0aW9uYWwoKSxcbiAgY29kZVRoZW1lTGlnaHQ6IENvZGVUaGVtZUxpZ2h0LmRlZmF1bHQoXCJjYXRwcHVjY2luLWxhdHRlXCIpLFxuICBjb2RlVGhlbWVEYXJrOiBDb2RlVGhlbWVEYXJrLmRlZmF1bHQoXCJjYXRwcHVjY2luLW1vY2hhXCIpLFxuICBsaW5rczogei5hcnJheShMaW5rKS5vcHRpb25hbCgpLFxuICBzZWN0aW9uczogei5hcnJheShTZWN0aW9uKS5vcHRpb25hbCgpLFxuICBwYXRjaGVzOiB6LmFycmF5KFBhdGNoKS5vcHRpb25hbCgpLFxuICAvLyBTdG9yZWQgYXMgYHVua25vd25gIGluIHRoZSBzY2hlbWEgKHpvZCBjYW4ndCB0eXBlIGEgVml0ZSBVc2VyQ29uZmlnXG4gIC8vIG9yIGEgZnVuY3Rpb24gcmV0dXJuaW5nIG9uZSBtZWFuaW5nZnVsbHkpOyB0aGUgcHVibGljIFR5cGVTY3JpcHRcbiAgLy8gc3VyZmFjZSBhYm92ZSBsaWZ0cyBpdCBiYWNrIHRvIFZpdGVPdmVycmlkZSBmb3IgZWRpdG9yIHN1cHBvcnQuXG4gIHZpdGU6IHoudW5rbm93bigpLm9wdGlvbmFsKCksXG59KVxuXG4vKiogV2Vic2l0ZSBjb25maWd1cmF0aW9uIGluamVjdGVkIGF0IGJ1aWxkIHRpbWUgdmlhIFZpdGUgZGVmaW5lLlxuICogIGBiYXNlYCBpcyBzdHJpY3RseSBhIHN0cmluZyBoZXJlIOKAlCB0aGUgYnVpbGQgcGlwZWxpbmUgaGFzIGFscmVhZHlcbiAqICByZXNvbHZlZCBhbnkgZnVuY3Rpb24gZm9ybSB2aWEgYHJlc29sdmVCYXNlKClgLiAqL1xuZXhwb3J0IHR5cGUgV2Vic2l0ZUNvbmZpZyA9IHouaW5mZXI8dHlwZW9mIFdlYnNpdGVDb25maWc+XG5leHBvcnQgY29uc3QgV2Vic2l0ZUNvbmZpZyA9IFVzZXJDb25maWcucGljayh7XG4gIHRpdGxlOiB0cnVlLFxuICBkZXNjcmlwdGlvbjogdHJ1ZSxcbiAgc2l0ZTogdHJ1ZSxcbiAgZmF2aWNvbjogdHJ1ZSxcbiAgbG9nbzogdHJ1ZSxcbiAgbGlua3M6IHRydWUsXG4gIHNlY3Rpb25zOiB0cnVlLFxufSkuZXh0ZW5kKHtcbiAgYmFzZTogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxufSlcblxuLyoqXG4gKiBTeXN0ZW0tY29tcHV0ZWQgY29uZmlndXJhdGlvbiBkZXJpdmVkIGZyb20gdGhlIGNvbmZpZyBmaWxlIGxvY2F0aW9uLlxuICovXG5leHBvcnQgdHlwZSBTeXN0ZW1Db25maWcgPSB6LmluZmVyPHR5cGVvZiBTeXN0ZW1Db25maWc+XG5leHBvcnQgY29uc3QgU3lzdGVtQ29uZmlnID0gei5vYmplY3Qoe1xuICByb290OiB6LnN0cmluZygpLFxuICBjb25maWdQYXRoOiB6LnN0cmluZygpLFxufSlcblxuLyoqXG4gKiBGdWxseSByZXNvbHZlZCBMaXZlbWFyayBjb25maWd1cmF0aW9uLlxuICpcbiAqIGB2aXRlYCBzdGF5cyBgdW5rbm93bmAgYmVjYXVzZSB0aGF0J3Mgd2hhdCB0aGUgcnVudGltZSBzY2hlbWFcbiAqIHByb2R1Y2VzIOKAlCBjb25zdW1lci1mYWNpbmcgdHlwaW5nIGhhcHBlbnMgdmlhIHtAbGluayBVc2VyQ29uZmlnfSBzb1xuICogYGRlZmluZUNvbmZpZ2Agc2hvd3MgdGhlIHJpZ2h0IHNoYXBlIGluIGVkaXRvcnMuIGBiYXNlYCBpcyBsaWZ0ZWQgdG9cbiAqIGBzdHJpbmcgfCB1bmRlZmluZWRgIGJlY2F1c2UgYGxvYWRDb25maWcoKWAgcmVzb2x2ZXMgYW55IGZ1bmN0aW9uXG4gKiBmb3JtIHZpYSBgcmVzb2x2ZUJhc2UoKWAgYmVmb3JlIHJldHVybmluZy5cbiAqL1xuZXhwb3J0IHR5cGUgQ29uZmlnID0gT21pdDx6LmluZmVyPHR5cGVvZiBDb25maWc+LCBcImJhc2VcIj4gJiB7XG4gIGJhc2U/OiBzdHJpbmdcbn1cbmV4cG9ydCBjb25zdCBDb25maWcgPSBVc2VyQ29uZmlnLm1lcmdlKFN5c3RlbUNvbmZpZylcbiJdfQ==
@@ -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">>;
@@ -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", [ArticleSection, BlogSection, ChangelogSection]));
26
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NvdXJjZS9tb2RlbHMvc2VjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFBO0FBRXZCLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDM0IsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsS0FBSyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDakIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsUUFBUSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO0NBQzFELENBQUMsQ0FBQTtBQUlGLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDO0lBQy9DLElBQUksRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztDQUMzQixDQUFDLENBQUE7QUFJRixNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUM1QyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7Q0FDeEIsQ0FBQyxDQUFBO0FBSUYsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUNqRCxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUM7SUFDNUIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsT0FBTyxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDaEMsQ0FBQyxDQUFBO0FBUUYsTUFBTSxDQUFDLE1BQU0sT0FBTyxHQUFHLENBQUMsQ0FBQyxVQUFVLENBQ2pDLEtBQUssQ0FBQyxFQUFFO0lBQ04sSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDeEUsT0FBTyxLQUFLLENBQUE7SUFDZCxDQUFDO0lBQ0QsTUFBTSxNQUFNLEdBQUcsS0FBZ0MsQ0FBQTtJQUMvQyxPQUFPLE1BQU0sSUFBSSxNQUFNLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLE1BQU0sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUE7QUFDbkUsQ0FBQyxFQUNELENBQUMsQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxjQUFjLEVBQUUsV0FBVyxFQUFFLGdCQUFnQixDQUFDLENBQUMsQ0FDOUUsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHogfSBmcm9tIFwiem9kXCJcblxuY29uc3QgQmFzZVNlY3Rpb24gPSB6Lm9iamVjdCh7XG4gIGljb246IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgdGl0bGU6IHouc3RyaW5nKCksXG4gIHByZWZpeDogei5zdHJpbmcoKSxcbiAgcG9zaXRpb246IHouZW51bShbXCJoZWFkZXJcIiwgXCJzaWRlYmFyXCJdKS5kZWZhdWx0KFwiaGVhZGVyXCIpLFxufSlcblxuLyoqIERlZmF1bHQgc2VjdGlvbjogcmVuZGVycyBhcnRpY2xlcy4gKi9cbmV4cG9ydCB0eXBlIEFydGljbGVTZWN0aW9uID0gei5pbmZlcjx0eXBlb2YgQXJ0aWNsZVNlY3Rpb24+XG5leHBvcnQgY29uc3QgQXJ0aWNsZVNlY3Rpb24gPSBCYXNlU2VjdGlvbi5leHRlbmQoe1xuICB0eXBlOiB6LmxpdGVyYWwoXCJhcnRpY2xlXCIpLFxufSlcblxuLyoqIEJsb2cgc2VjdGlvbjogZGF0ZS1zb3J0ZWQgcG9zdHMgd2l0aCB0YWdzIGFuZCBpbmRleC4gKi9cbmV4cG9ydCB0eXBlIEJsb2dTZWN0aW9uID0gei5pbmZlcjx0eXBlb2YgQmxvZ1NlY3Rpb24+XG5leHBvcnQgY29uc3QgQmxvZ1NlY3Rpb24gPSBCYXNlU2VjdGlvbi5leHRlbmQoe1xuICB0eXBlOiB6LmxpdGVyYWwoXCJibG9nXCIpLFxufSlcblxuLyoqIENoYW5nZWxvZyBzZWN0aW9uOiBzaW5nbGUgZmlsZSBvciBHaXRIdWIgcmVsZWFzZXMsIHNwbGl0IHBlciB2ZXJzaW9uLiAqL1xuZXhwb3J0IHR5cGUgQ2hhbmdlbG9nU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIENoYW5nZWxvZ1NlY3Rpb24+XG5leHBvcnQgY29uc3QgQ2hhbmdlbG9nU2VjdGlvbiA9IEJhc2VTZWN0aW9uLmV4dGVuZCh7XG4gIHR5cGU6IHoubGl0ZXJhbChcImNoYW5nZWxvZ1wiKSxcbiAgc291cmNlOiB6LnN0cmluZygpLFxuICB2ZXJzaW9uOiB6LmJvb2xlYW4oKS5vcHRpb25hbCgpLFxufSlcblxuLyoqXG4gKiBBIHNpdGUgc2VjdGlvbi4gRGlzY3JpbWluYXRlZCBvbiBgdHlwZWAuXG4gKiBXaGVuIGB0eXBlYCBpcyBvbWl0dGVkLCBpdCBkZWZhdWx0cyB0byBcImFydGljbGVcIiBzbyBwbGFpblxuICogYHsgdGl0bGUsIHByZWZpeCB9YCBlbnRyaWVzIGtlZXAgd29ya2luZy5cbiAqL1xuZXhwb3J0IHR5cGUgU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIFNlY3Rpb24+XG5leHBvcnQgY29uc3QgU2VjdGlvbiA9IHoucHJlcHJvY2VzcyhcbiAgaW5wdXQgPT4ge1xuICAgIGlmIChpbnB1dCA9PT0gbnVsbCB8fCB0eXBlb2YgaW5wdXQgIT09IFwib2JqZWN0XCIgfHwgQXJyYXkuaXNBcnJheShpbnB1dCkpIHtcbiAgICAgIHJldHVybiBpbnB1dFxuICAgIH1cbiAgICBjb25zdCByZWNvcmQgPSBpbnB1dCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPlxuICAgIHJldHVybiBcInR5cGVcIiBpbiByZWNvcmQgPyByZWNvcmQgOiB7IC4uLnJlY29yZCwgdHlwZTogXCJhcnRpY2xlXCIgfVxuICB9LFxuICB6LmRpc2NyaW1pbmF0ZWRVbmlvbihcInR5cGVcIiwgW0FydGljbGVTZWN0aW9uLCBCbG9nU2VjdGlvbiwgQ2hhbmdlbG9nU2VjdGlvbl0pLFxuKVxuIl19
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,6 +1,6 @@
1
1
  {
2
2
  "name": "livemark",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Livemark description",
5
5
  "license": "MIT",
6
6
  "author": "Evgeny Karev",
@@ -1,11 +1,9 @@
1
- import type { z } from "zod"
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
- input: z.input<typeof UserConfig>,
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 { z } from "zod"
5
- import type { Config } from "../../models/config.ts"
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: z.input<typeof UserConfig> = {}
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 z.input<typeof UserConfig>) ?? {}
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
- return { ...validated, root: dirname(resolved), configPath: resolved }
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
- articleGroups,
5
+ articleTree,
6
6
  currentSection,
7
- sectionArticleGroups,
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 groups = configSections?.length
35
- ? (sectionArticleGroups.get(section?.prefix ?? "__default__") ??
36
- articleGroups)
37
- : articleGroups
34
+ const tree = configSections?.length
35
+ ? (sectionArticleTrees.get(section?.prefix ?? "__default__") ?? articleTree)
36
+ : articleTree
38
37
 
39
38
  return (
40
- <>
41
- {groups.map((group, index) => (
42
- <SidebarGroup key={group.name ?? `__unnamed-${index}`}>
43
- {group.name && (
44
- <SidebarGroupLabel className="uppercase font-mono text-xs tracking-widest">
45
- {group.name}
46
- </SidebarGroupLabel>
47
- )}
48
- <SidebarGroupContent>
49
- <SidebarMenu>
50
- {group.nodes.map(node => (
51
- <NavNode key={node.path} node={node} currentPath={pathname} />
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 { sectionIcon } from "../helpers/section.ts"
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={sectionIcon(s)} className="size-4" />
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 { ArticleGroup } from "../models/article.ts"
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 sections derived from the path tree, partitioned by `group` */
24
- export const articleGroups = groupArticleTree(
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 all sections (matches sidebar walk) */
30
- export const flatArticles = articleGroups.flatMap(group =>
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
- /** Per-section article groups, keyed by section prefix */
46
- export const sectionArticleGroups = new Map<string, ArticleGroup[]>()
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 (configSections?.length) {
55
- const sectionByPrefix = new Map(configSections.map(s => [s.prefix, s]))
56
- const buckets = partitionBySection(sortedArticles, configSections)
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 groups = groupArticleTree(tree, sorted)
68
- const flat = groups.flatMap(g => flattenArticleTree(g.nodes))
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 (!configSections?.length) return undefined
78
- return matchSection(path, configSections)
71
+ if (!routedSections.length) return undefined
72
+ return matchSection(path, routedSections)
79
73
  }
@@ -1,4 +1,4 @@
1
- import type { ArticleGroup, ArticleNode } from "../models/article.ts"
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
+ }