blume 0.3.0 → 0.5.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/dist/cli/index.js +1631 -940
- package/dist/cli/index.js.map +62 -50
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/project.d.ts +12 -2
- package/dist/types/core/schema.d.ts +442 -292
- package/dist/types/core/types.d.ts +7 -0
- package/dist/types/migrate/mintlify/assets.d.ts +8 -0
- package/docs/01-quickstart.mdx +5 -16
- package/docs/02-deployment.mdx +21 -54
- package/docs/advanced/api-reference.mdx +34 -51
- package/docs/advanced/blog.mdx +9 -25
- package/docs/advanced/bridge.mdx +74 -0
- package/docs/advanced/changelog.mdx +10 -33
- package/docs/advanced/custom-pages.mdx +21 -78
- package/docs/advanced/meta.ts +8 -1
- package/docs/advanced/migrate.mdx +119 -0
- package/docs/configuration/ai.mdx +42 -103
- package/docs/configuration/analytics.mdx +20 -38
- package/docs/configuration/customization.mdx +40 -73
- package/docs/configuration/export.mdx +9 -34
- package/docs/configuration/index.mdx +67 -87
- package/docs/configuration/search.mdx +17 -54
- package/docs/configuration/seo.mdx +17 -48
- package/docs/configuration/theming.mdx +20 -42
- package/docs/content/components.mdx +95 -101
- package/docs/content/i18n.mdx +21 -72
- package/docs/content/index.mdx +18 -48
- package/docs/content/islands.mdx +25 -52
- package/docs/content/meta.mdx +23 -50
- package/docs/content/navigation.mdx +23 -62
- package/docs/content/sources.mdx +20 -83
- package/docs/content/syntax.mdx +37 -105
- package/docs/index.mdx +12 -41
- package/docs/reference/cli.mdx +47 -30
- package/docs/reference/frontmatter.mdx +7 -5
- package/package.json +11 -1
- package/src/astro/generate.ts +18 -8
- package/src/astro/integration.ts +26 -3
- package/src/astro/islands.ts +6 -2
- package/src/astro/markdown-negotiation.ts +17 -3
- package/src/astro/pages.ts +6 -1
- package/src/astro/static-assets.ts +117 -0
- package/src/astro/templates.ts +76 -30
- package/src/cli/args.ts +23 -0
- package/src/cli/commands/build.ts +129 -62
- package/src/cli/commands/check.ts +20 -0
- package/src/cli/commands/dev.ts +11 -2
- package/src/cli/commands/doctor.ts +10 -1
- package/src/cli/commands/eject.ts +3 -1
- package/src/cli/commands/init.ts +21 -1
- package/src/cli/commands/preview.ts +2 -1
- package/src/cli/commands/validate.ts +12 -1
- package/src/cli/dev-lock.ts +92 -0
- package/src/cli/log.ts +11 -0
- package/src/cli/prepare.ts +3 -0
- package/src/components/BlumePage.astro +8 -0
- package/src/components/Icon.astro +13 -10
- package/src/components/content/ApiField.astro +75 -0
- package/src/components/content/ParamField.astro +39 -0
- package/src/components/content/RequestField.astro +23 -0
- package/src/components/content/ResponseField.astro +23 -0
- package/src/components/content/Step.astro +1 -1
- package/src/components/content/YouTube.astro +35 -0
- package/src/components/content/youtube.ts +46 -0
- package/src/components/islands/ask-ai.tsx +14 -14
- package/src/components/layout/Breadcrumbs.astro +7 -2
- package/src/components/layout/NavTree.astro +24 -8
- package/src/components/layout/RootLayout.astro +56 -34
- package/src/components/layout/Search.astro +1 -1
- package/src/components/openapi/ApiOverview.astro +84 -0
- package/src/components/openapi/MethodBadge.astro +28 -0
- package/src/components/openapi/Operation.astro +140 -0
- package/src/components/openapi/ParametersTable.astro +97 -0
- package/src/components/openapi/RequestBody.astro +58 -0
- package/src/components/openapi/RequestPanel.astro +169 -0
- package/src/components/openapi/Responses.astro +91 -0
- package/src/components/openapi/SchemaProperty.astro +118 -0
- package/src/components/openapi/SchemaTable.astro +86 -0
- package/src/components/openapi/helpers.ts +238 -0
- package/src/components/openapi/panel.ts +59 -0
- package/src/components/openapi/snippets.ts +201 -0
- package/src/components/props.ts +3 -0
- package/src/core/assets.ts +31 -0
- package/src/core/bridge.ts +10 -0
- package/src/core/builtin-tags.ts +6 -0
- package/src/core/data.ts +2 -0
- package/src/core/diagnostics.ts +6 -1
- package/src/core/gitignore.ts +30 -0
- package/src/core/links.ts +60 -19
- package/src/core/project-graph.ts +5 -1
- package/src/core/project.ts +25 -3
- package/src/core/schema.ts +54 -6
- package/src/core/sources/mdx-remote.ts +54 -8
- package/src/core/sources/mintlify.ts +1 -1
- package/src/core/sources/normalize.ts +6 -1
- package/src/core/sources/notion.ts +49 -5
- package/src/core/sources/resolve.ts +28 -6
- package/src/core/sources/sanity.ts +5 -1
- package/src/core/types.ts +7 -0
- package/src/deploy/rss.ts +1 -8
- package/src/deploy/sitemap.ts +20 -1
- package/src/deploy/xml.ts +8 -0
- package/src/markdown/directives.ts +15 -7
- package/src/markdown/package-commands.ts +26 -4
- package/src/migrate/fumadocs/content.ts +14 -1
- package/src/migrate/fumadocs/groups.ts +7 -0
- package/src/migrate/fumadocs/index.ts +5 -2
- package/src/migrate/mintlify/assets.ts +46 -0
- package/src/migrate/mintlify/config.ts +153 -1
- package/src/migrate/mintlify/content.ts +8 -2
- package/src/migrate/mintlify/index.ts +111 -46
- package/src/migrate/shared.ts +12 -27
- package/src/og/card.ts +14 -2
- package/src/openapi/model.ts +174 -0
- package/src/openapi/parse.ts +48 -0
- package/src/openapi/references.ts +164 -0
- package/src/openapi/render-mdx.ts +76 -0
- package/src/openapi/scalar.ts +15 -103
- package/src/openapi/source.ts +140 -0
- package/src/registry/eject.ts +28 -5
- package/src/registry/registry.ts +6 -0
- package/src/registry/rewrite-imports.ts +31 -19
- package/src/search/documents.ts +23 -5
- package/src/search/sync/algolia.ts +5 -1
- package/src/search/sync/typesense.ts +24 -16
- package/src/theme/chrome-icons.ts +22 -0
- package/src/theme/icons.ts +151 -161
- package/src/theme/palette.ts +26 -7
package/src/theme/icons.ts
CHANGED
|
@@ -1,184 +1,174 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Icon resolution backed by the open Iconify icon sets — Font Awesome (free),
|
|
3
|
+
* Lucide, and Tabler — the three libraries Mintlify exposes. Resolution runs at
|
|
4
|
+
* **build time, server-side**, and returns ready-to-inline SVG, so icons stay
|
|
5
|
+
* zero-JS and fully self-contained (no runtime CDN fetch, unlike Mintlify).
|
|
6
|
+
*
|
|
7
|
+
* Because the Iconify set data is large, this module must only be imported from
|
|
8
|
+
* server contexts (`.astro` frontmatter, the CLI). Client scripts use the tiny
|
|
9
|
+
* hand-inlined set in `./chrome-icons.ts` instead.
|
|
10
|
+
*
|
|
11
|
+
* Coverage vs Mintlify: full parity for every Font Awesome *free* name, Lucide,
|
|
12
|
+
* and Tabler. Font Awesome Pro styles (`light`/`thin`/`duotone`/`sharp-solid`)
|
|
13
|
+
* aren't in the open data, so they fall back to `solid`.
|
|
5
14
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"message-circle": '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>',
|
|
52
|
-
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
|
|
53
|
-
"panel-left":
|
|
54
|
-
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/>',
|
|
55
|
-
"panel-left-close":
|
|
56
|
-
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m16 15-3-3 3-3"/>',
|
|
57
|
-
"panel-right":
|
|
58
|
-
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/>',
|
|
59
|
-
"panel-right-close":
|
|
60
|
-
'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m8 9 3 3-3 3"/>',
|
|
61
|
-
paperclip:
|
|
62
|
-
'<path d="m16 6-8.41 8.41a2 2 0 0 0 2.83 2.83L18.83 8.83a4 4 0 0 0-5.66-5.66L4.76 11.59a6 6 0 0 0 8.49 8.49L21.66 11.66"/>',
|
|
63
|
-
"puzzle-piece":
|
|
64
|
-
'<path d="M15.39 4.39a2.1 2.1 0 0 0-2.97 0L12 4.82l-.42-.43a2.1 2.1 0 1 0-2.97 2.97l.43.42L7.6 9.22H4.75A1.75 1.75 0 0 0 3 10.97v8.28C3 20.22 3.78 21 4.75 21h8.28c.97 0 1.75-.78 1.75-1.75V16.4l1.44-1.44.42.43a2.1 2.1 0 1 0 2.97-2.97l-.43-.42.43-.42a2.1 2.1 0 1 0-2.97-2.97l-.42.43-1.44-1.44.61-.61a2.1 2.1 0 0 0 0-2.97Z"/>',
|
|
65
|
-
python:
|
|
66
|
-
'<path d="M12 2h4a4 4 0 0 1 4 4v3H8a4 4 0 0 0-4 4v1"/><path d="M12 22H8a4 4 0 0 1-4-4v-3h12a4 4 0 0 0 4-4v-1"/><path d="M9 6h.01"/><path d="M15 18h.01"/>',
|
|
67
|
-
rocket:
|
|
68
|
-
'<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
|
|
69
|
-
rss: '<path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/>',
|
|
70
|
-
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
|
71
|
-
sparkles:
|
|
72
|
-
'<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3Z"/><path d="M5 3v4"/><path d="M3 5h4"/><path d="M19 17v4"/><path d="M17 19h4"/>',
|
|
73
|
-
star: '<path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01Z"/>',
|
|
74
|
-
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
|
|
75
|
-
"text-align-start":
|
|
76
|
-
'<path d="M4 6h16"/><path d="M4 10h10"/><path d="M4 14h16"/><path d="M4 18h10"/>',
|
|
77
|
-
"thumbs-down":
|
|
78
|
-
'<path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/>',
|
|
79
|
-
"thumbs-up":
|
|
80
|
-
'<path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/>',
|
|
81
|
-
"triangle-alert":
|
|
82
|
-
'<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
83
|
-
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
15
|
+
import { createRequire } from "node:module";
|
|
16
|
+
|
|
17
|
+
import type { IconifyJSON } from "@iconify/types";
|
|
18
|
+
import { getIconData, iconToSVG } from "@iconify/utils";
|
|
19
|
+
|
|
20
|
+
// Load the icon-set JSON with a CJS `require` rather than an `import ... with
|
|
21
|
+
// { type: "json" }`: the bundled Node CLI drops the import attribute (Bun.build
|
|
22
|
+
// strips it when externalizing) and then rejects the module, whereas `require`
|
|
23
|
+
// of a JSON file needs no attribute and works under both Node and Bun.
|
|
24
|
+
const requireJson = createRequire(import.meta.url);
|
|
25
|
+
const loadSet = (pkg: string): IconifyJSON => requireJson(pkg) as IconifyJSON;
|
|
26
|
+
|
|
27
|
+
const SETS: Record<string, IconifyJSON> = {
|
|
28
|
+
"fa6-brands": loadSet("@iconify-json/fa6-brands/icons.json"),
|
|
29
|
+
"fa6-regular": loadSet("@iconify-json/fa6-regular/icons.json"),
|
|
30
|
+
"fa6-solid": loadSet("@iconify-json/fa6-solid/icons.json"),
|
|
31
|
+
lucide: loadSet("@iconify-json/lucide/icons.json"),
|
|
32
|
+
tabler: loadSet("@iconify-json/tabler/icons.json"),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Blume's default library when a project sets no `icons.library`. */
|
|
36
|
+
const DEFAULT_SET = "lucide";
|
|
37
|
+
|
|
38
|
+
/** `icons.library` config value → Iconify set. */
|
|
39
|
+
const LIBRARY_SETS: Record<string, string> = {
|
|
40
|
+
fa: "fa6-solid",
|
|
41
|
+
"font-awesome": "fa6-solid",
|
|
42
|
+
fontawesome: "fa6-solid",
|
|
43
|
+
lucide: "lucide",
|
|
44
|
+
tabler: "tabler",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Mintlify Font Awesome `iconType` → Iconify set. The Pro-only styles
|
|
49
|
+
* (`light`/`thin`/`duotone`/`sharp-solid`) aren't in the free data, so they map
|
|
50
|
+
* to `solid` rather than render nothing.
|
|
51
|
+
*/
|
|
52
|
+
const ICON_TYPE_SETS: Record<string, string> = {
|
|
53
|
+
brands: "fa6-brands",
|
|
54
|
+
duotone: "fa6-solid",
|
|
55
|
+
light: "fa6-solid",
|
|
56
|
+
regular: "fa6-regular",
|
|
57
|
+
"sharp-solid": "fa6-solid",
|
|
58
|
+
solid: "fa6-solid",
|
|
59
|
+
thin: "fa6-solid",
|
|
84
60
|
};
|
|
85
61
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
times: "x",
|
|
101
|
-
"x-twitter": "brand-x",
|
|
62
|
+
/** Explicit `prefix:name` prefixes (Iconify prefixes + common FA aliases). */
|
|
63
|
+
const PREFIX_SETS: Record<string, string> = {
|
|
64
|
+
...LIBRARY_SETS,
|
|
65
|
+
fa: "fa6-solid",
|
|
66
|
+
"fa-brands": "fa6-brands",
|
|
67
|
+
"fa-regular": "fa6-regular",
|
|
68
|
+
"fa-solid": "fa6-solid",
|
|
69
|
+
"fa6-brands": "fa6-brands",
|
|
70
|
+
"fa6-regular": "fa6-regular",
|
|
71
|
+
"fa6-solid": "fa6-solid",
|
|
72
|
+
fab: "fa6-brands",
|
|
73
|
+
far: "fa6-regular",
|
|
74
|
+
fas: "fa6-solid",
|
|
75
|
+
ti: "tabler",
|
|
102
76
|
};
|
|
103
77
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"ti",
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
const normalizedIconName = (name: string): string =>
|
|
78
|
+
export interface ResolvedIcon {
|
|
79
|
+
/** Inner SVG markup (self-styled: carries its own fill/stroke). */
|
|
80
|
+
body: string;
|
|
81
|
+
/** The resolved icon name. */
|
|
82
|
+
name: string;
|
|
83
|
+
/** The icon's viewBox, which varies per library (24×24, 512-height, …). */
|
|
84
|
+
viewBox: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ResolveIconOptions {
|
|
88
|
+
/** Font Awesome style selector (`solid`, `regular`, `brands`, …). */
|
|
89
|
+
iconType?: string;
|
|
90
|
+
/** Default library for a bare name (`fontawesome` | `lucide` | `tabler`). */
|
|
91
|
+
library?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const normalize = (name: string): string =>
|
|
125
95
|
name
|
|
126
96
|
.trim()
|
|
127
97
|
.toLowerCase()
|
|
128
98
|
.replaceAll(/[\s_]+/gu, "-");
|
|
129
99
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
while (changed) {
|
|
137
|
-
changed = false;
|
|
138
|
-
for (const prefix of libraryPrefixes) {
|
|
139
|
-
if (normalized.startsWith(`${prefix}-`)) {
|
|
140
|
-
normalized = normalized.slice(prefix.length + 1);
|
|
141
|
-
changed = true;
|
|
142
|
-
}
|
|
143
|
-
if (normalized.startsWith(`${prefix}:`)) {
|
|
144
|
-
normalized = normalized.slice(prefix.length + 1);
|
|
145
|
-
changed = true;
|
|
146
|
-
}
|
|
100
|
+
/** Which set a bare name resolves against, given library/iconType hints. */
|
|
101
|
+
const setFor = (options: ResolveIconOptions): string => {
|
|
102
|
+
if (options.iconType) {
|
|
103
|
+
const set = ICON_TYPE_SETS[normalize(options.iconType)];
|
|
104
|
+
if (set) {
|
|
105
|
+
return set;
|
|
147
106
|
}
|
|
148
107
|
}
|
|
149
|
-
|
|
108
|
+
if (options.library) {
|
|
109
|
+
const set = LIBRARY_SETS[normalize(options.library)];
|
|
110
|
+
if (set) {
|
|
111
|
+
return set;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return DEFAULT_SET;
|
|
150
115
|
};
|
|
151
116
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
117
|
+
const fromSet = (setName: string, iconName: string): ResolvedIcon | null => {
|
|
118
|
+
const set = SETS[setName];
|
|
119
|
+
const data = set && getIconData(set, iconName);
|
|
120
|
+
if (!data) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const { attributes, body } = iconToSVG(data, { height: "auto" });
|
|
124
|
+
return { body, name: iconName, viewBox: attributes.viewBox };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Font Awesome splits brands into their own set, so a bare `github` under a
|
|
128
|
+
// solid/regular default still resolves.
|
|
129
|
+
const fromFaSet = (setName: string, name: string): ResolvedIcon | null =>
|
|
130
|
+
fromSet(setName, name) ?? fromSet("fa6-brands", name);
|
|
156
131
|
|
|
157
|
-
|
|
132
|
+
const resolveInSet = (setName: string, name: string): ResolvedIcon | null =>
|
|
133
|
+
setName.startsWith("fa6-")
|
|
134
|
+
? fromFaSet(setName, name)
|
|
135
|
+
: fromSet(setName, name);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolve an icon name to inline SVG. Honors an explicit `prefix:name`
|
|
139
|
+
* (`lucide:rocket`, `fa6-brands:github`), then `iconType`, then the configured
|
|
140
|
+
* `library`, falling back to Lucide.
|
|
141
|
+
*/
|
|
158
142
|
export const resolveIcon = (
|
|
159
143
|
name: string,
|
|
160
|
-
|
|
144
|
+
options: ResolveIconOptions = {}
|
|
161
145
|
): ResolvedIcon | null => {
|
|
162
|
-
const normalized =
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
type ? `${type}-${stripped}` : null,
|
|
169
|
-
iconAliases[normalized],
|
|
170
|
-
iconAliases[stripped],
|
|
171
|
-
].filter(isString);
|
|
172
|
-
|
|
173
|
-
for (const candidate of candidates) {
|
|
174
|
-
const markup = icons[candidate];
|
|
175
|
-
if (markup) {
|
|
176
|
-
return { markup, name: candidate };
|
|
146
|
+
const normalized = normalize(name);
|
|
147
|
+
const colon = normalized.indexOf(":");
|
|
148
|
+
if (colon > 0) {
|
|
149
|
+
const setName = PREFIX_SETS[normalized.slice(0, colon)];
|
|
150
|
+
if (setName) {
|
|
151
|
+
return resolveInSet(setName, normalized.slice(colon + 1));
|
|
177
152
|
}
|
|
178
153
|
}
|
|
179
|
-
return
|
|
154
|
+
return resolveInSet(setFor(options), normalized);
|
|
180
155
|
};
|
|
181
156
|
|
|
182
|
-
/**
|
|
183
|
-
|
|
184
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Whether a name resolves to a known icon. Without a library hint this checks
|
|
159
|
+
* every bundled set, so a valid Font Awesome/Tabler name isn't flagged as a typo
|
|
160
|
+
* just because the project's default library is Lucide.
|
|
161
|
+
*/
|
|
162
|
+
export const hasIcon = (
|
|
163
|
+
name: string,
|
|
164
|
+
options: ResolveIconOptions = {}
|
|
165
|
+
): boolean => {
|
|
166
|
+
if (resolveIcon(name, options)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
const normalized = normalize(name);
|
|
170
|
+
const bare = normalized.includes(":")
|
|
171
|
+
? normalized.slice(normalized.indexOf(":") + 1)
|
|
172
|
+
: normalized;
|
|
173
|
+
return Object.values(SETS).some((set) => getIconData(set, bare) !== null);
|
|
174
|
+
};
|
package/src/theme/palette.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
2
2
|
|
|
3
|
+
const FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
|
|
4
|
+
|
|
3
5
|
/** Named accent presets mapped to OKLCH values. */
|
|
4
6
|
const ACCENTS: Record<string, string> = {
|
|
5
|
-
blue:
|
|
7
|
+
blue: FALLBACK_ACCENT,
|
|
6
8
|
green: "oklch(0.6 0.16 150)",
|
|
7
9
|
orange: "oklch(0.68 0.17 50)",
|
|
8
10
|
pink: "oklch(0.65 0.2 350)",
|
|
@@ -11,6 +13,19 @@ const ACCENTS: Record<string, string> = {
|
|
|
11
13
|
teal: "oklch(0.6 0.12 195)",
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
// Characters valid in a CSS color value (hex, rgb/hsl/oklch functions, named
|
|
17
|
+
// colors). Anything else — notably `;`, `{`, `}` — could break out of the
|
|
18
|
+
// declaration and inject rules, so such a value is rejected.
|
|
19
|
+
const CSS_COLOR = /^[\w\s#%.,()/+-]+$/u;
|
|
20
|
+
|
|
21
|
+
/** Pass a raw color through only if it can't break out of a CSS declaration. */
|
|
22
|
+
const safeColor = (value: string, fallback: string): string =>
|
|
23
|
+
CSS_COLOR.test(value.trim()) ? value.trim() : fallback;
|
|
24
|
+
|
|
25
|
+
/** Like {@link safeColor} but drops an unsafe/absent value to `null`. */
|
|
26
|
+
const safeColorOrNull = (value: string | undefined): string | null =>
|
|
27
|
+
value && CSS_COLOR.test(value.trim()) ? value.trim() : null;
|
|
28
|
+
|
|
14
29
|
const RADII: Record<ResolvedConfig["theme"]["radius"], string> = {
|
|
15
30
|
lg: "0.75rem",
|
|
16
31
|
md: "0.5rem",
|
|
@@ -66,7 +81,7 @@ const themeRootCss = (
|
|
|
66
81
|
"--blume-action-foreground",
|
|
67
82
|
options.action ? "oklch(1 0 0)" : null
|
|
68
83
|
),
|
|
69
|
-
...cssToken("--blume-background", theme.background),
|
|
84
|
+
...cssToken("--blume-background", safeColorOrNull(theme.background)),
|
|
70
85
|
...cssToken(
|
|
71
86
|
"--blume-background-image",
|
|
72
87
|
theme.backgroundImage ? backgroundImageCss(theme.backgroundImage) : null
|
|
@@ -83,7 +98,7 @@ const themeDarkCss = (
|
|
|
83
98
|
): string => {
|
|
84
99
|
const tokens = [
|
|
85
100
|
...cssToken("--blume-accent", accentDark),
|
|
86
|
-
...cssToken("--blume-background", theme.backgroundDark),
|
|
101
|
+
...cssToken("--blume-background", safeColorOrNull(theme.backgroundDark)),
|
|
87
102
|
...cssToken(
|
|
88
103
|
"--blume-background-image",
|
|
89
104
|
theme.backgroundImageDark
|
|
@@ -106,7 +121,7 @@ ${tokens.join("\n")}
|
|
|
106
121
|
* arbitrary colors without a config change.
|
|
107
122
|
*/
|
|
108
123
|
export const resolveAccent = (theme: ResolvedConfig["theme"]): string =>
|
|
109
|
-
ACCENTS[theme.accent] ?? theme.accent;
|
|
124
|
+
ACCENTS[theme.accent] ?? safeColor(theme.accent, FALLBACK_ACCENT);
|
|
110
125
|
|
|
111
126
|
/** Resolve the configured radius preset to a CSS length. */
|
|
112
127
|
export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
@@ -118,11 +133,15 @@ export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
|
118
133
|
* arbitrary colors without a config change.
|
|
119
134
|
*/
|
|
120
135
|
export const buildThemeCss = (theme: ResolvedConfig["theme"]): string => {
|
|
121
|
-
const accent =
|
|
136
|
+
const accent =
|
|
137
|
+
ACCENTS[theme.accent] ?? safeColor(theme.accent, FALLBACK_ACCENT);
|
|
122
138
|
const accentDark = theme.accentDark
|
|
123
|
-
? (ACCENTS[theme.accentDark] ??
|
|
139
|
+
? (ACCENTS[theme.accentDark] ??
|
|
140
|
+
safeColor(theme.accentDark, FALLBACK_ACCENT))
|
|
141
|
+
: null;
|
|
142
|
+
const action = theme.action
|
|
143
|
+
? (ACCENTS[theme.action] ?? safeColor(theme.action, FALLBACK_ACCENT))
|
|
124
144
|
: null;
|
|
125
|
-
const action = theme.action ? (ACCENTS[theme.action] ?? theme.action) : null;
|
|
126
145
|
const backgroundDecoration = backgroundDecorationCss(
|
|
127
146
|
theme.backgroundDecoration
|
|
128
147
|
);
|