blume 1.5.3 → 1.6.1
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/CHANGELOG.md +94 -0
- package/dist/cli/index.js +3949 -1403
- package/dist/cli/index.js.map +111 -96
- package/dist/types/ai/component-markdown.d.ts +79 -0
- package/dist/types/components/layout/nav-utils.d.ts +60 -0
- package/dist/types/core/base-path.d.ts +9 -0
- package/dist/types/core/config-input.d.ts +206 -4
- package/dist/types/core/config.d.ts +6 -4
- package/dist/types/core/data.d.ts +33 -2
- package/dist/types/core/github.d.ts +35 -0
- package/dist/types/core/i18n-ui.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +69 -0
- package/dist/types/core/schema.d.ts +122 -1
- package/dist/types/core/sources/types.d.ts +31 -6
- package/dist/types/core/types.d.ts +29 -2
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/openapi/references.d.ts +26 -1
- package/dist/types/seo/jsonld.d.ts +105 -0
- package/dist/types/theme/fonts.d.ts +34 -4
- package/docs/07-faq.mdx +9 -9
- package/docs/_snippets/include-demo.mdx +7 -0
- package/docs/advanced/api-reference.mdx +13 -4
- package/docs/advanced/custom-pages.mdx +4 -2
- package/docs/advanced/graphql.mdx +84 -0
- package/docs/advanced/meta.ts +8 -1
- package/docs/configuration/ai.mdx +25 -3
- package/docs/configuration/index.mdx +24 -0
- package/docs/configuration/search.mdx +13 -1
- package/docs/configuration/seo.mdx +30 -3
- package/docs/configuration/theming.mdx +23 -0
- package/docs/content/components.mdx +15 -1
- package/docs/content/includes.mdx +68 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +25 -0
- package/docs/content/sources.mdx +42 -1
- package/docs/content/syntax.mdx +69 -1
- package/docs/content/versioning.mdx +15 -9
- package/docs/reference/cli.mdx +2 -1
- package/package.json +66 -57
- package/skills/blume-migrate/SKILL.md +16 -7
- package/skills/blume-migrate/references/docusaurus.md +5 -3
- package/skills/blume-migrate/references/fumadocs.md +10 -2
- package/skills/blume-migrate/references/mintlify.md +3 -2
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +1 -1
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/ask-data.ts +2 -1
- package/src/ai/component-markdown.ts +199 -36
- package/src/ai/llms.ts +93 -6
- package/src/ai/markdown.ts +2 -2
- package/src/ai/mcp/discovery.ts +10 -2
- package/src/ai/mcp/server.ts +74 -2
- package/src/astro/examples.ts +29 -2
- package/src/astro/generate.ts +282 -177
- package/src/astro/include-hmr.ts +81 -0
- package/src/astro/include-refresh.ts +0 -0
- package/src/astro/index.ts +10 -5
- package/src/astro/markdown-negotiation.ts +1 -1
- package/src/astro/runtime-modules.ts +196 -0
- package/src/astro/templates.ts +365 -113
- package/src/cli/commands/build.ts +91 -16
- package/src/cli/commands/dev.ts +6 -3
- package/src/cli/host-args.ts +18 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/init/questions.ts +1 -0
- package/src/cli/init/scaffold.ts +27 -4
- package/src/components/colors.ts +142 -0
- package/src/components/content/Badge.astro +5 -12
- package/src/components/content/Callout.astro +19 -36
- package/src/components/content/Card.astro +15 -21
- package/src/components/content/Component.astro +10 -1
- package/src/components/content/GithubInfo.astro +28 -9
- package/src/components/content/Tabs.astro +27 -5
- package/src/components/content/github-info.ts +20 -5
- package/src/components/copy-feedback.ts +93 -9
- package/src/components/dropdown-dismiss.ts +122 -0
- package/src/components/islands/ask-ai.tsx +4 -1
- package/src/components/islands/hooks.ts +3 -1
- package/src/components/layout/Fonts.astro +15 -8
- package/src/components/layout/Header.astro +44 -0
- package/src/components/layout/LanguageSwitcher.astro +9 -1
- package/src/components/layout/NavSelector.astro +12 -3
- package/src/components/layout/NavTree.astro +6 -18
- package/src/components/layout/PageActions.astro +54 -22
- package/src/components/layout/PageLayout.astro +2 -0
- package/src/components/layout/ReferenceLayout.astro +6 -1
- package/src/components/layout/RootLayout.astro +42 -15
- package/src/components/layout/Search.astro +36 -4
- package/src/components/layout/TableOfContents.astro +8 -2
- package/src/components/layout/head-scripts.ts +30 -1
- package/src/components/openapi/ApiOverview.astro +13 -3
- package/src/components/openapi/AsyncApiOperation.astro +7 -14
- package/src/components/openapi/GraphqlChip.astro +33 -0
- package/src/components/openapi/GraphqlFieldsTable.astro +111 -0
- package/src/components/openapi/GraphqlOperation.astro +186 -0
- package/src/components/openapi/GraphqlType.astro +154 -0
- package/src/components/openapi/MethodBadge.astro +3 -14
- package/src/components/openapi/Operation.astro +12 -5
- package/src/components/openapi/OperationPanel.astro +43 -0
- package/src/components/openapi/RequestPanel.astro +5 -10
- package/src/components/openapi/Responses.astro +1 -16
- package/src/components/openapi/graphql-helpers.ts +466 -0
- package/src/components/openapi/playground-client.ts +15 -0
- package/src/components/openapi/sample-panels.ts +45 -0
- package/src/components/openapi/snippets.ts +13 -35
- package/src/core/base-path.ts +11 -0
- package/src/core/config-input.ts +209 -2
- package/src/core/config.ts +6 -4
- package/src/core/content-assets.ts +15 -4
- package/src/core/data.ts +28 -3
- package/src/core/define-components.ts +2 -0
- package/src/core/diagnostics.ts +8 -0
- package/src/core/frontmatter.ts +20 -8
- package/src/core/github.ts +71 -0
- package/src/core/graph.ts +22 -8
- package/src/core/heading-markers.ts +96 -0
- package/src/core/i18n-ui.ts +12 -0
- package/src/core/includes.ts +633 -0
- package/src/core/last-modified.ts +36 -11
- package/src/core/links.ts +79 -13
- package/src/core/manifest.ts +10 -0
- package/src/core/meta.ts +2 -1
- package/src/core/nav-diagnostics.ts +11 -2
- package/src/core/navigation.ts +27 -6
- package/src/core/project-graph.ts +61 -9
- package/src/core/schema.ts +235 -36
- package/src/core/server-features.ts +5 -9
- package/src/core/sources/github-releases.ts +2 -2
- package/src/core/sources/normalize.ts +502 -115
- package/src/core/sources/notion.ts +43 -8
- package/src/core/sources/obsidian.ts +1038 -0
- package/src/core/sources/read.ts +36 -1
- package/src/core/sources/resolve.ts +34 -1
- package/src/core/sources/types.ts +28 -6
- package/src/core/sources/watch.ts +12 -8
- package/src/core/tsconfig-aliases.ts +48 -35
- package/src/core/types.ts +31 -2
- package/src/core/ui-packs/ar.ts +2 -0
- package/src/core/ui-packs/bg.ts +3 -0
- package/src/core/ui-packs/bn.ts +2 -0
- package/src/core/ui-packs/ca.ts +3 -0
- package/src/core/ui-packs/cs.ts +2 -0
- package/src/core/ui-packs/da.ts +2 -0
- package/src/core/ui-packs/de.ts +3 -0
- package/src/core/ui-packs/el.ts +3 -0
- package/src/core/ui-packs/es.ts +3 -0
- package/src/core/ui-packs/fa.ts +2 -0
- package/src/core/ui-packs/fi.ts +2 -0
- package/src/core/ui-packs/fr.ts +3 -0
- package/src/core/ui-packs/he.ts +2 -0
- package/src/core/ui-packs/hi.ts +2 -0
- package/src/core/ui-packs/hr.ts +3 -0
- package/src/core/ui-packs/hu.ts +3 -0
- package/src/core/ui-packs/id.ts +3 -0
- package/src/core/ui-packs/it.ts +2 -0
- package/src/core/ui-packs/ja.ts +3 -0
- package/src/core/ui-packs/ko.ts +3 -0
- package/src/core/ui-packs/nl.ts +3 -0
- package/src/core/ui-packs/no.ts +3 -0
- package/src/core/ui-packs/pl.ts +3 -0
- package/src/core/ui-packs/pt-br.ts +3 -0
- package/src/core/ui-packs/pt.ts +3 -0
- package/src/core/ui-packs/ro.ts +3 -0
- package/src/core/ui-packs/ru.ts +3 -0
- package/src/core/ui-packs/sk.ts +2 -0
- package/src/core/ui-packs/sr.ts +2 -0
- package/src/core/ui-packs/sv.ts +3 -0
- package/src/core/ui-packs/th.ts +2 -0
- package/src/core/ui-packs/tr.ts +3 -0
- package/src/core/ui-packs/uk.ts +3 -0
- package/src/core/ui-packs/vi.ts +2 -0
- package/src/core/ui-packs/zh-tw.ts +2 -0
- package/src/core/ui-packs/zh.ts +2 -0
- package/src/core/version-cut.ts +26 -6
- package/src/core/yaml.ts +26 -0
- package/src/deploy/function-bundle.ts +251 -0
- package/src/deploy/vercel-negotiation.ts +49 -6
- package/src/eval/schema.ts +3 -1
- package/src/markdown/code-title.ts +22 -16
- package/src/markdown/features.ts +21 -0
- package/src/markdown/fence-meta.ts +50 -0
- package/src/markdown/heading-anchors.ts +198 -37
- package/src/markdown/include.ts +247 -0
- package/src/markdown/index.ts +43 -34
- package/src/markdown/language-icon.ts +2 -2
- package/src/markdown/mdast.ts +7 -3
- package/src/markdown/ts2js.ts +264 -0
- package/src/og/card.ts +1 -1
- package/src/openapi/asyncapi.ts +4 -1
- package/src/openapi/graphql-build.ts +293 -0
- package/src/openapi/graphql.ts +212 -0
- package/src/openapi/model.ts +38 -5
- package/src/openapi/parse.ts +34 -0
- package/src/openapi/proxy.ts +30 -5
- package/src/openapi/references.ts +97 -13
- package/src/openapi/render-mdx.ts +66 -12
- package/src/openapi/scalar.ts +5 -16
- package/src/openapi/source.ts +91 -23
- package/src/registry/eject.ts +47 -17
- package/src/search/documents.ts +229 -37
- package/src/search/orama-index.ts +9 -5
- package/src/seo/jsonld.ts +293 -51
- package/src/theme/code-block-padding.ts +16 -0
- package/src/theme/entry.ts +67 -13
- package/src/theme/fonts.ts +189 -16
- package/src/theme/sources.ts +49 -0
- package/src/translate/prompts.ts +2 -0
- package/src/translate/run.ts +7 -0
- package/src/translate/work-list.ts +0 -0
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
buildSkillsIndex,
|
|
26
26
|
collectSkills,
|
|
27
27
|
} from "../../ai/skills.ts";
|
|
28
|
+
import type { SkillArtifact } from "../../ai/skills.ts";
|
|
28
29
|
import {
|
|
29
30
|
buildSignaturesDirectory,
|
|
30
31
|
SIGNATURES_DIRECTORY_PATH,
|
|
@@ -46,6 +47,11 @@ import {
|
|
|
46
47
|
injectWorkerNegotiation,
|
|
47
48
|
NEGOTIATION_WORKER_FILE,
|
|
48
49
|
} from "../../deploy/cloudflare-negotiation.ts";
|
|
50
|
+
import {
|
|
51
|
+
auditVercelFunctions,
|
|
52
|
+
blumeDependencyNames,
|
|
53
|
+
functionBundleVerdict,
|
|
54
|
+
} from "../../deploy/function-bundle.ts";
|
|
49
55
|
import { buildNetlifyHeaders } from "../../deploy/headers.ts";
|
|
50
56
|
import {
|
|
51
57
|
buildNetlifyRedirects,
|
|
@@ -195,29 +201,29 @@ export const emitHeaderFiles = async (
|
|
|
195
201
|
};
|
|
196
202
|
|
|
197
203
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
204
|
+
* Collect the Agent Skills `ai.skills` publishes, once per build, so both the
|
|
205
|
+
* skills surface and llms.txt (which lists them) read the same set. Empty
|
|
206
|
+
* when the feature is off, the directory is missing, nothing in it is
|
|
207
|
+
* publishable (each with a warning), or a user-shipped
|
|
208
|
+
* `public/.well-known/agent-skills/index.json` already owns the surface.
|
|
202
209
|
*/
|
|
203
|
-
const
|
|
210
|
+
const collectConfiguredSkills = async (
|
|
204
211
|
project: BlumeProject,
|
|
205
212
|
distDir: string
|
|
206
|
-
): Promise<
|
|
213
|
+
): Promise<SkillArtifact[]> => {
|
|
207
214
|
const configured = project.config.ai.skills;
|
|
208
215
|
if (!configured) {
|
|
209
|
-
return;
|
|
216
|
+
return [];
|
|
210
217
|
}
|
|
211
218
|
const dir = resolve(project.context.root, configured);
|
|
212
219
|
if (!existsSync(dir)) {
|
|
213
220
|
logger.warn(
|
|
214
221
|
`ai.skills points at "${configured}" (${dir}), which does not exist; no skills published.`
|
|
215
222
|
);
|
|
216
|
-
return;
|
|
223
|
+
return [];
|
|
217
224
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return;
|
|
225
|
+
if (existsSync(join(distDir, AGENT_SKILLS_DIR.slice(1), "index.json"))) {
|
|
226
|
+
return [];
|
|
221
227
|
}
|
|
222
228
|
const { skills, warnings } = await collectSkills(dir);
|
|
223
229
|
for (const warning of warnings) {
|
|
@@ -225,8 +231,26 @@ const emitAgentSkills = async (
|
|
|
225
231
|
}
|
|
226
232
|
if (skills.length === 0) {
|
|
227
233
|
logger.warn(`ai.skills: no publishable skills found in "${configured}".`);
|
|
234
|
+
}
|
|
235
|
+
return skills;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Publish the collected Agent Skills: copy each skill artifact under
|
|
240
|
+
* `.well-known/agent-skills/` and emit the discovery index. A user-shipped
|
|
241
|
+
* `public/.well-known/agent-skills/index.json` takes over the whole surface
|
|
242
|
+
* (the collector returns nothing then), matching every other generated
|
|
243
|
+
* artifact.
|
|
244
|
+
*/
|
|
245
|
+
const emitAgentSkills = async (
|
|
246
|
+
project: BlumeProject,
|
|
247
|
+
distDir: string,
|
|
248
|
+
skills: readonly SkillArtifact[]
|
|
249
|
+
): Promise<void> => {
|
|
250
|
+
if (skills.length === 0) {
|
|
228
251
|
return;
|
|
229
252
|
}
|
|
253
|
+
const outDir = join(distDir, AGENT_SKILLS_DIR.slice(1));
|
|
230
254
|
await Promise.all(
|
|
231
255
|
skills.map(async (skill) => {
|
|
232
256
|
const target = join(outDir, skill.path);
|
|
@@ -310,12 +334,18 @@ const emitVercelNegotiation = async (
|
|
|
310
334
|
// endpoint stamps it on dev/server-rendered responses itself.
|
|
311
335
|
const rawMarkdown = await buildRawMarkdown(project);
|
|
312
336
|
const home = rawMarkdown["/"];
|
|
337
|
+
// The Markdown 404 routes point at the prerendered `404.md`; only wire them
|
|
338
|
+
// when the build actually emitted it (a project that owns `/404` gets none).
|
|
339
|
+
const notFoundMarkdown = existsSync(
|
|
340
|
+
join(root, ".vercel", "output", "static", "404.md")
|
|
341
|
+
);
|
|
313
342
|
const injected = injectNegotiationRoutes(
|
|
314
343
|
await readFile(configPath, "utf-8"),
|
|
315
344
|
routePaths,
|
|
316
345
|
buildHomeLinkHeader(config, routePaths),
|
|
317
346
|
overrides,
|
|
318
|
-
home ? markdownTokenCount(agentMarkdown(home)) : undefined
|
|
347
|
+
home ? markdownTokenCount(agentMarkdown(home)) : undefined,
|
|
348
|
+
notFoundMarkdown
|
|
319
349
|
);
|
|
320
350
|
if (injected === null) {
|
|
321
351
|
logger.warn(
|
|
@@ -329,6 +359,38 @@ const emitVercelNegotiation = async (
|
|
|
329
359
|
);
|
|
330
360
|
};
|
|
331
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Refuse to ship a Vercel function bundle that would crash at runtime: a bare
|
|
364
|
+
* import the adapter's dependency trace silently dropped (see
|
|
365
|
+
* `deploy/function-bundle.ts`). A missing package that is one of Blume's own
|
|
366
|
+
* dependencies is fatal — the generated runtime imports it, so every request
|
|
367
|
+
* would die; a project's own external import is reported as a warning and left
|
|
368
|
+
* to the author.
|
|
369
|
+
*/
|
|
370
|
+
const checkVercelFunctionBundles = async (
|
|
371
|
+
outputDir: string,
|
|
372
|
+
root: string
|
|
373
|
+
): Promise<void> => {
|
|
374
|
+
const audits = await auditVercelFunctions(outputDir);
|
|
375
|
+
if (audits.length === 0) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const own = blumeDependencyNames();
|
|
379
|
+
let fatal = false;
|
|
380
|
+
for (const audit of audits) {
|
|
381
|
+
const verdict = functionBundleVerdict(audit, root, own);
|
|
382
|
+
if (verdict.fatal) {
|
|
383
|
+
fatal = true;
|
|
384
|
+
logger.error(verdict.message);
|
|
385
|
+
} else {
|
|
386
|
+
logger.warn(verdict.message);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (fatal) {
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
332
394
|
const warnCloudflareNegotiationSkipped = (): void =>
|
|
333
395
|
logger.warn(
|
|
334
396
|
"Could not wire Accept: text/markdown negotiation into dist/server/wrangler.json — raw Markdown stays available at the .md URLs."
|
|
@@ -559,7 +621,8 @@ export const isolatedStaticDir = (
|
|
|
559
621
|
*/
|
|
560
622
|
const publishLlmsFiles = async (
|
|
561
623
|
project: BlumeProject,
|
|
562
|
-
distDir: string
|
|
624
|
+
distDir: string,
|
|
625
|
+
skills: readonly SkillArtifact[]
|
|
563
626
|
): Promise<void> => {
|
|
564
627
|
const indexPath = join(distDir, "llms.txt");
|
|
565
628
|
const fullPath = join(distDir, "llms-full.txt");
|
|
@@ -568,7 +631,7 @@ const publishLlmsFiles = async (
|
|
|
568
631
|
if (!(writeIndex || writeFull)) {
|
|
569
632
|
return;
|
|
570
633
|
}
|
|
571
|
-
const { index, full } = await buildLlmsFiles(project);
|
|
634
|
+
const { index, full } = await buildLlmsFiles(project, { skills });
|
|
572
635
|
const writes: Promise<void>[] = [];
|
|
573
636
|
if (writeIndex) {
|
|
574
637
|
writes.push(writeFile(indexPath, index, "utf-8"));
|
|
@@ -613,8 +676,10 @@ const publishBuildArtifacts = async (
|
|
|
613
676
|
warn: (message) => logger.warn(message),
|
|
614
677
|
});
|
|
615
678
|
|
|
679
|
+
// Collected once: llms.txt lists the skills the build publishes below.
|
|
680
|
+
const skills = await collectConfiguredSkills(project, distDir);
|
|
616
681
|
if (project.config.ai.llmsTxt.enabled) {
|
|
617
|
-
await publishLlmsFiles(project, distDir);
|
|
682
|
+
await publishLlmsFiles(project, distDir, skills);
|
|
618
683
|
}
|
|
619
684
|
|
|
620
685
|
// A user's own public/ file (copied into dist by Astro) always wins.
|
|
@@ -652,7 +717,7 @@ const publishBuildArtifacts = async (
|
|
|
652
717
|
}
|
|
653
718
|
|
|
654
719
|
await emitWellKnownFiles(project.config, distDir);
|
|
655
|
-
await emitAgentSkills(project, distDir);
|
|
720
|
+
await emitAgentSkills(project, distDir, skills);
|
|
656
721
|
|
|
657
722
|
await emitRedirectFiles(project.config, distDir);
|
|
658
723
|
await emitHeaderFiles(project, distDir);
|
|
@@ -798,6 +863,15 @@ export const buildCommand = defineCommand({
|
|
|
798
863
|
// 100` exiting 0 without measuring anything would be a silent false pass
|
|
799
864
|
// in CI.
|
|
800
865
|
if (runtimeDir) {
|
|
866
|
+
if (
|
|
867
|
+
project.config.deployment.output === "server" &&
|
|
868
|
+
project.config.deployment.adapter === "vercel"
|
|
869
|
+
) {
|
|
870
|
+
await checkVercelFunctionBundles(
|
|
871
|
+
isolatedOutputDir(project.config, project.context),
|
|
872
|
+
root
|
|
873
|
+
);
|
|
874
|
+
}
|
|
801
875
|
await runClientAssetChecks(
|
|
802
876
|
isolatedStaticDir(project.config, project.context),
|
|
803
877
|
args
|
|
@@ -831,6 +905,7 @@ export const buildCommand = defineCommand({
|
|
|
831
905
|
}
|
|
832
906
|
|
|
833
907
|
if (project.config.deployment.output === "server" && adapter === "vercel") {
|
|
908
|
+
await checkVercelFunctionBundles(join(root, ".vercel", "output"), root);
|
|
834
909
|
await emitVercelNegotiation(project, markdownRoutePaths(project), root);
|
|
835
910
|
}
|
|
836
911
|
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -19,8 +19,9 @@ import { prepareProject } from "../prepare.ts";
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Resolve a `--host` flag value into what Astro/Vite's `server.host` expects.
|
|
22
|
-
* citty
|
|
23
|
-
* string and a bare `--host` parses as `""`
|
|
22
|
+
* citty has no mixed string/boolean arg type, so `host` is declared as a
|
|
23
|
+
* string and a bare `--host` parses as `""` (the CLI entry rewrites it to
|
|
24
|
+
* `--host=` first; see `host-args.ts`) — Node would bind all interfaces
|
|
24
25
|
* for `""`, but Vite's `resolveHostname` treats it as a literal hostname and
|
|
25
26
|
* prints malformed URLs like `http://:4321/`. Match Astro's own `--host`
|
|
26
27
|
* semantics instead: bare flag → `true` (bind all interfaces), `--host
|
|
@@ -219,7 +220,9 @@ export const devCommand = defineCommand({
|
|
|
219
220
|
}).on("all", regenerate);
|
|
220
221
|
const disposers = [
|
|
221
222
|
...project.sources.map((source) => source.watch?.(regenerate)),
|
|
222
|
-
() =>
|
|
223
|
+
() => {
|
|
224
|
+
void projectWatcher.close();
|
|
225
|
+
},
|
|
223
226
|
].filter((dispose) => dispose !== undefined);
|
|
224
227
|
|
|
225
228
|
const shutdown = async () => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite a bare `--host` in the raw argv to `--host=` before citty parses it.
|
|
3
|
+
*
|
|
4
|
+
* `host` is a string arg (citty has no mixed string/boolean type), and citty
|
|
5
|
+
* 0.2 parses with `node:util.parseArgs`, where a string option consumes the
|
|
6
|
+
* next token as its value even when that token is another flag: `blume dev
|
|
7
|
+
* --host --open` would bind the literal hostname "--open" and drop `--open`.
|
|
8
|
+
* The `--host=` spelling parses as `""` without touching its neighbor, which
|
|
9
|
+
* `normalizeHost` then maps to Astro's "bind all interfaces".
|
|
10
|
+
*/
|
|
11
|
+
export const normalizeHostArgs = (rawArgs: readonly string[]): string[] =>
|
|
12
|
+
rawArgs.map((arg, index) => {
|
|
13
|
+
if (arg !== "--host") {
|
|
14
|
+
return arg;
|
|
15
|
+
}
|
|
16
|
+
const next = rawArgs[index + 1];
|
|
17
|
+
return next === undefined || next.startsWith("-") ? "--host=" : arg;
|
|
18
|
+
});
|
package/src/cli/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { translateCommand } from "./commands/translate.ts";
|
|
|
17
17
|
import { validateCommand } from "./commands/validate.ts";
|
|
18
18
|
import { versionCommand } from "./commands/version.ts";
|
|
19
19
|
import { loadEnvFiles } from "./env.ts";
|
|
20
|
+
import { normalizeHostArgs } from "./host-args.ts";
|
|
20
21
|
import { reportInternalError } from "./internal-error.ts";
|
|
21
22
|
|
|
22
23
|
const main = defineCommand({
|
|
@@ -60,4 +61,4 @@ process.on("unhandledRejection", (error) => {
|
|
|
60
61
|
process.exit(1);
|
|
61
62
|
});
|
|
62
63
|
|
|
63
|
-
runMain(main);
|
|
64
|
+
runMain(main, { rawArgs: normalizeHostArgs(process.argv.slice(2)) });
|
|
@@ -111,6 +111,7 @@ export const collectAnswers = async (
|
|
|
111
111
|
message: "Where does your content live?",
|
|
112
112
|
options: [
|
|
113
113
|
{ hint: "Local .mdx files", label: "filesystem", value: "filesystem" },
|
|
114
|
+
{ hint: "An Obsidian vault", label: "obsidian", value: "obsidian" },
|
|
114
115
|
{
|
|
115
116
|
hint: "Changelog from GitHub Releases",
|
|
116
117
|
label: "github-releases",
|
package/src/cli/init/scaffold.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
|
15
15
|
/** The content-source kinds `init` can scaffold a config block for. */
|
|
16
16
|
export const SOURCE_KINDS = [
|
|
17
17
|
"filesystem",
|
|
18
|
+
"obsidian",
|
|
18
19
|
"github-releases",
|
|
19
20
|
"notion",
|
|
20
21
|
"sanity",
|
|
@@ -209,8 +210,12 @@ export const titleize = (raw: string): string => {
|
|
|
209
210
|
.join(" ");
|
|
210
211
|
};
|
|
211
212
|
|
|
212
|
-
/**
|
|
213
|
-
|
|
213
|
+
/**
|
|
214
|
+
* True when any selected source needs an explicit `content.sources` array —
|
|
215
|
+
* everything except `filesystem`, which is what the implicit default already
|
|
216
|
+
* desugars to.
|
|
217
|
+
*/
|
|
218
|
+
const needsExplicitSources = (sources: SourceKind[]): boolean =>
|
|
214
219
|
sources.some((source) => source !== "filesystem");
|
|
215
220
|
|
|
216
221
|
/**
|
|
@@ -239,6 +244,14 @@ const SOURCE_SNIPPETS = {
|
|
|
239
244
|
database: "your-database-id",
|
|
240
245
|
prefix: "notion",
|
|
241
246
|
},`,
|
|
247
|
+
obsidian: ` // An Obsidian vault, read in place. No export step, and no
|
|
248
|
+
// generated notes in your repo. Point \`vault\` at your vault directory,
|
|
249
|
+
// relative to this config file.
|
|
250
|
+
{
|
|
251
|
+
type: "obsidian",
|
|
252
|
+
vault: "vault",
|
|
253
|
+
prefix: "notes",
|
|
254
|
+
},`,
|
|
242
255
|
sanity: ` // Documents from a Sanity dataset. Private datasets read SANITY_TOKEN
|
|
243
256
|
// from the environment.
|
|
244
257
|
{
|
|
@@ -258,7 +271,7 @@ const SOURCE_SNIPPETS = {
|
|
|
258
271
|
const contentBlockFor = (answers: InitAnswers): string => {
|
|
259
272
|
const sources =
|
|
260
273
|
answers.sources.length === 0 ? ["filesystem" as const] : answers.sources;
|
|
261
|
-
if (!
|
|
274
|
+
if (!needsExplicitSources(sources)) {
|
|
262
275
|
return answers.contentDir === "docs"
|
|
263
276
|
? ""
|
|
264
277
|
: `
|
|
@@ -267,7 +280,7 @@ const contentBlockFor = (answers: InitAnswers): string => {
|
|
|
267
280
|
},`;
|
|
268
281
|
}
|
|
269
282
|
// Explicit sources replace the implicit filesystem desugar, so the local
|
|
270
|
-
// content dir must be listed alongside the
|
|
283
|
+
// content dir must be listed alongside the other sources to stay included.
|
|
271
284
|
const entries = SOURCE_KINDS.filter((kind) => sources.includes(kind)).map(
|
|
272
285
|
(kind) =>
|
|
273
286
|
kind === "filesystem"
|
|
@@ -328,6 +341,16 @@ export const buildPlan = (
|
|
|
328
341
|
.map((file) => ({ ...file, path: join(root, file.path) }))
|
|
329
342
|
);
|
|
330
343
|
}
|
|
344
|
+
// The obsidian snippet points at `vault/`; seed the directory with a first
|
|
345
|
+
// note so the scaffolded project passes the source's `validate()` and boots
|
|
346
|
+
// before the user has opened Obsidian at all.
|
|
347
|
+
if (answers.sources.includes("obsidian")) {
|
|
348
|
+
files.push({
|
|
349
|
+
content:
|
|
350
|
+
"# Welcome\n\nThis folder is read by Blume's `obsidian` source. Open it as a vault in Obsidian and write notes — `[[Wikilinks]]` become site links.\n",
|
|
351
|
+
path: join(root, "vault", "Welcome.md"),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
331
354
|
return files;
|
|
332
355
|
};
|
|
333
356
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color classes shared by every component that draws a label over a tint of
|
|
3
|
+
* its own hue — method badges, response status chips, `<Badge>`, the sidebar's
|
|
4
|
+
* method badges — and by the typed callout/card icons. One table so the pairs
|
|
5
|
+
* can't drift between components.
|
|
6
|
+
*
|
|
7
|
+
* Contrast rule (light mode): text over a 15% tint of its hue must clear 4.5:1,
|
|
8
|
+
* the WCAG AA bar for text this size. `-700` does for most hues; green and
|
|
9
|
+
* orange need `-800`, the way yellow already does over its 20% tint. Dark mode
|
|
10
|
+
* clears the bar at `-300` throughout. Check any hue added here.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Hue =
|
|
14
|
+
| "blue"
|
|
15
|
+
| "green"
|
|
16
|
+
| "orange"
|
|
17
|
+
| "purple"
|
|
18
|
+
| "red"
|
|
19
|
+
| "teal"
|
|
20
|
+
| "violet"
|
|
21
|
+
| "yellow";
|
|
22
|
+
|
|
23
|
+
/** Label over a translucent tint of the same hue. */
|
|
24
|
+
export const TINT = {
|
|
25
|
+
blue: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
|
|
26
|
+
green: "bg-green-500/15 text-green-800 dark:text-green-300",
|
|
27
|
+
orange: "bg-orange-500/15 text-orange-800 dark:text-orange-300",
|
|
28
|
+
purple: "bg-purple-500/15 text-purple-700 dark:text-purple-300",
|
|
29
|
+
red: "bg-red-500/15 text-red-700 dark:text-red-300",
|
|
30
|
+
teal: "bg-teal-500/15 text-teal-700 dark:text-teal-300",
|
|
31
|
+
violet: "bg-violet-500/15 text-violet-700 dark:text-violet-300",
|
|
32
|
+
yellow: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
|
|
33
|
+
} satisfies Record<Hue, string>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Label with a translucent border of the same hue and no fill. The label
|
|
37
|
+
* inherits whatever surface it sits on (a callout, a card, the muted panel), so
|
|
38
|
+
* it is held to the same values as `TINT`: green at `-700` is 4.47:1 on the
|
|
39
|
+
* default muted surface.
|
|
40
|
+
*/
|
|
41
|
+
export const STROKE = {
|
|
42
|
+
blue: "border-blue-500/40 text-blue-700 dark:text-blue-300",
|
|
43
|
+
green: "border-green-500/40 text-green-800 dark:text-green-300",
|
|
44
|
+
orange: "border-orange-500/40 text-orange-800 dark:text-orange-300",
|
|
45
|
+
purple: "border-purple-500/40 text-purple-700 dark:text-purple-300",
|
|
46
|
+
red: "border-red-500/40 text-red-700 dark:text-red-300",
|
|
47
|
+
teal: "border-teal-500/40 text-teal-700 dark:text-teal-300",
|
|
48
|
+
violet: "border-violet-500/40 text-violet-700 dark:text-violet-300",
|
|
49
|
+
yellow: "border-yellow-500/40 text-yellow-800 dark:text-yellow-300",
|
|
50
|
+
} satisfies Record<Hue, string>;
|
|
51
|
+
|
|
52
|
+
/** The neutral fallback every table falls through to. */
|
|
53
|
+
export const MUTED = "bg-muted text-muted-foreground";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* HTTP methods, AsyncAPI actions, and GraphQL root-field kinds, as shown on an
|
|
57
|
+
* operation's badge and in a reference's sidebar. Type-page kinds fall through
|
|
58
|
+
* to the muted default so operation badges stay the loud ones.
|
|
59
|
+
*/
|
|
60
|
+
export const METHOD_COLORS = {
|
|
61
|
+
DELETE: TINT.red,
|
|
62
|
+
GET: TINT.green,
|
|
63
|
+
HEAD: MUTED,
|
|
64
|
+
MUTATION: TINT.blue,
|
|
65
|
+
OPTIONS: MUTED,
|
|
66
|
+
PATCH: TINT.yellow,
|
|
67
|
+
POST: TINT.blue,
|
|
68
|
+
PUT: TINT.orange,
|
|
69
|
+
QUERY: TINT.green,
|
|
70
|
+
RECEIVE: TINT.teal,
|
|
71
|
+
SEND: TINT.violet,
|
|
72
|
+
SUBSCRIPTION: TINT.violet,
|
|
73
|
+
} satisfies Record<string, string>;
|
|
74
|
+
|
|
75
|
+
const isMethod = (key: string): key is keyof typeof METHOD_COLORS =>
|
|
76
|
+
Object.hasOwn(METHOD_COLORS, key);
|
|
77
|
+
|
|
78
|
+
export const methodColor = (method: string): string => {
|
|
79
|
+
const key = method.toUpperCase();
|
|
80
|
+
return isMethod(key) ? METHOD_COLORS[key] : MUTED;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Response status chips, by the status code's class. */
|
|
84
|
+
export const statusColor = (status: string): string => {
|
|
85
|
+
if (status.startsWith("2")) {
|
|
86
|
+
return TINT.green;
|
|
87
|
+
}
|
|
88
|
+
if (status.startsWith("3")) {
|
|
89
|
+
return TINT.blue;
|
|
90
|
+
}
|
|
91
|
+
if (status.startsWith("4")) {
|
|
92
|
+
return TINT.orange;
|
|
93
|
+
}
|
|
94
|
+
if (status.startsWith("5")) {
|
|
95
|
+
return TINT.red;
|
|
96
|
+
}
|
|
97
|
+
return MUTED;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The small `deprecated` label beside an operation's path or field name. It
|
|
102
|
+
* draws on the page background, where orange needs `-700` for 4.5:1.
|
|
103
|
+
*/
|
|
104
|
+
export const DEPRECATED_LABEL_CLASS =
|
|
105
|
+
"font-medium text-[0.625rem] text-orange-700 uppercase tracking-wide dark:text-orange-400";
|
|
106
|
+
|
|
107
|
+
/** The typed admonitions `<Callout>` and `<Card>` render; `check` is an alias of `success`. */
|
|
108
|
+
export type AdmonitionType =
|
|
109
|
+
| "info"
|
|
110
|
+
| "note"
|
|
111
|
+
| "tip"
|
|
112
|
+
| "success"
|
|
113
|
+
| "warning"
|
|
114
|
+
| "danger";
|
|
115
|
+
|
|
116
|
+
export const admonitionType = (
|
|
117
|
+
type: AdmonitionType | "check"
|
|
118
|
+
): AdmonitionType => (type === "check" ? "success" : type);
|
|
119
|
+
|
|
120
|
+
export const ADMONITION_ICON = {
|
|
121
|
+
danger: "circle-x",
|
|
122
|
+
info: "info",
|
|
123
|
+
note: "info",
|
|
124
|
+
success: "circle-check",
|
|
125
|
+
tip: "lightbulb",
|
|
126
|
+
warning: "triangle-alert",
|
|
127
|
+
} satisfies Record<AdmonitionType, string>;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* An admonition's icon names its type, so it is meaningful UI held to the 3:1
|
|
131
|
+
* non-text bar against the tint behind it: green and amber need `-700` over
|
|
132
|
+
* their 10% tint, and the note icon is full-strength muted-foreground (at 70%
|
|
133
|
+
* it fell under the bar on the muted surface).
|
|
134
|
+
*/
|
|
135
|
+
export const ADMONITION_ICON_CLASS = {
|
|
136
|
+
danger: "text-red-600 dark:text-red-400",
|
|
137
|
+
info: "text-blue-600 dark:text-blue-400",
|
|
138
|
+
note: "text-muted-foreground",
|
|
139
|
+
success: "text-green-700 dark:text-green-400",
|
|
140
|
+
tip: "text-accent",
|
|
141
|
+
warning: "text-amber-700 dark:text-amber-400",
|
|
142
|
+
} satisfies Record<AdmonitionType, string>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { STROKE, TINT } from "../colors.ts";
|
|
2
3
|
import Icon from "../Icon.astro";
|
|
3
4
|
|
|
4
5
|
type BadgeVariant = "default" | "accent" | "success" | "warning" | "danger";
|
|
@@ -44,34 +45,26 @@ const colorName =
|
|
|
44
45
|
}[variant];
|
|
45
46
|
const customColor = colorName.startsWith("#") ? colorName : null;
|
|
46
47
|
|
|
48
|
+
// The hue rows come from the shared table (see colors.ts for the contrast
|
|
49
|
+
// rule); the neutral and surface rows are Badge's own.
|
|
47
50
|
const filledColorClass: Record<string, string> = {
|
|
48
|
-
|
|
51
|
+
...TINT,
|
|
49
52
|
gray: "bg-muted text-muted-foreground",
|
|
50
|
-
green: "bg-green-500/15 text-green-700 dark:text-green-300",
|
|
51
|
-
orange: "bg-orange-500/15 text-orange-700 dark:text-orange-300",
|
|
52
|
-
purple: "bg-purple-500/15 text-purple-700 dark:text-purple-300",
|
|
53
|
-
red: "bg-red-500/15 text-red-700 dark:text-red-300",
|
|
54
53
|
surface: "bg-background text-muted-foreground ring-1 ring-border",
|
|
55
54
|
"surface-destructive":
|
|
56
55
|
"bg-background text-red-700 ring-1 ring-red-500/30 dark:text-red-300",
|
|
57
56
|
white: "bg-white text-muted-foreground ring-1 ring-border dark:bg-white/10",
|
|
58
57
|
"white-destructive":
|
|
59
58
|
"bg-white text-red-700 ring-1 ring-red-500/30 dark:bg-white/10 dark:text-red-300",
|
|
60
|
-
yellow: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
|
|
61
59
|
};
|
|
62
60
|
|
|
63
61
|
const strokeColorClass: Record<string, string> = {
|
|
64
|
-
|
|
62
|
+
...STROKE,
|
|
65
63
|
gray: "border-border text-muted-foreground",
|
|
66
|
-
green: "border-green-500/40 text-green-700 dark:text-green-300",
|
|
67
|
-
orange: "border-orange-500/40 text-orange-700 dark:text-orange-300",
|
|
68
|
-
purple: "border-purple-500/40 text-purple-700 dark:text-purple-300",
|
|
69
|
-
red: "border-red-500/40 text-red-700 dark:text-red-300",
|
|
70
64
|
surface: "border-border text-muted-foreground",
|
|
71
65
|
"surface-destructive": "border-red-500/40 text-red-700 dark:text-red-300",
|
|
72
66
|
white: "border-border text-muted-foreground",
|
|
73
67
|
"white-destructive": "border-red-500/40 text-red-700 dark:text-red-300",
|
|
74
|
-
yellow: "border-yellow-500/40 text-yellow-800 dark:text-yellow-300",
|
|
75
68
|
};
|
|
76
69
|
|
|
77
70
|
const sizeClass: Record<BadgeSize, string> = {
|
|
@@ -1,64 +1,47 @@
|
|
|
1
1
|
---
|
|
2
|
+
import {
|
|
3
|
+
ADMONITION_ICON,
|
|
4
|
+
ADMONITION_ICON_CLASS,
|
|
5
|
+
type AdmonitionType,
|
|
6
|
+
admonitionType,
|
|
7
|
+
} from "../colors.ts";
|
|
2
8
|
import Icon from "../Icon.astro";
|
|
3
9
|
|
|
4
|
-
type CalloutType =
|
|
5
|
-
| "info"
|
|
6
|
-
| "note"
|
|
7
|
-
| "tip"
|
|
8
|
-
| "success"
|
|
9
|
-
| "check"
|
|
10
|
-
| "warning"
|
|
11
|
-
| "danger";
|
|
12
|
-
|
|
13
10
|
interface Props {
|
|
14
|
-
type?:
|
|
11
|
+
type?: AdmonitionType | "check";
|
|
15
12
|
title?: string;
|
|
16
13
|
icon?: unknown;
|
|
17
14
|
color?: string;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
const { color, icon, type = "info", title } = Astro.props;
|
|
18
|
+
// `check` is an alias of `success`; fold it once so the tables need one row.
|
|
19
|
+
const kind = admonitionType(type);
|
|
21
20
|
|
|
22
|
-
const
|
|
23
|
-
check: "circle-check",
|
|
24
|
-
danger: "circle-x",
|
|
25
|
-
info: "info",
|
|
26
|
-
note: "info",
|
|
27
|
-
success: "circle-check",
|
|
28
|
-
tip: "lightbulb",
|
|
29
|
-
warning: "triangle-alert",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const variantClass: Record<CalloutType, string> = {
|
|
33
|
-
check: "border-green-500/25 bg-green-500/10",
|
|
21
|
+
const variantClass: Record<AdmonitionType, string> = {
|
|
34
22
|
danger: "border-red-500/25 bg-red-500/10",
|
|
35
23
|
info: "border-blue-500/25 bg-blue-500/10",
|
|
36
24
|
note: "border-border bg-muted",
|
|
37
25
|
success: "border-green-500/25 bg-green-500/10",
|
|
38
|
-
|
|
26
|
+
// 6% rather than the 10% the other variants use: this tint comes from the
|
|
27
|
+
// user-configurable accent, whose default is near black, and muted body
|
|
28
|
+
// text on a 10% black tint sits at 4.25:1. 6% clears WCAG AA (4.5:1)
|
|
29
|
+
// against any accent — 4.62:1 worst case on pure black.
|
|
30
|
+
tip: "border-accent/25 bg-accent/6",
|
|
39
31
|
warning: "border-amber-500/25 bg-amber-500/10",
|
|
40
32
|
};
|
|
41
|
-
|
|
42
|
-
const iconClass: Record<CalloutType, string> = {
|
|
43
|
-
check: "text-green-600 dark:text-green-400",
|
|
44
|
-
danger: "text-red-600 dark:text-red-400",
|
|
45
|
-
info: "text-blue-600 dark:text-blue-400",
|
|
46
|
-
note: "text-muted-foreground/70",
|
|
47
|
-
success: "text-green-600 dark:text-green-400",
|
|
48
|
-
tip: "text-accent",
|
|
49
|
-
warning: "text-amber-600 dark:text-amber-400",
|
|
50
|
-
};
|
|
51
33
|
---
|
|
52
34
|
|
|
53
35
|
<aside
|
|
54
36
|
class:list={[
|
|
55
37
|
"not-prose my-5 flex flex-row items-start gap-2 rounded-blume border px-4 py-3 font-sans text-muted-foreground text-sm leading-6 [&_a]:underline [&_a]:decoration-current/50 [&_code]:font-mono",
|
|
56
|
-
color ? "bg-background" : variantClass[
|
|
38
|
+
color ? "bg-background" : variantClass[kind],
|
|
57
39
|
]}
|
|
40
|
+
data-blume-callout={kind}
|
|
58
41
|
style={color ? `border:1px solid ${color};color:${color}` : undefined}
|
|
59
42
|
>
|
|
60
|
-
<span class:list={["mt-0.5 shrink-0", color ? "" :
|
|
61
|
-
<Icon color={color} icon={icon ??
|
|
43
|
+
<span class:list={["mt-0.5 shrink-0", color ? "" : ADMONITION_ICON_CLASS[kind]]}>
|
|
44
|
+
<Icon color={color} icon={icon ?? ADMONITION_ICON[kind]} size={16} />
|
|
62
45
|
</span>
|
|
63
46
|
{/* The global prose rule leaks a 1rem margin onto these paragraphs/lists even
|
|
64
47
|
though the callout is not-prose; with a title the body isn't the first
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
+
import {
|
|
3
|
+
ADMONITION_ICON,
|
|
4
|
+
ADMONITION_ICON_CLASS,
|
|
5
|
+
admonitionType,
|
|
6
|
+
} from "../colors.ts";
|
|
2
7
|
import Icon from "../Icon.astro";
|
|
3
8
|
import { withBase } from "../islands/base-path.ts";
|
|
4
9
|
import { contentHref } from "./base-href.ts";
|
|
@@ -22,31 +27,20 @@ const external = href?.startsWith("http");
|
|
|
22
27
|
const isHorizontal = horizontal === true || horizontal === "true";
|
|
23
28
|
const showArrow =
|
|
24
29
|
href && arrow === undefined ? external : arrow === true || arrow === "true";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
note: "info",
|
|
30
|
-
tip: "lightbulb",
|
|
31
|
-
warning: "triangle-alert",
|
|
32
|
-
};
|
|
33
|
-
const iconClass = {
|
|
34
|
-
check: "text-green-600 dark:text-green-400",
|
|
35
|
-
danger: "text-red-600 dark:text-red-400",
|
|
36
|
-
info: "text-blue-600 dark:text-blue-400",
|
|
37
|
-
note: "text-muted-foreground/70",
|
|
38
|
-
tip: "text-accent",
|
|
39
|
-
warning: "text-amber-600 dark:text-amber-400",
|
|
40
|
-
};
|
|
41
|
-
const iconName = icon ?? (type ? iconByType[type] : undefined);
|
|
30
|
+
// The typed icon shares Callout's table (and its contrast rule); the surface
|
|
31
|
+
// tints are Card's own, a shade lighter than a callout's.
|
|
32
|
+
const kind = type ? admonitionType(type) : undefined;
|
|
33
|
+
const iconName = icon ?? (kind ? ADMONITION_ICON[kind] : undefined);
|
|
42
34
|
const variantClass = {
|
|
43
|
-
check: "border-green-500/30 bg-green-500/10",
|
|
44
35
|
danger: "border-red-500/30 bg-red-500/10",
|
|
45
36
|
info: "border-blue-500/30 bg-blue-500/10",
|
|
46
37
|
note: "border-border bg-muted/40",
|
|
47
|
-
|
|
38
|
+
success: "border-green-500/30 bg-green-500/10",
|
|
39
|
+
// 6% for the same reason as Callout: the accent defaults to near black, and
|
|
40
|
+
// muted body text on a 10% black tint misses WCAG AA.
|
|
41
|
+
tip: "border-accent/30 bg-accent/6",
|
|
48
42
|
warning: "border-amber-500/30 bg-amber-500/10",
|
|
49
|
-
}[
|
|
43
|
+
}[kind ?? "note"];
|
|
50
44
|
---
|
|
51
45
|
|
|
52
46
|
<Tag
|
|
@@ -75,7 +69,7 @@ const variantClass = {
|
|
|
75
69
|
<div class="p-5">
|
|
76
70
|
{
|
|
77
71
|
iconName && (
|
|
78
|
-
<div class:list={["mb-2.5", color ? "" :
|
|
72
|
+
<div class:list={["mb-2.5", color ? "" : kind ? ADMONITION_ICON_CLASS[kind] : "text-accent"]}>
|
|
79
73
|
<Icon color={color} name={iconName} size={20} />
|
|
80
74
|
</div>
|
|
81
75
|
)
|