livemark 0.16.0 → 0.18.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 (49) 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 +13 -11
  5. package/build/components/SearchDialog.js +2 -3
  6. package/build/components/SidebarArticles.js +3 -3
  7. package/build/components/SidebarLinks.js +3 -3
  8. package/build/components/SidebarSections.js +8 -8
  9. package/build/content/article.js +3 -3
  10. package/build/content-collections.js +2 -3
  11. package/build/helpers/article-icon.d.ts +4 -4
  12. package/build/helpers/article-icon.js +8 -36
  13. package/build/helpers/article.d.ts +1 -1
  14. package/build/helpers/article.js +1 -1
  15. package/build/helpers/resolve-base.d.ts +6 -0
  16. package/build/helpers/resolve-base.js +13 -0
  17. package/build/helpers/resolve-base.unit.d.ts +1 -0
  18. package/build/helpers/resolve-base.unit.js +49 -0
  19. package/build/helpers/section.d.ts +7 -3
  20. package/build/helpers/section.js +16 -10
  21. package/build/helpers/section.unit.js +32 -2
  22. package/build/hooks/media.d.ts +1 -1
  23. package/build/hooks/media.js +2 -2
  24. package/build/models/article.d.ts +1 -1
  25. package/build/models/article.js +1 -1
  26. package/build/models/config.d.ts +29 -11
  27. package/build/models/config.js +9 -11
  28. package/build/models/section.d.ts +7 -5
  29. package/build/models/section.js +4 -4
  30. package/package.json +1 -1
  31. package/source/actions/config/define.ts +4 -6
  32. package/source/actions/config/load.ts +26 -6
  33. package/source/components/Layout.tsx +16 -16
  34. package/source/components/SearchDialog.tsx +4 -2
  35. package/source/components/SidebarArticles.tsx +2 -2
  36. package/source/components/SidebarLinks.tsx +2 -2
  37. package/source/components/SidebarSections.tsx +11 -8
  38. package/source/content/article.ts +2 -2
  39. package/source/content-collections.ts +1 -2
  40. package/source/helpers/article-icon.ts +7 -38
  41. package/source/helpers/article.ts +1 -1
  42. package/source/helpers/resolve-base.ts +11 -0
  43. package/source/helpers/resolve-base.unit.ts +61 -0
  44. package/source/helpers/section.ts +13 -12
  45. package/source/helpers/section.unit.ts +43 -1
  46. package/source/hooks/media.ts +1 -1
  47. package/source/models/article.ts +1 -1
  48. package/source/models/config.ts +32 -14
  49. package/source/models/section.ts +7 -5
@@ -7,15 +7,27 @@ import type { ConfigEnv, UserConfig as ViteUserConfig } from "vite";
7
7
  * merge, etc. Use this for deployment-target tweaks (e.g. CF Workers
8
8
  * via `environments.ssr.resolve.conditions: ["workerd", ...]`). */
9
9
  export type ViteOverride = ViteUserConfig | ((env: ConfigEnv) => ViteUserConfig | Promise<ViteUserConfig>);
10
+ /** Sub-path mount. String form is used as-is; function form is called
11
+ * with Vite's `command` (`"serve"` in dev, `"build"` in production) so
12
+ * the user can branch (typical case: serve at `/` in dev but at
13
+ * `/repo/` in production for GitHub Pages). The build pipeline
14
+ * resolves it to a string before any runtime sees it. */
15
+ export type Base = string | ((command: "build" | "serve") => string | undefined);
10
16
  /**
11
17
  * User-provided configuration for Livemark.
18
+ *
19
+ * `vite` and `base` are lifted from `unknown` to their public types so
20
+ * `defineConfig` shows the right shape in editors. Use this type for
21
+ * the input to `defineConfig` — it keeps defaulted fields optional
22
+ * (because zod input ≠ output for fields with `.default()`).
12
23
  */
13
- export type UserConfig = Omit<z.infer<typeof UserConfig>, "vite"> & {
24
+ export type UserConfig = Omit<z.input<typeof UserConfig>, "vite" | "base"> & {
14
25
  vite?: ViteOverride;
26
+ base?: Base;
15
27
  };
16
28
  export declare const UserConfig: z.ZodObject<{
17
29
  site: z.ZodOptional<z.ZodString>;
18
- base: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
30
+ base: z.ZodOptional<z.ZodUnknown>;
19
31
  favicon: z.ZodOptional<z.ZodString>;
20
32
  logo: z.ZodOptional<z.ZodString>;
21
33
  title: z.ZodDefault<z.ZodString>;
@@ -137,7 +149,7 @@ export declare const UserConfig: z.ZodObject<{
137
149
  header: "header";
138
150
  sidebar: "sidebar";
139
151
  }>>;
140
- type: z.ZodLiteral<"external">;
152
+ type: z.ZodLiteral<"custom">;
141
153
  url: z.ZodString;
142
154
  }, z.core.$strip>], "type">>>>;
143
155
  patches: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -164,11 +176,12 @@ export declare const UserConfig: z.ZodObject<{
164
176
  }, z.core.$strip>>>;
165
177
  vite: z.ZodOptional<z.ZodUnknown>;
166
178
  }, z.core.$strip>;
167
- /** Website configuration injected at build time via Vite define */
179
+ /** Website configuration injected at build time via Vite define.
180
+ * `base` is strictly a string here — the build pipeline has already
181
+ * resolved any function form via `resolveBase()`. */
168
182
  export type WebsiteConfig = z.infer<typeof WebsiteConfig>;
169
183
  export declare const WebsiteConfig: z.ZodObject<{
170
184
  site: z.ZodOptional<z.ZodString>;
171
- base: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
172
185
  favicon: z.ZodOptional<z.ZodString>;
173
186
  logo: z.ZodOptional<z.ZodString>;
174
187
  title: z.ZodDefault<z.ZodString>;
@@ -219,9 +232,10 @@ export declare const WebsiteConfig: z.ZodObject<{
219
232
  header: "header";
220
233
  sidebar: "sidebar";
221
234
  }>>;
222
- type: z.ZodLiteral<"external">;
235
+ type: z.ZodLiteral<"custom">;
223
236
  url: z.ZodString;
224
237
  }, z.core.$strip>], "type">>>>;
238
+ base: z.ZodOptional<z.ZodString>;
225
239
  }, z.core.$strip>;
226
240
  /**
227
241
  * System-computed configuration derived from the config file location.
@@ -234,14 +248,18 @@ export declare const SystemConfig: z.ZodObject<{
234
248
  /**
235
249
  * Fully resolved Livemark configuration.
236
250
  *
237
- * `vite` stays `unknown` here because that's what the runtime schema
251
+ * `vite` stays `unknown` because that's what the runtime schema
238
252
  * produces — consumer-facing typing happens via {@link UserConfig} so
239
- * `defineConfig` shows the right shape in editors.
253
+ * `defineConfig` shows the right shape in editors. `base` is lifted to
254
+ * `string | undefined` because `loadConfig()` resolves any function
255
+ * form via `resolveBase()` before returning.
240
256
  */
241
- export type Config = z.infer<typeof Config>;
257
+ export type Config = Omit<z.infer<typeof Config>, "base"> & {
258
+ base?: string;
259
+ };
242
260
  export declare const Config: z.ZodObject<{
243
261
  site: z.ZodOptional<z.ZodString>;
244
- base: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
262
+ base: z.ZodOptional<z.ZodUnknown>;
245
263
  favicon: z.ZodOptional<z.ZodString>;
246
264
  logo: z.ZodOptional<z.ZodString>;
247
265
  title: z.ZodDefault<z.ZodString>;
@@ -363,7 +381,7 @@ export declare const Config: z.ZodObject<{
363
381
  header: "header";
364
382
  sidebar: "sidebar";
365
383
  }>>;
366
- type: z.ZodLiteral<"external">;
384
+ type: z.ZodLiteral<"custom">;
367
385
  url: z.ZodString;
368
386
  }, z.core.$strip>], "type">>>>;
369
387
  patches: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -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==
@@ -37,16 +37,18 @@ export declare const ChangelogSection: z.ZodObject<{
37
37
  source: z.ZodString;
38
38
  version: z.ZodOptional<z.ZodBoolean>;
39
39
  }, z.core.$strip>;
40
- /** External section: renders as a link to an arbitrary URL. */
41
- export type ExternalSection = z.infer<typeof ExternalSection>;
42
- export declare const ExternalSection: z.ZodObject<{
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<{
43
45
  icon: z.ZodOptional<z.ZodString>;
44
46
  title: z.ZodString;
45
47
  position: z.ZodDefault<z.ZodEnum<{
46
48
  header: "header";
47
49
  sidebar: "sidebar";
48
50
  }>>;
49
- type: z.ZodLiteral<"external">;
51
+ type: z.ZodLiteral<"custom">;
50
52
  url: z.ZodString;
51
53
  }, z.core.$strip>;
52
54
  /**
@@ -91,6 +93,6 @@ export declare const Section: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodD
91
93
  header: "header";
92
94
  sidebar: "sidebar";
93
95
  }>>;
94
- type: z.ZodLiteral<"external">;
96
+ type: z.ZodLiteral<"custom">;
95
97
  url: z.ZodString;
96
98
  }, z.core.$strip>], "type">>;
@@ -18,8 +18,8 @@ export const ChangelogSection = BaseSection.extend({
18
18
  source: z.string(),
19
19
  version: z.boolean().optional(),
20
20
  });
21
- export const ExternalSection = BaseSection.extend({
22
- type: z.literal("external"),
21
+ export const CustomSection = BaseSection.extend({
22
+ type: z.literal("custom"),
23
23
  url: z.string(),
24
24
  });
25
25
  export const Section = z.preprocess(input => {
@@ -32,6 +32,6 @@ export const Section = z.preprocess(input => {
32
32
  ArticleSection,
33
33
  BlogSection,
34
34
  ChangelogSection,
35
- ExternalSection,
35
+ CustomSection,
36
36
  ]));
37
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NvdXJjZS9tb2RlbHMvc2VjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFBO0FBRXZCLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDM0IsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsS0FBSyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDakIsUUFBUSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO0NBQzFELENBQUMsQ0FBQTtBQUlGLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDO0lBQy9DLElBQUksRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUMxQixNQUFNLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRTtDQUNuQixDQUFDLENBQUE7QUFJRixNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUM1QyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7SUFDdkIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7Q0FDbkIsQ0FBQyxDQUFBO0FBSUYsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUNqRCxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUM7SUFDNUIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsT0FBTyxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDaEMsQ0FBQyxDQUFBO0FBSUYsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUM7SUFDaEQsSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDO0lBQzNCLEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0NBQ2hCLENBQUMsQ0FBQTtBQVFGLE1BQU0sQ0FBQyxNQUFNLE9BQU8sR0FBRyxDQUFDLENBQUMsVUFBVSxDQUNqQyxLQUFLLENBQUMsRUFBRTtJQUNOLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hFLE9BQU8sS0FBSyxDQUFBO0lBQ2QsQ0FBQztJQUNELE1BQU0sTUFBTSxHQUFHLEtBQWdDLENBQUE7SUFDL0MsT0FBTyxNQUFNLElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFBO0FBQ25FLENBQUMsRUFDRCxDQUFDLENBQUMsa0JBQWtCLENBQUMsTUFBTSxFQUFFO0lBQzNCLGNBQWM7SUFDZCxXQUFXO0lBQ1gsZ0JBQWdCO0lBQ2hCLGVBQWU7Q0FDaEIsQ0FBQyxDQUNILENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyB6IH0gZnJvbSBcInpvZFwiXG5cbmNvbnN0IEJhc2VTZWN0aW9uID0gei5vYmplY3Qoe1xuICBpY29uOiB6LnN0cmluZygpLm9wdGlvbmFsKCksXG4gIHRpdGxlOiB6LnN0cmluZygpLFxuICBwb3NpdGlvbjogei5lbnVtKFtcImhlYWRlclwiLCBcInNpZGViYXJcIl0pLmRlZmF1bHQoXCJoZWFkZXJcIiksXG59KVxuXG4vKiogRGVmYXVsdCBzZWN0aW9uOiByZW5kZXJzIGFydGljbGVzLiAqL1xuZXhwb3J0IHR5cGUgQXJ0aWNsZVNlY3Rpb24gPSB6LmluZmVyPHR5cGVvZiBBcnRpY2xlU2VjdGlvbj5cbmV4cG9ydCBjb25zdCBBcnRpY2xlU2VjdGlvbiA9IEJhc2VTZWN0aW9uLmV4dGVuZCh7XG4gIHR5cGU6IHoubGl0ZXJhbChcImFydGljbGVcIiksXG4gIHByZWZpeDogei5zdHJpbmcoKSxcbn0pXG5cbi8qKiBCbG9nIHNlY3Rpb246IGRhdGUtc29ydGVkIHBvc3RzIHdpdGggdGFncyBhbmQgaW5kZXguICovXG5leHBvcnQgdHlwZSBCbG9nU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIEJsb2dTZWN0aW9uPlxuZXhwb3J0IGNvbnN0IEJsb2dTZWN0aW9uID0gQmFzZVNlY3Rpb24uZXh0ZW5kKHtcbiAgdHlwZTogei5saXRlcmFsKFwiYmxvZ1wiKSxcbiAgcHJlZml4OiB6LnN0cmluZygpLFxufSlcblxuLyoqIENoYW5nZWxvZyBzZWN0aW9uOiBzaW5nbGUgZmlsZSBvciBHaXRIdWIgcmVsZWFzZXMsIHNwbGl0IHBlciB2ZXJzaW9uLiAqL1xuZXhwb3J0IHR5cGUgQ2hhbmdlbG9nU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIENoYW5nZWxvZ1NlY3Rpb24+XG5leHBvcnQgY29uc3QgQ2hhbmdlbG9nU2VjdGlvbiA9IEJhc2VTZWN0aW9uLmV4dGVuZCh7XG4gIHR5cGU6IHoubGl0ZXJhbChcImNoYW5nZWxvZ1wiKSxcbiAgcHJlZml4OiB6LnN0cmluZygpLFxuICBzb3VyY2U6IHouc3RyaW5nKCksXG4gIHZlcnNpb246IHouYm9vbGVhbigpLm9wdGlvbmFsKCksXG59KVxuXG4vKiogRXh0ZXJuYWwgc2VjdGlvbjogcmVuZGVycyBhcyBhIGxpbmsgdG8gYW4gYXJiaXRyYXJ5IFVSTC4gKi9cbmV4cG9ydCB0eXBlIEV4dGVybmFsU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIEV4dGVybmFsU2VjdGlvbj5cbmV4cG9ydCBjb25zdCBFeHRlcm5hbFNlY3Rpb24gPSBCYXNlU2VjdGlvbi5leHRlbmQoe1xuICB0eXBlOiB6LmxpdGVyYWwoXCJleHRlcm5hbFwiKSxcbiAgdXJsOiB6LnN0cmluZygpLFxufSlcblxuLyoqXG4gKiBBIHNpdGUgc2VjdGlvbi4gRGlzY3JpbWluYXRlZCBvbiBgdHlwZWAuXG4gKiBXaGVuIGB0eXBlYCBpcyBvbWl0dGVkLCBpdCBkZWZhdWx0cyB0byBcImFydGljbGVcIiBzbyBwbGFpblxuICogYHsgdGl0bGUsIHByZWZpeCB9YCBlbnRyaWVzIGtlZXAgd29ya2luZy5cbiAqL1xuZXhwb3J0IHR5cGUgU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIFNlY3Rpb24+XG5leHBvcnQgY29uc3QgU2VjdGlvbiA9IHoucHJlcHJvY2VzcyhcbiAgaW5wdXQgPT4ge1xuICAgIGlmIChpbnB1dCA9PT0gbnVsbCB8fCB0eXBlb2YgaW5wdXQgIT09IFwib2JqZWN0XCIgfHwgQXJyYXkuaXNBcnJheShpbnB1dCkpIHtcbiAgICAgIHJldHVybiBpbnB1dFxuICAgIH1cbiAgICBjb25zdCByZWNvcmQgPSBpbnB1dCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPlxuICAgIHJldHVybiBcInR5cGVcIiBpbiByZWNvcmQgPyByZWNvcmQgOiB7IC4uLnJlY29yZCwgdHlwZTogXCJhcnRpY2xlXCIgfVxuICB9LFxuICB6LmRpc2NyaW1pbmF0ZWRVbmlvbihcInR5cGVcIiwgW1xuICAgIEFydGljbGVTZWN0aW9uLFxuICAgIEJsb2dTZWN0aW9uLFxuICAgIENoYW5nZWxvZ1NlY3Rpb24sXG4gICAgRXh0ZXJuYWxTZWN0aW9uLFxuICBdKSxcbilcbiJdfQ==
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.16.0",
3
+ "version": "0.18.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 } from "../helpers/section.ts"
13
13
  import {
14
14
  SidebarInset,
15
15
  SidebarProvider,
@@ -47,14 +47,14 @@ export function Layout(props: {
47
47
  const header = (
48
48
  <header className="sticky top-0 z-20 flex h-16 shrink-0 items-center border-b bg-background">
49
49
  <div
50
- className={`flex items-center self-stretch pl-4 ${props.withSidebar ? "pr-4" : "md:pr-20"}`}
50
+ className={`flex items-center self-stretch pl-4 ${props.withSidebar ? "pr-4" : "lg:pr-20"}`}
51
51
  >
52
52
  {props.withSidebar ? (
53
53
  <>
54
- <div className="hidden md:flex">
54
+ <div className="hidden lg:flex">
55
55
  <SidebarTrigger />
56
56
  </div>
57
- <Link to="/" className="md:hidden flex items-center gap-2">
57
+ <Link to="/" className="lg:hidden flex items-center gap-2">
58
58
  <SiteTitle />
59
59
  </Link>
60
60
  </>
@@ -64,22 +64,25 @@ export function Layout(props: {
64
64
  </Link>
65
65
  )}
66
66
  </div>
67
- <div className="hidden md:flex flex-1 items-center gap-8 self-stretch px-6 text-sm">
67
+ <div className="hidden lg:flex flex-1 items-center gap-8 self-stretch px-6 text-sm">
68
68
  {sections?.length ? (
69
69
  sections
70
70
  .filter(section => section.position === "header")
71
71
  .map(section => {
72
- if (section.type === "external") {
72
+ const icon = section.icon
73
+ if (section.type === "custom") {
74
+ const isActive = customSectionActive(section.url, pathname)
73
75
  return (
74
76
  <a
75
77
  key={section.url}
76
78
  href={prefixUrl(section.url, config.base)}
77
- className="inline-flex items-center gap-1.5 text-foreground opacity-80 hover:opacity-100 transition-opacity"
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
+ }`}
78
84
  >
79
- <DynamicIcon
80
- name={sectionIcon(section)}
81
- className="size-3.5"
82
- />
85
+ {icon && <DynamicIcon name={icon} className="size-3.5" />}
83
86
  <span>{section.title}</span>
84
87
  </a>
85
88
  )
@@ -107,10 +110,7 @@ export function Layout(props: {
107
110
  : "opacity-80 hover:opacity-100 transition-opacity"
108
111
  }`}
109
112
  >
110
- <DynamicIcon
111
- name={sectionIcon(section)}
112
- className="size-3.5"
113
- />
113
+ {icon && <DynamicIcon name={icon} className="size-3.5" />}
114
114
  <span>
115
115
  {section.title}
116
116
  {latestVersion && (
@@ -151,7 +151,7 @@ export function Layout(props: {
151
151
  ))}
152
152
  <Banner />
153
153
  </div>
154
- <div className="md:hidden ml-auto pr-2">
154
+ <div className="lg:hidden ml-auto pr-2">
155
155
  <SidebarTrigger />
156
156
  </div>
157
157
  </header>
@@ -15,7 +15,7 @@ import {
15
15
  interface SearchResult {
16
16
  title: string
17
17
  path: string
18
- icon: string
18
+ icon?: string
19
19
  snippet: string
20
20
  group: string
21
21
  }
@@ -157,7 +157,9 @@ export function SearchDialog(props: {
157
157
  onSelect={() => handleSelect(result.path)}
158
158
  className="py-2.5"
159
159
  >
160
- <Icon className="size-5 shrink-0 mt-0.5 self-start opacity-60" />
160
+ {Icon && (
161
+ <Icon className="size-5 shrink-0 mt-0.5 self-start opacity-60" />
162
+ )}
161
163
  <div className="flex flex-col gap-0.5 min-w-0">
162
164
  <span className="text-base font-medium">
163
165
  {result.title}
@@ -83,7 +83,7 @@ function NavNode(props: {
83
83
  className={active ? "" : "opacity-75"}
84
84
  render={link}
85
85
  >
86
- {!isSub && <Icon className="size-4" />}
86
+ {!isSub && Icon && <Icon className="size-4" />}
87
87
  {label}
88
88
  </Button>
89
89
  </Item>
@@ -98,7 +98,7 @@ function NavNode(props: {
98
98
  className={active ? "" : "opacity-75"}
99
99
  render={link}
100
100
  >
101
- {!isSub && <Icon className="size-4" />}
101
+ {!isSub && Icon && <Icon className="size-4" />}
102
102
  {label}
103
103
  </Button>
104
104
  <CollapsibleTrigger
@@ -27,7 +27,7 @@ export function SidebarLinks() {
27
27
  const hasDesktopItems = links.some(l => l.position === "sidebar")
28
28
 
29
29
  return (
30
- <SidebarGroup className={hasDesktopItems ? undefined : "md:hidden"}>
30
+ <SidebarGroup className={hasDesktopItems ? undefined : "lg:hidden"}>
31
31
  <SidebarGroupLabel className="uppercase font-mono text-xs tracking-widest">
32
32
  Links
33
33
  </SidebarGroupLabel>
@@ -38,7 +38,7 @@ export function SidebarLinks() {
38
38
  return (
39
39
  <SidebarMenuItem
40
40
  key={link.url}
41
- className={mobileOnly ? "md:hidden" : undefined}
41
+ className={mobileOnly ? "lg:hidden" : undefined}
42
42
  >
43
43
  <SidebarMenuButton
44
44
  className="text-muted-foreground"
@@ -3,7 +3,7 @@ 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
5
  import { prefixUrl } from "../helpers/prefix-url.ts"
6
- import { sectionIcon } from "../helpers/section.ts"
6
+ import { customSectionActive } from "../helpers/section.ts"
7
7
  import {
8
8
  SidebarGroup,
9
9
  SidebarGroupContent,
@@ -32,7 +32,7 @@ export function SidebarSections() {
32
32
  const hasDesktopItems = sections.some(s => s.position === "sidebar")
33
33
 
34
34
  return (
35
- <SidebarGroup className={hasDesktopItems ? undefined : "md:hidden"}>
35
+ <SidebarGroup className={hasDesktopItems ? undefined : "lg:hidden"}>
36
36
  <SidebarGroupLabel className="uppercase font-mono text-xs tracking-widest">
37
37
  Sections
38
38
  </SidebarGroupLabel>
@@ -40,17 +40,20 @@ export function SidebarSections() {
40
40
  <SidebarMenu>
41
41
  {sections.map(s => {
42
42
  const mobileOnly = s.position !== "sidebar"
43
- if (s.type === "external") {
43
+ const icon = s.icon
44
+ if (s.type === "custom") {
45
+ const active = customSectionActive(s.url, pathname)
44
46
  return (
45
47
  <SidebarMenuItem
46
48
  key={s.url}
47
- className={mobileOnly ? "md:hidden" : undefined}
49
+ className={mobileOnly ? "lg:hidden" : undefined}
48
50
  >
49
51
  <SidebarMenuButton
50
- className="opacity-75"
52
+ isActive={active}
53
+ className={active ? "" : "opacity-75"}
51
54
  render={<a href={prefixUrl(s.url, config.base)} />}
52
55
  >
53
- <DynamicIcon name={sectionIcon(s)} className="size-4" />
56
+ {icon && <DynamicIcon name={icon} className="size-4" />}
54
57
  <span>{s.title}</span>
55
58
  </SidebarMenuButton>
56
59
  </SidebarMenuItem>
@@ -64,7 +67,7 @@ export function SidebarSections() {
64
67
  return (
65
68
  <SidebarMenuItem
66
69
  key={s.prefix}
67
- className={mobileOnly ? "md:hidden" : undefined}
70
+ className={mobileOnly ? "lg:hidden" : undefined}
68
71
  >
69
72
  <SidebarMenuButton
70
73
  isActive={active}
@@ -76,7 +79,7 @@ export function SidebarSections() {
76
79
  />
77
80
  }
78
81
  >
79
- <DynamicIcon name={sectionIcon(s)} className="size-4" />
82
+ {icon && <DynamicIcon name={icon} className="size-4" />}
80
83
  <span>{s.title}</span>
81
84
  </SidebarMenuButton>
82
85
  </SidebarMenuItem>
@@ -33,9 +33,9 @@ function orderKey(order?: number) {
33
33
 
34
34
  const configSections = config.sections
35
35
 
36
- /** Route-bound sections (everything except `external`). External sections
36
+ /** Route-bound sections (everything except `custom`). Custom sections
37
37
  * are pure links — they have no `prefix` and don't claim a URL space. */
38
- const routedSections = configSections?.filter(s => s.type !== "external") ?? []
38
+ const routedSections = configSections?.filter(s => s.type !== "custom") ?? []
39
39
 
40
40
  /** Per-section article tree, keyed by section prefix */
41
41
  export const sectionArticleTrees = new Map<string, ArticleNode[]>()
@@ -16,7 +16,6 @@ import remarkMath from "remark-math"
16
16
  import { loadConfig } from "./actions/config/load.ts"
17
17
  import { buildChangelog, cacheIncludeGlob } from "./actions/changelog/build.ts"
18
18
  import { Article } from "./models/article.ts"
19
- import { pickDefaultIcon } from "./helpers/article-icon.ts"
20
19
  import slugify from "@sindresorhus/slugify"
21
20
  import remarkCustomHeadingId from "./plugins/remark-custom-heading-id.ts"
22
21
  import { remarkCallout } from "./plugins/remark-callout.ts"
@@ -128,7 +127,7 @@ const articles = defineCollection({
128
127
  const filePath = resolve(config.root, doc._meta.filePath)
129
128
  const content = resolveIncludes(doc.content, filePath)
130
129
  const title = doc.title ?? extractTitle(content, doc._meta.path)
131
- const icon = doc.icon ?? pickDefaultIcon(doc._meta.path)
130
+ const icon = doc.icon
132
131
  const path = doc.path ?? toPath(doc._meta.filePath)
133
132
  // The article's h1 is rendered from `title` by the layout, so strip any
134
133
  // leading h1 in the body to avoid two h1s side-by-side (especially when
@@ -1,43 +1,12 @@
1
- import { FileText, icons } from "lucide-react"
1
+ import { icons } from "lucide-react"
2
2
 
3
- /** Resolve a Lucide icon by kebab-case name, falling back to FileText. */
4
- export function resolveArticleIcon(name: string) {
3
+ /** Resolve a Lucide icon by kebab-case name. Returns undefined when no
4
+ * name is given or the name doesn't match a known icon — articles
5
+ * without an explicit `icon` frontmatter render with no icon at all. */
6
+ export function resolveArticleIcon(name: string | undefined) {
7
+ if (!name) return undefined
5
8
  const key = toPascalCase(name)
6
- return key in icons ? icons[key as keyof typeof icons] : FileText
7
- }
8
-
9
- /** Curated set of icons used as default fallbacks for articles without one. */
10
- const defaultIconNames = [
11
- "binary",
12
- "blocks",
13
- "brain-circuit",
14
- "bug",
15
- "cloud",
16
- "code",
17
- "cpu",
18
- "database",
19
- "file-code",
20
- "git-branch",
21
- "globe",
22
- "hard-drive",
23
- "layers",
24
- "network",
25
- "rocket",
26
- "server",
27
- "shield",
28
- "sliders-horizontal",
29
- "terminal",
30
- "webhook",
31
- "zap",
32
- ]
33
-
34
- /** Deterministic icon name based on article path */
35
- export function pickDefaultIcon(path: string) {
36
- let hash = 0
37
- for (let i = 0; i < path.length; i++) {
38
- hash = (hash * 31 + path.charCodeAt(i)) | 0
39
- }
40
- return defaultIconNames[Math.abs(hash) % defaultIconNames.length]!
9
+ return key in icons ? icons[key as keyof typeof icons] : undefined
41
10
  }
42
11
 
43
12
  function toPascalCase(name: string) {
@@ -6,7 +6,7 @@ export function buildArticleTree(
6
6
  path: string
7
7
  title: string
8
8
  label?: string
9
- icon: string
9
+ icon?: string
10
10
  }[],
11
11
  ) {
12
12
  const nodeMap = new Map<string, ArticleNode>()
@@ -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
+ }