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.
- package/build/actions/config/define.d.ts +67 -3
- package/build/actions/config/define.js +3 -3
- package/build/actions/config/load.js +23 -2
- package/build/components/Layout.js +13 -11
- package/build/components/SearchDialog.js +2 -3
- package/build/components/SidebarArticles.js +3 -3
- package/build/components/SidebarLinks.js +3 -3
- package/build/components/SidebarSections.js +8 -8
- package/build/content/article.js +3 -3
- package/build/content-collections.js +2 -3
- package/build/helpers/article-icon.d.ts +4 -4
- package/build/helpers/article-icon.js +8 -36
- package/build/helpers/article.d.ts +1 -1
- package/build/helpers/article.js +1 -1
- package/build/helpers/resolve-base.d.ts +6 -0
- package/build/helpers/resolve-base.js +13 -0
- package/build/helpers/resolve-base.unit.d.ts +1 -0
- package/build/helpers/resolve-base.unit.js +49 -0
- package/build/helpers/section.d.ts +7 -3
- package/build/helpers/section.js +16 -10
- package/build/helpers/section.unit.js +32 -2
- package/build/hooks/media.d.ts +1 -1
- package/build/hooks/media.js +2 -2
- package/build/models/article.d.ts +1 -1
- package/build/models/article.js +1 -1
- package/build/models/config.d.ts +29 -11
- package/build/models/config.js +9 -11
- package/build/models/section.d.ts +7 -5
- package/build/models/section.js +4 -4
- package/package.json +1 -1
- package/source/actions/config/define.ts +4 -6
- package/source/actions/config/load.ts +26 -6
- package/source/components/Layout.tsx +16 -16
- package/source/components/SearchDialog.tsx +4 -2
- package/source/components/SidebarArticles.tsx +2 -2
- package/source/components/SidebarLinks.tsx +2 -2
- package/source/components/SidebarSections.tsx +11 -8
- package/source/content/article.ts +2 -2
- package/source/content-collections.ts +1 -2
- package/source/helpers/article-icon.ts +7 -38
- package/source/helpers/article.ts +1 -1
- package/source/helpers/resolve-base.ts +11 -0
- package/source/helpers/resolve-base.unit.ts +61 -0
- package/source/helpers/section.ts +13 -12
- package/source/helpers/section.unit.ts +43 -1
- package/source/hooks/media.ts +1 -1
- package/source/models/article.ts +1 -1
- package/source/models/config.ts +32 -14
- package/source/models/section.ts +7 -5
package/build/models/config.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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<"
|
|
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<"
|
|
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`
|
|
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.
|
|
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<"
|
|
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<{
|
package/build/models/config.js
CHANGED
|
@@ -5,15 +5,12 @@ import { Section } from "./section.js";
|
|
|
5
5
|
import { CodeThemeDark, CodeThemeLight } from "./theme.js";
|
|
6
6
|
export const UserConfig = z.object({
|
|
7
7
|
site: z.string().optional(),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const trimmed = v.replace(/^\/|\/$/g, "");
|
|
15
|
-
return trimmed ? `/${trimmed}` : undefined;
|
|
16
|
-
}),
|
|
8
|
+
// Stored as `unknown` in the schema (zod can't type a function and
|
|
9
|
+
// string union meaningfully); the public TypeScript surface above
|
|
10
|
+
// lifts it back to `Base` for editor support. The build pipeline
|
|
11
|
+
// resolves it via `resolveBase()` before any runtime consumer sees
|
|
12
|
+
// it, so the runtime `WebsiteConfig.base` is strictly `string`.
|
|
13
|
+
base: z.unknown().optional(),
|
|
17
14
|
favicon: z.string().optional(),
|
|
18
15
|
logo: z.string().optional(),
|
|
19
16
|
title: z.string().default("Livemark"),
|
|
@@ -34,15 +31,16 @@ export const WebsiteConfig = UserConfig.pick({
|
|
|
34
31
|
title: true,
|
|
35
32
|
description: true,
|
|
36
33
|
site: true,
|
|
37
|
-
base: true,
|
|
38
34
|
favicon: true,
|
|
39
35
|
logo: true,
|
|
40
36
|
links: true,
|
|
41
37
|
sections: true,
|
|
38
|
+
}).extend({
|
|
39
|
+
base: z.string().optional(),
|
|
42
40
|
});
|
|
43
41
|
export const SystemConfig = z.object({
|
|
44
42
|
root: z.string(),
|
|
45
43
|
configPath: z.string(),
|
|
46
44
|
});
|
|
47
45
|
export const Config = UserConfig.merge(SystemConfig);
|
|
48
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
46
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc291cmNlL21vZGVscy9jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQTtBQUV2QixPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBQ2hDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDbEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUN0QyxPQUFPLEVBQUUsYUFBYSxFQUFFLGNBQWMsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQStCMUQsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDakMsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsbUVBQW1FO0lBQ25FLGtFQUFrRTtJQUNsRSxpRUFBaUU7SUFDakUsbUVBQW1FO0lBQ25FLGdFQUFnRTtJQUNoRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUM1QixPQUFPLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUM5QixJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUMzQixLQUFLLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7SUFDckMsV0FBVyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMseUJBQXlCLENBQUM7SUFDMUQsT0FBTyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUN0RSxPQUFPLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDOUQsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFDMUQsYUFBYSxFQUFFLGFBQWEsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFDeEQsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQy9CLFFBQVEsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLFFBQVEsRUFBRTtJQUNyQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDbEMsc0VBQXNFO0lBQ3RFLG1FQUFtRTtJQUNuRSxrRUFBa0U7SUFDbEUsSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDN0IsQ0FBQyxDQUFBO0FBTUYsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUM7SUFDM0MsS0FBSyxFQUFFLElBQUk7SUFDWCxXQUFXLEVBQUUsSUFBSTtJQUNqQixJQUFJLEVBQUUsSUFBSTtJQUNWLE9BQU8sRUFBRSxJQUFJO0lBQ2IsSUFBSSxFQUFFLElBQUk7SUFDVixLQUFLLEVBQUUsSUFBSTtJQUNYLFFBQVEsRUFBRSxJQUFJO0NBQ2YsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUNSLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQzVCLENBQUMsQ0FBQTtBQU1GLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ25DLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0lBQ2hCLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0NBQ3ZCLENBQUMsQ0FBQTtBQWNGLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgeiB9IGZyb20gXCJ6b2RcIlxuaW1wb3J0IHR5cGUgeyBDb25maWdFbnYsIFVzZXJDb25maWcgYXMgVml0ZVVzZXJDb25maWcgfSBmcm9tIFwidml0ZVwiXG5pbXBvcnQgeyBMaW5rIH0gZnJvbSBcIi4vbGluay50c1wiXG5pbXBvcnQgeyBQYXRjaCB9IGZyb20gXCIuL3BhdGNoLnRzXCJcbmltcG9ydCB7IFNlY3Rpb24gfSBmcm9tIFwiLi9zZWN0aW9uLnRzXCJcbmltcG9ydCB7IENvZGVUaGVtZURhcmssIENvZGVUaGVtZUxpZ2h0IH0gZnJvbSBcIi4vdGhlbWUudHNcIlxuXG4vKiogRXNjYXBlIGhhdGNoIGZvciBhcmJpdHJhcnkgVml0ZSBjb25maWcg4oCUIGFjY2VwdHMgZWl0aGVyIGEgY29uZmlnXG4gKiAgb2JqZWN0IChzaW1wbGUgb3ZlcnJpZGVzKSBvciBhIGZ1bmN0aW9uIGZvcm0gKGAoZW52KSA9PiBjb25maWdgLFxuICogIG1hdGNoaW5nIFZpdGUncyBvd24gYGRlZmluZUNvbmZpZ2ApLiBNZXJnZWQgb24gdG9wIG9mIGxpdmVtYXJrJ3NcbiAqICBkZWZhdWx0cyB2aWEgYG1lcmdlQ29uZmlnYCwgc28gcGx1Z2luIGFycmF5cyBjb25jYXRlbmF0ZSwgZGVmaW5lc1xuICogIG1lcmdlLCBldGMuIFVzZSB0aGlzIGZvciBkZXBsb3ltZW50LXRhcmdldCB0d2Vha3MgKGUuZy4gQ0YgV29ya2Vyc1xuICogIHZpYSBgZW52aXJvbm1lbnRzLnNzci5yZXNvbHZlLmNvbmRpdGlvbnM6IFtcIndvcmtlcmRcIiwgLi4uXWApLiAqL1xuZXhwb3J0IHR5cGUgVml0ZU92ZXJyaWRlID1cbiAgfCBWaXRlVXNlckNvbmZpZ1xuICB8ICgoZW52OiBDb25maWdFbnYpID0+IFZpdGVVc2VyQ29uZmlnIHwgUHJvbWlzZTxWaXRlVXNlckNvbmZpZz4pXG5cbi8qKiBTdWItcGF0aCBtb3VudC4gU3RyaW5nIGZvcm0gaXMgdXNlZCBhcy1pczsgZnVuY3Rpb24gZm9ybSBpcyBjYWxsZWRcbiAqICB3aXRoIFZpdGUncyBgY29tbWFuZGAgKGBcInNlcnZlXCJgIGluIGRldiwgYFwiYnVpbGRcImAgaW4gcHJvZHVjdGlvbikgc29cbiAqICB0aGUgdXNlciBjYW4gYnJhbmNoICh0eXBpY2FsIGNhc2U6IHNlcnZlIGF0IGAvYCBpbiBkZXYgYnV0IGF0XG4gKiAgYC9yZXBvL2AgaW4gcHJvZHVjdGlvbiBmb3IgR2l0SHViIFBhZ2VzKS4gVGhlIGJ1aWxkIHBpcGVsaW5lXG4gKiAgcmVzb2x2ZXMgaXQgdG8gYSBzdHJpbmcgYmVmb3JlIGFueSBydW50aW1lIHNlZXMgaXQuICovXG5leHBvcnQgdHlwZSBCYXNlID0gc3RyaW5nIHwgKChjb21tYW5kOiBcImJ1aWxkXCIgfCBcInNlcnZlXCIpID0+IHN0cmluZyB8IHVuZGVmaW5lZClcblxuLyoqXG4gKiBVc2VyLXByb3ZpZGVkIGNvbmZpZ3VyYXRpb24gZm9yIExpdmVtYXJrLlxuICpcbiAqIGB2aXRlYCBhbmQgYGJhc2VgIGFyZSBsaWZ0ZWQgZnJvbSBgdW5rbm93bmAgdG8gdGhlaXIgcHVibGljIHR5cGVzIHNvXG4gKiBgZGVmaW5lQ29uZmlnYCBzaG93cyB0aGUgcmlnaHQgc2hhcGUgaW4gZWRpdG9ycy4gVXNlIHRoaXMgdHlwZSBmb3JcbiAqIHRoZSBpbnB1dCB0byBgZGVmaW5lQ29uZmlnYCDigJQgaXQga2VlcHMgZGVmYXVsdGVkIGZpZWxkcyBvcHRpb25hbFxuICogKGJlY2F1c2Ugem9kIGlucHV0IOKJoCBvdXRwdXQgZm9yIGZpZWxkcyB3aXRoIGAuZGVmYXVsdCgpYCkuXG4gKi9cbmV4cG9ydCB0eXBlIFVzZXJDb25maWcgPSBPbWl0PHouaW5wdXQ8dHlwZW9mIFVzZXJDb25maWc+LCBcInZpdGVcIiB8IFwiYmFzZVwiPiAmIHtcbiAgdml0ZT86IFZpdGVPdmVycmlkZVxuICBiYXNlPzogQmFzZVxufVxuZXhwb3J0IGNvbnN0IFVzZXJDb25maWcgPSB6Lm9iamVjdCh7XG4gIHNpdGU6IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgLy8gU3RvcmVkIGFzIGB1bmtub3duYCBpbiB0aGUgc2NoZW1hICh6b2QgY2FuJ3QgdHlwZSBhIGZ1bmN0aW9uIGFuZFxuICAvLyBzdHJpbmcgdW5pb24gbWVhbmluZ2Z1bGx5KTsgdGhlIHB1YmxpYyBUeXBlU2NyaXB0IHN1cmZhY2UgYWJvdmVcbiAgLy8gbGlmdHMgaXQgYmFjayB0byBgQmFzZWAgZm9yIGVkaXRvciBzdXBwb3J0LiBUaGUgYnVpbGQgcGlwZWxpbmVcbiAgLy8gcmVzb2x2ZXMgaXQgdmlhIGByZXNvbHZlQmFzZSgpYCBiZWZvcmUgYW55IHJ1bnRpbWUgY29uc3VtZXIgc2Vlc1xuICAvLyBpdCwgc28gdGhlIHJ1bnRpbWUgYFdlYnNpdGVDb25maWcuYmFzZWAgaXMgc3RyaWN0bHkgYHN0cmluZ2AuXG4gIGJhc2U6IHoudW5rbm93bigpLm9wdGlvbmFsKCksXG4gIGZhdmljb246IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgbG9nbzogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxuICB0aXRsZTogei5zdHJpbmcoKS5kZWZhdWx0KFwiTGl2ZW1hcmtcIiksXG4gIGRlc2NyaXB0aW9uOiB6LnN0cmluZygpLmRlZmF1bHQoXCJNYXJrZG93biBzaXRlIGdlbmVyYXRvclwiKSxcbiAgaW5jbHVkZTogei51bmlvbihbei5zdHJpbmcoKSwgei5hcnJheSh6LnN0cmluZygpKV0pLmRlZmF1bHQoXCIqKi8qLm1kXCIpLFxuICBleGNsdWRlOiB6LnVuaW9uKFt6LnN0cmluZygpLCB6LmFycmF5KHouc3RyaW5nKCkpXSkub3B0aW9uYWwoKSxcbiAgY29kZVRoZW1lTGlnaHQ6IENvZGVUaGVtZUxpZ2h0LmRlZmF1bHQoXCJjYXRwcHVjY2luLWxhdHRlXCIpLFxuICBjb2RlVGhlbWVEYXJrOiBDb2RlVGhlbWVEYXJrLmRlZmF1bHQoXCJjYXRwcHVjY2luLW1vY2hhXCIpLFxuICBsaW5rczogei5hcnJheShMaW5rKS5vcHRpb25hbCgpLFxuICBzZWN0aW9uczogei5hcnJheShTZWN0aW9uKS5vcHRpb25hbCgpLFxuICBwYXRjaGVzOiB6LmFycmF5KFBhdGNoKS5vcHRpb25hbCgpLFxuICAvLyBTdG9yZWQgYXMgYHVua25vd25gIGluIHRoZSBzY2hlbWEgKHpvZCBjYW4ndCB0eXBlIGEgVml0ZSBVc2VyQ29uZmlnXG4gIC8vIG9yIGEgZnVuY3Rpb24gcmV0dXJuaW5nIG9uZSBtZWFuaW5nZnVsbHkpOyB0aGUgcHVibGljIFR5cGVTY3JpcHRcbiAgLy8gc3VyZmFjZSBhYm92ZSBsaWZ0cyBpdCBiYWNrIHRvIFZpdGVPdmVycmlkZSBmb3IgZWRpdG9yIHN1cHBvcnQuXG4gIHZpdGU6IHoudW5rbm93bigpLm9wdGlvbmFsKCksXG59KVxuXG4vKiogV2Vic2l0ZSBjb25maWd1cmF0aW9uIGluamVjdGVkIGF0IGJ1aWxkIHRpbWUgdmlhIFZpdGUgZGVmaW5lLlxuICogIGBiYXNlYCBpcyBzdHJpY3RseSBhIHN0cmluZyBoZXJlIOKAlCB0aGUgYnVpbGQgcGlwZWxpbmUgaGFzIGFscmVhZHlcbiAqICByZXNvbHZlZCBhbnkgZnVuY3Rpb24gZm9ybSB2aWEgYHJlc29sdmVCYXNlKClgLiAqL1xuZXhwb3J0IHR5cGUgV2Vic2l0ZUNvbmZpZyA9IHouaW5mZXI8dHlwZW9mIFdlYnNpdGVDb25maWc+XG5leHBvcnQgY29uc3QgV2Vic2l0ZUNvbmZpZyA9IFVzZXJDb25maWcucGljayh7XG4gIHRpdGxlOiB0cnVlLFxuICBkZXNjcmlwdGlvbjogdHJ1ZSxcbiAgc2l0ZTogdHJ1ZSxcbiAgZmF2aWNvbjogdHJ1ZSxcbiAgbG9nbzogdHJ1ZSxcbiAgbGlua3M6IHRydWUsXG4gIHNlY3Rpb25zOiB0cnVlLFxufSkuZXh0ZW5kKHtcbiAgYmFzZTogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxufSlcblxuLyoqXG4gKiBTeXN0ZW0tY29tcHV0ZWQgY29uZmlndXJhdGlvbiBkZXJpdmVkIGZyb20gdGhlIGNvbmZpZyBmaWxlIGxvY2F0aW9uLlxuICovXG5leHBvcnQgdHlwZSBTeXN0ZW1Db25maWcgPSB6LmluZmVyPHR5cGVvZiBTeXN0ZW1Db25maWc+XG5leHBvcnQgY29uc3QgU3lzdGVtQ29uZmlnID0gei5vYmplY3Qoe1xuICByb290OiB6LnN0cmluZygpLFxuICBjb25maWdQYXRoOiB6LnN0cmluZygpLFxufSlcblxuLyoqXG4gKiBGdWxseSByZXNvbHZlZCBMaXZlbWFyayBjb25maWd1cmF0aW9uLlxuICpcbiAqIGB2aXRlYCBzdGF5cyBgdW5rbm93bmAgYmVjYXVzZSB0aGF0J3Mgd2hhdCB0aGUgcnVudGltZSBzY2hlbWFcbiAqIHByb2R1Y2VzIOKAlCBjb25zdW1lci1mYWNpbmcgdHlwaW5nIGhhcHBlbnMgdmlhIHtAbGluayBVc2VyQ29uZmlnfSBzb1xuICogYGRlZmluZUNvbmZpZ2Agc2hvd3MgdGhlIHJpZ2h0IHNoYXBlIGluIGVkaXRvcnMuIGBiYXNlYCBpcyBsaWZ0ZWQgdG9cbiAqIGBzdHJpbmcgfCB1bmRlZmluZWRgIGJlY2F1c2UgYGxvYWRDb25maWcoKWAgcmVzb2x2ZXMgYW55IGZ1bmN0aW9uXG4gKiBmb3JtIHZpYSBgcmVzb2x2ZUJhc2UoKWAgYmVmb3JlIHJldHVybmluZy5cbiAqL1xuZXhwb3J0IHR5cGUgQ29uZmlnID0gT21pdDx6LmluZmVyPHR5cGVvZiBDb25maWc+LCBcImJhc2VcIj4gJiB7XG4gIGJhc2U/OiBzdHJpbmdcbn1cbmV4cG9ydCBjb25zdCBDb25maWcgPSBVc2VyQ29uZmlnLm1lcmdlKFN5c3RlbUNvbmZpZylcbiJdfQ==
|
|
@@ -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
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
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<"
|
|
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<"
|
|
96
|
+
type: z.ZodLiteral<"custom">;
|
|
95
97
|
url: z.ZodString;
|
|
96
98
|
}, z.core.$strip>], "type">>;
|
package/build/models/section.js
CHANGED
|
@@ -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
|
|
22
|
-
type: z.literal("
|
|
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
|
-
|
|
35
|
+
CustomSection,
|
|
36
36
|
]));
|
|
37
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
37
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NvdXJjZS9tb2RlbHMvc2VjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFBO0FBRXZCLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDM0IsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDM0IsS0FBSyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDakIsUUFBUSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO0NBQzFELENBQUMsQ0FBQTtBQUlGLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDO0lBQy9DLElBQUksRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUMxQixNQUFNLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRTtDQUNuQixDQUFDLENBQUE7QUFJRixNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUM1QyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7SUFDdkIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7Q0FDbkIsQ0FBQyxDQUFBO0FBSUYsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztJQUNqRCxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUM7SUFDNUIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbEIsT0FBTyxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDaEMsQ0FBQyxDQUFBO0FBTUYsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUM7SUFDOUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO0lBQ3pCLEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0NBQ2hCLENBQUMsQ0FBQTtBQVFGLE1BQU0sQ0FBQyxNQUFNLE9BQU8sR0FBRyxDQUFDLENBQUMsVUFBVSxDQUNqQyxLQUFLLENBQUMsRUFBRTtJQUNOLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hFLE9BQU8sS0FBSyxDQUFBO0lBQ2QsQ0FBQztJQUNELE1BQU0sTUFBTSxHQUFHLEtBQWdDLENBQUE7SUFDL0MsT0FBTyxNQUFNLElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFBO0FBQ25FLENBQUMsRUFDRCxDQUFDLENBQUMsa0JBQWtCLENBQUMsTUFBTSxFQUFFO0lBQzNCLGNBQWM7SUFDZCxXQUFXO0lBQ1gsZ0JBQWdCO0lBQ2hCLGFBQWE7Q0FDZCxDQUFDLENBQ0gsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHogfSBmcm9tIFwiem9kXCJcblxuY29uc3QgQmFzZVNlY3Rpb24gPSB6Lm9iamVjdCh7XG4gIGljb246IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgdGl0bGU6IHouc3RyaW5nKCksXG4gIHBvc2l0aW9uOiB6LmVudW0oW1wiaGVhZGVyXCIsIFwic2lkZWJhclwiXSkuZGVmYXVsdChcImhlYWRlclwiKSxcbn0pXG5cbi8qKiBEZWZhdWx0IHNlY3Rpb246IHJlbmRlcnMgYXJ0aWNsZXMuICovXG5leHBvcnQgdHlwZSBBcnRpY2xlU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIEFydGljbGVTZWN0aW9uPlxuZXhwb3J0IGNvbnN0IEFydGljbGVTZWN0aW9uID0gQmFzZVNlY3Rpb24uZXh0ZW5kKHtcbiAgdHlwZTogei5saXRlcmFsKFwiYXJ0aWNsZVwiKSxcbiAgcHJlZml4OiB6LnN0cmluZygpLFxufSlcblxuLyoqIEJsb2cgc2VjdGlvbjogZGF0ZS1zb3J0ZWQgcG9zdHMgd2l0aCB0YWdzIGFuZCBpbmRleC4gKi9cbmV4cG9ydCB0eXBlIEJsb2dTZWN0aW9uID0gei5pbmZlcjx0eXBlb2YgQmxvZ1NlY3Rpb24+XG5leHBvcnQgY29uc3QgQmxvZ1NlY3Rpb24gPSBCYXNlU2VjdGlvbi5leHRlbmQoe1xuICB0eXBlOiB6LmxpdGVyYWwoXCJibG9nXCIpLFxuICBwcmVmaXg6IHouc3RyaW5nKCksXG59KVxuXG4vKiogQ2hhbmdlbG9nIHNlY3Rpb246IHNpbmdsZSBmaWxlIG9yIEdpdEh1YiByZWxlYXNlcywgc3BsaXQgcGVyIHZlcnNpb24uICovXG5leHBvcnQgdHlwZSBDaGFuZ2Vsb2dTZWN0aW9uID0gei5pbmZlcjx0eXBlb2YgQ2hhbmdlbG9nU2VjdGlvbj5cbmV4cG9ydCBjb25zdCBDaGFuZ2Vsb2dTZWN0aW9uID0gQmFzZVNlY3Rpb24uZXh0ZW5kKHtcbiAgdHlwZTogei5saXRlcmFsKFwiY2hhbmdlbG9nXCIpLFxuICBwcmVmaXg6IHouc3RyaW5nKCksXG4gIHNvdXJjZTogei5zdHJpbmcoKSxcbiAgdmVyc2lvbjogei5ib29sZWFuKCkub3B0aW9uYWwoKSxcbn0pXG5cbi8qKiBDdXN0b20gc2VjdGlvbjogcmVuZGVycyBhcyBhIGxpbmsgdG8gYW4gYXJiaXRyYXJ5IFVSTCDigJQgYWJzb2x1dGVcbiAqICAoYGh0dHBzOi8v4oCmYCkgb3IgaW50ZXJuYWwgKGAvY2hhbmdlbG9nYCkuIEludGVybmFsIFVSTHMgcGFydGljaXBhdGVcbiAqICBpbiBhY3RpdmUtc3RhdGUgaGlnaGxpZ2h0aW5nIGxpa2Ugcm91dGVkIHNlY3Rpb25zLiAqL1xuZXhwb3J0IHR5cGUgQ3VzdG9tU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIEN1c3RvbVNlY3Rpb24+XG5leHBvcnQgY29uc3QgQ3VzdG9tU2VjdGlvbiA9IEJhc2VTZWN0aW9uLmV4dGVuZCh7XG4gIHR5cGU6IHoubGl0ZXJhbChcImN1c3RvbVwiKSxcbiAgdXJsOiB6LnN0cmluZygpLFxufSlcblxuLyoqXG4gKiBBIHNpdGUgc2VjdGlvbi4gRGlzY3JpbWluYXRlZCBvbiBgdHlwZWAuXG4gKiBXaGVuIGB0eXBlYCBpcyBvbWl0dGVkLCBpdCBkZWZhdWx0cyB0byBcImFydGljbGVcIiBzbyBwbGFpblxuICogYHsgdGl0bGUsIHByZWZpeCB9YCBlbnRyaWVzIGtlZXAgd29ya2luZy5cbiAqL1xuZXhwb3J0IHR5cGUgU2VjdGlvbiA9IHouaW5mZXI8dHlwZW9mIFNlY3Rpb24+XG5leHBvcnQgY29uc3QgU2VjdGlvbiA9IHoucHJlcHJvY2VzcyhcbiAgaW5wdXQgPT4ge1xuICAgIGlmIChpbnB1dCA9PT0gbnVsbCB8fCB0eXBlb2YgaW5wdXQgIT09IFwib2JqZWN0XCIgfHwgQXJyYXkuaXNBcnJheShpbnB1dCkpIHtcbiAgICAgIHJldHVybiBpbnB1dFxuICAgIH1cbiAgICBjb25zdCByZWNvcmQgPSBpbnB1dCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPlxuICAgIHJldHVybiBcInR5cGVcIiBpbiByZWNvcmQgPyByZWNvcmQgOiB7IC4uLnJlY29yZCwgdHlwZTogXCJhcnRpY2xlXCIgfVxuICB9LFxuICB6LmRpc2NyaW1pbmF0ZWRVbmlvbihcInR5cGVcIiwgW1xuICAgIEFydGljbGVTZWN0aW9uLFxuICAgIEJsb2dTZWN0aW9uLFxuICAgIENoYW5nZWxvZ1NlY3Rpb24sXG4gICAgQ3VzdG9tU2VjdGlvbixcbiAgXSksXG4pXG4iXX0=
|
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { UserConfig } from "../../models/config.ts"
|
|
1
|
+
import { UserConfig as UserConfigSchema } from "../../models/config.ts"
|
|
2
|
+
import type { UserConfig } from "../../models/config.ts"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Validate and fill defaults for a partial config object.
|
|
6
6
|
*/
|
|
7
|
-
export function defineConfig(
|
|
8
|
-
|
|
9
|
-
): z.output<typeof UserConfig> {
|
|
10
|
-
return UserConfig.parse(input)
|
|
7
|
+
export function defineConfig(input: UserConfig) {
|
|
8
|
+
return UserConfigSchema.parse(input)
|
|
11
9
|
}
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import { dirname, resolve } from "node:path"
|
|
3
3
|
import { pathToFileURL } from "node:url"
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import { UserConfig } from "../../models/config.ts"
|
|
4
|
+
import type { Config, UserConfig } from "../../models/config.ts"
|
|
5
|
+
import { resolveBase } from "../../helpers/resolve-base.ts"
|
|
7
6
|
import { defineConfig } from "./define.ts"
|
|
8
7
|
|
|
8
|
+
/** Map livemark CLI verbs to the Vite-equivalent `command` string used by
|
|
9
|
+
* `resolveBase()`. `preview` serves the production build's artifacts so
|
|
10
|
+
* it uses the build-mode base. Default to `"build"` for safety when no
|
|
11
|
+
* recognised verb is in argv (the common path is `livemark <verb> ...`). */
|
|
12
|
+
const COMMAND_BY_VERB: Record<string, "build" | "serve"> = {
|
|
13
|
+
start: "serve",
|
|
14
|
+
build: "build",
|
|
15
|
+
preview: "build",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function detectCommand() {
|
|
19
|
+
for (const arg of process.argv.slice(2)) {
|
|
20
|
+
if (arg in COMMAND_BY_VERB) return COMMAND_BY_VERB[arg]!
|
|
21
|
+
}
|
|
22
|
+
return "build" as const
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
26
|
* Load and validate a config file, checking --config/-c CLI arg, then cwd.
|
|
11
27
|
*
|
|
@@ -23,15 +39,19 @@ export async function loadConfig(path?: string): Promise<Config> {
|
|
|
23
39
|
const cliPath = configIndex !== -1 ? process.argv[configIndex + 1] : undefined
|
|
24
40
|
const explicit = path ?? cliPath
|
|
25
41
|
const resolved = resolve(explicit ?? "livemark.config.ts")
|
|
26
|
-
let input:
|
|
42
|
+
let input: UserConfig = {}
|
|
27
43
|
if (existsSync(resolved)) {
|
|
28
44
|
// Cache-bust the ESM import so dev-server restart reloads edits on disk.
|
|
29
45
|
const url = `${pathToFileURL(resolved).href}?t=${Date.now()}`
|
|
30
46
|
const module: Record<string, unknown> = await import(url)
|
|
31
|
-
input = (module.default as
|
|
47
|
+
input = (module.default as UserConfig) ?? {}
|
|
32
48
|
} else if (explicit) {
|
|
33
49
|
throw new Error(`Config file not found: ${resolved}`)
|
|
34
50
|
}
|
|
35
51
|
const validated = defineConfig(input)
|
|
36
|
-
|
|
52
|
+
// Resolve the function form of `base` once, here, using the livemark
|
|
53
|
+
// CLI verb. Downstream consumers (Vite plugin, content-collections,
|
|
54
|
+
// runtime) get a plain string|undefined and never see the function.
|
|
55
|
+
const base = resolveBase(validated.base, detectCommand())
|
|
56
|
+
return { ...validated, base, root: dirname(resolved), configPath: resolved }
|
|
37
57
|
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "../content/article.ts"
|
|
10
10
|
import { DynamicIcon } from "../helpers/dynamic-icon.tsx"
|
|
11
11
|
import { prefixUrl } from "../helpers/prefix-url.ts"
|
|
12
|
-
import {
|
|
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" : "
|
|
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
|
|
54
|
+
<div className="hidden lg:flex">
|
|
55
55
|
<SidebarTrigger />
|
|
56
56
|
</div>
|
|
57
|
-
<Link to="/" className="
|
|
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
|
|
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
|
-
|
|
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=
|
|
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="
|
|
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
|
|
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
|
-
|
|
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 : "
|
|
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 ? "
|
|
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 {
|
|
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 : "
|
|
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
|
-
|
|
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 ? "
|
|
49
|
+
className={mobileOnly ? "lg:hidden" : undefined}
|
|
48
50
|
>
|
|
49
51
|
<SidebarMenuButton
|
|
50
|
-
|
|
52
|
+
isActive={active}
|
|
53
|
+
className={active ? "" : "opacity-75"}
|
|
51
54
|
render={<a href={prefixUrl(s.url, config.base)} />}
|
|
52
55
|
>
|
|
53
|
-
<DynamicIcon name={
|
|
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 ? "
|
|
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={
|
|
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 `
|
|
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 !== "
|
|
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
|
|
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 {
|
|
1
|
+
import { icons } from "lucide-react"
|
|
2
2
|
|
|
3
|
-
/** Resolve a Lucide icon by kebab-case name
|
|
4
|
-
|
|
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] :
|
|
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) {
|
|
@@ -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
|
+
}
|