nebula-cms 0.1.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/.claude/settings.local.json +42 -0
- package/.github/workflows/ci.yml +31 -0
- package/.mcp.json +12 -0
- package/.prettierignore +5 -0
- package/.prettierrc.cjs +22 -0
- package/AGENTS.md +183 -0
- package/LICENSE +201 -0
- package/README.md +128 -0
- package/package.json +74 -0
- package/playground/.claude/settings.local.json +5 -0
- package/playground/astro.config.mjs +7 -0
- package/playground/node_modules/.bin/astro +21 -0
- package/playground/node_modules/.bin/rollup +21 -0
- package/playground/node_modules/.bin/tsc +21 -0
- package/playground/node_modules/.bin/tsserver +21 -0
- package/playground/node_modules/.bin/vite +21 -0
- package/playground/node_modules/.vite/_svelte_metadata.json +1 -0
- package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +80 -0
- package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +7 -0
- package/playground/node_modules/.vite/deps/_metadata.json +184 -0
- package/playground/node_modules/.vite/deps/astro___aria-query.js +6776 -0
- package/playground/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
- package/playground/node_modules/.vite/deps/astro___axobject-query.js +3754 -0
- package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
- package/playground/node_modules/.vite/deps/astro___html-escaper.js +34 -0
- package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
- package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js +1005 -0
- package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +8 -0
- package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +21 -0
- package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +223 -0
- package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js +204 -0
- package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js +27 -0
- package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js +4376 -0
- package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js +688 -0
- package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js +5099 -0
- package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +23 -0
- package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +148 -0
- package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +7 -0
- package/playground/node_modules/.vite/deps/package.json +3 -0
- package/playground/node_modules/.vite/deps/smol-toml.js +843 -0
- package/playground/node_modules/.vite/deps/smol-toml.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte.js +55 -0
- package/playground/node_modules/.vite/deps/svelte.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte___clsx.js +9 -0
- package/playground/node_modules/.vite/deps/svelte___clsx.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_animate.js +57 -0
- package/playground/node_modules/.vite/deps/svelte_animate.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_attachments.js +15 -0
- package/playground/node_modules/.vite/deps/svelte_attachments.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_easing.js +67 -0
- package/playground/node_modules/.vite/deps/svelte_easing.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_events.js +11 -0
- package/playground/node_modules/.vite/deps/svelte_events.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal.js +5 -0
- package/playground/node_modules/.vite/deps/svelte_internal.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_client.js +402 -0
- package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +10 -0
- package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +8 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +8 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +8 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_legacy.js +35 -0
- package/playground/node_modules/.vite/deps/svelte_legacy.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_motion.js +545 -0
- package/playground/node_modules/.vite/deps/svelte_motion.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity.js +29 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +127 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_store.js +103 -0
- package/playground/node_modules/.vite/deps/svelte_store.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_transition.js +208 -0
- package/playground/node_modules/.vite/deps/svelte_transition.js.map +7 -0
- package/playground/package.json +16 -0
- package/playground/pnpm-lock.yaml +3167 -0
- package/playground/src/content/authors/jane-doe.json +8 -0
- package/playground/src/content/config/build.toml +2 -0
- package/playground/src/content/courses/web-fundamentals.json +29 -0
- package/playground/src/content/docs/advanced.mdx +6 -0
- package/playground/src/content/docs/intro.md +6 -0
- package/playground/src/content/guides/getting-started.mdx +6 -0
- package/playground/src/content/posts/hello-world.md +7 -0
- package/playground/src/content/products/t-shirt.json +16 -0
- package/playground/src/content/recipes/pancakes.mdoc +8 -0
- package/playground/src/content/settings/site.yml +2 -0
- package/playground/src/content.config.ts +198 -0
- package/playground/src/env.d.ts +1 -0
- package/playground/src/pages/index.astro +11 -0
- package/playground/src/pages/nebula.astro +14 -0
- package/pnpm-workspace.yaml +2 -0
- package/scripts/subset-icons.mjs +178 -0
- package/src/astro/index.ts +295 -0
- package/src/client/Admin.svelte +283 -0
- package/src/client/components/BackendPicker.svelte +291 -0
- package/src/client/components/DraftChip.svelte +46 -0
- package/src/client/components/MetadataForm.svelte +56 -0
- package/src/client/components/ThemeToggle.svelte +18 -0
- package/src/client/components/dialogs/DeleteDraftDialog.svelte +51 -0
- package/src/client/components/dialogs/FilenameDialog.svelte +129 -0
- package/src/client/components/editor/EditorPane.svelte +227 -0
- package/src/client/components/editor/EditorTabs.svelte +81 -0
- package/src/client/components/editor/EditorToolbar.svelte +131 -0
- package/src/client/components/editor/FormatSelector.svelte +66 -0
- package/src/client/components/editor/Toolbar.svelte +17 -0
- package/src/client/components/fields/ArrayField.svelte +339 -0
- package/src/client/components/fields/ArrayItem.svelte +325 -0
- package/src/client/components/fields/BooleanField.svelte +114 -0
- package/src/client/components/fields/DateField.svelte +82 -0
- package/src/client/components/fields/EnumField.svelte +74 -0
- package/src/client/components/fields/FieldWrapper.svelte +96 -0
- package/src/client/components/fields/NumberField.svelte +99 -0
- package/src/client/components/fields/ObjectField.svelte +121 -0
- package/src/client/components/fields/SchemaField.svelte +107 -0
- package/src/client/components/fields/StringField.svelte +104 -0
- package/src/client/components/sidebar/AdminSidebar.svelte +339 -0
- package/src/client/components/sidebar/AdminSidebarSort.svelte +123 -0
- package/src/client/css/a11y.css +14 -0
- package/src/client/css/btn.css +113 -0
- package/src/client/css/dialog.css +29 -0
- package/src/client/css/field-input.css +39 -0
- package/src/client/css/reset.css +59 -0
- package/src/client/css/theme.css +77 -0
- package/src/client/index.ts +1 -0
- package/src/client/js/drafts/merge.svelte.ts +121 -0
- package/src/client/js/drafts/ops.svelte.ts +227 -0
- package/src/client/js/drafts/storage.ts +108 -0
- package/src/client/js/drafts/workers/diff.ts +40 -0
- package/src/client/js/editor/editor.svelte.ts +343 -0
- package/src/client/js/editor/languages.ts +98 -0
- package/src/client/js/editor/link-wrap.ts +45 -0
- package/src/client/js/editor/markdown-shortcuts.ts +261 -0
- package/src/client/js/handlers/admin.ts +246 -0
- package/src/client/js/state/dialogs.svelte.ts +35 -0
- package/src/client/js/state/router.svelte.ts +156 -0
- package/src/client/js/state/schema.svelte.ts +140 -0
- package/src/client/js/state/state.svelte.ts +334 -0
- package/src/client/js/state/theme.svelte.ts +173 -0
- package/src/client/js/storage/adapter.ts +102 -0
- package/src/client/js/storage/client.ts +150 -0
- package/src/client/js/storage/db.ts +36 -0
- package/src/client/js/storage/fsa.ts +110 -0
- package/src/client/js/storage/github.ts +297 -0
- package/src/client/js/storage/storage.ts +83 -0
- package/src/client/js/storage/workers/frontmatter.ts +320 -0
- package/src/client/js/storage/workers/storage.ts +177 -0
- package/src/client/js/storage/workers/toml-parser.ts +106 -0
- package/src/client/js/storage/workers/yaml-parser.ts +132 -0
- package/src/client/js/utils/file-types.ts +192 -0
- package/src/client/js/utils/format.ts +16 -0
- package/src/client/js/utils/frontmatter.ts +38 -0
- package/src/client/js/utils/schema-utils.ts +295 -0
- package/src/client/js/utils/slug.ts +18 -0
- package/src/client/js/utils/sort.ts +84 -0
- package/src/client/js/utils/stable-stringify.ts +27 -0
- package/src/client/js/utils/url-utils.ts +38 -0
- package/src/types.ts +25 -0
- package/src/virtual.d.ts +22 -0
- package/svelte.config.js +4 -0
- package/tests/astro/build.test.ts +63 -0
- package/tests/astro/index.test.ts +689 -0
- package/tests/client/components/Admin.test.ts +446 -0
- package/tests/client/components/BackendPicker.test.ts +239 -0
- package/tests/client/components/DraftChip.test.ts +53 -0
- package/tests/client/components/MetadataForm.test.ts +164 -0
- package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +91 -0
- package/tests/client/components/dialogs/FilenameDialog.test.ts +209 -0
- package/tests/client/components/dialogs/dialog-stubs.ts +19 -0
- package/tests/client/components/editor/EditorPane.test.ts +100 -0
- package/tests/client/components/editor/EditorTabs.test.ts +253 -0
- package/tests/client/components/editor/EditorToolbar.test.ts +252 -0
- package/tests/client/components/editor/fixtures.ts +31 -0
- package/tests/client/components/fields/ArrayField.test.ts +197 -0
- package/tests/client/components/fields/BooleanField.test.ts +206 -0
- package/tests/client/components/fields/DateField.test.ts +210 -0
- package/tests/client/components/fields/EnumField.test.ts +246 -0
- package/tests/client/components/fields/NumberField.test.ts +240 -0
- package/tests/client/components/fields/ObjectField.test.ts +157 -0
- package/tests/client/components/fields/SchemaField.test.ts +190 -0
- package/tests/client/components/fields/StringField.test.ts +223 -0
- package/tests/client/components/sidebar/AdminSidebar.test.ts +285 -0
- package/tests/client/components/sidebar/AdminSidebarSort.test.ts +135 -0
- package/tests/client/components/sidebar/sort-mock.ts +23 -0
- package/tests/client/js/drafts/fixtures.ts +22 -0
- package/tests/client/js/drafts/merge.test.ts +282 -0
- package/tests/client/js/drafts/ops.test.ts +658 -0
- package/tests/client/js/drafts/storage.test.ts +200 -0
- package/tests/client/js/drafts/workers/diff.test.ts +165 -0
- package/tests/client/js/editor/editor.test.ts +616 -0
- package/tests/client/js/editor/link-wrap.test.ts +225 -0
- package/tests/client/js/editor/markdown-shortcuts.test.ts +370 -0
- package/tests/client/js/handlers/admin.test.ts +467 -0
- package/tests/client/js/state/router.test.ts +619 -0
- package/tests/client/js/state/schema.test.ts +266 -0
- package/tests/client/js/state/state.test.ts +328 -0
- package/tests/client/js/storage/adapter.test.ts +115 -0
- package/tests/client/js/storage/client.test.ts +250 -0
- package/tests/client/js/storage/db.test.ts +59 -0
- package/tests/client/js/storage/fsa.test.ts +284 -0
- package/tests/client/js/storage/github.test.ts +349 -0
- package/tests/client/js/storage/mock-port.ts +95 -0
- package/tests/client/js/storage/storage.test.ts +77 -0
- package/tests/client/js/storage/workers/frontmatter.test.ts +479 -0
- package/tests/client/js/storage/workers/storage.test.ts +299 -0
- package/tests/client/js/storage/workers/toml-parser.test.ts +169 -0
- package/tests/client/js/storage/workers/yaml-parser.test.ts +168 -0
- package/tests/client/js/utils/file-types.test.ts +268 -0
- package/tests/client/js/utils/frontmatter.test.ts +87 -0
- package/tests/client/js/utils/schema-utils.test.ts +318 -0
- package/tests/client/js/utils/slug.test.ts +58 -0
- package/tests/client/js/utils/sort.test.ts +276 -0
- package/tests/client/js/utils/stable-stringify.test.ts +68 -0
- package/tests/client/js/utils/url-utils.test.ts +70 -0
- package/tests/e2e/backend-connection.test.ts +301 -0
- package/tests/e2e/draft-lifecycle.test.ts +388 -0
- package/tests/e2e/editing.test.ts +355 -0
- package/tests/e2e/github-adapter.test.ts +330 -0
- package/tests/e2e/helpers/mock-adapter.ts +166 -0
- package/tests/e2e/helpers/test-app.ts +155 -0
- package/tests/e2e/navigation.test.ts +358 -0
- package/tests/e2e/publishing.test.ts +345 -0
- package/tests/e2e/unsaved-changes.test.ts +317 -0
- package/tests/setup.ts +2 -0
- package/tests/stubs/codemirror.ts +197 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +178 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Web Fundamentals",
|
|
3
|
+
"instructor": {
|
|
4
|
+
"name": "Dr. Smith",
|
|
5
|
+
"credentials": [
|
|
6
|
+
{
|
|
7
|
+
"institution": "MIT",
|
|
8
|
+
"year": 2015
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"modules": [
|
|
13
|
+
{
|
|
14
|
+
"title": "HTML Basics",
|
|
15
|
+
"lessons": [
|
|
16
|
+
{
|
|
17
|
+
"title": "Document Structure",
|
|
18
|
+
"duration": 30,
|
|
19
|
+
"resources": [
|
|
20
|
+
{
|
|
21
|
+
"type": "video",
|
|
22
|
+
"url": "https://example.com/html-structure"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { defineCollection, reference } from 'astro:content';
|
|
2
|
+
import { glob } from 'astro/loaders';
|
|
3
|
+
import { z } from 'astro/zod';
|
|
4
|
+
|
|
5
|
+
//////////////////////////////
|
|
6
|
+
// Content collection definitions for the playground.
|
|
7
|
+
//
|
|
8
|
+
// These exercise a range of schema shapes: flat fields, cross-collection references, nested objects, and deeply nested arrays of objects.
|
|
9
|
+
//////////////////////////////
|
|
10
|
+
|
|
11
|
+
// Simple flat schema with a cross-collection reference
|
|
12
|
+
const posts = defineCollection({
|
|
13
|
+
loader: glob({ base: './src/content/posts', pattern: '**/*.md' }),
|
|
14
|
+
schema: z
|
|
15
|
+
.object({
|
|
16
|
+
title: z.string(),
|
|
17
|
+
date: z.date(),
|
|
18
|
+
draft: z.boolean().optional(),
|
|
19
|
+
author: reference('authors'),
|
|
20
|
+
})
|
|
21
|
+
.meta({
|
|
22
|
+
title: 'Posts',
|
|
23
|
+
description: 'Blog posts',
|
|
24
|
+
files: ['md'],
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Nested object — social has optional string fields
|
|
29
|
+
const authors = defineCollection({
|
|
30
|
+
loader: glob({ base: './src/content/authors', pattern: '**/*.json' }),
|
|
31
|
+
schema: z
|
|
32
|
+
.object({
|
|
33
|
+
name: z.string(),
|
|
34
|
+
bio: z.string(),
|
|
35
|
+
social: z.object({
|
|
36
|
+
twitter: z.string().optional(),
|
|
37
|
+
github: z.string().optional(),
|
|
38
|
+
website: z.string().optional(),
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
41
|
+
.meta({
|
|
42
|
+
title: 'Authors',
|
|
43
|
+
description: 'Those who write',
|
|
44
|
+
files: ['json'],
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Array of nested objects, each containing a nested array
|
|
49
|
+
const products = defineCollection({
|
|
50
|
+
loader: glob({ base: './src/content/products', pattern: '**/*.json' }),
|
|
51
|
+
schema: z
|
|
52
|
+
.object({
|
|
53
|
+
name: z.string(),
|
|
54
|
+
price: z.number(),
|
|
55
|
+
variants: z.array(
|
|
56
|
+
z.object({
|
|
57
|
+
sku: z.string(),
|
|
58
|
+
color: z.string(),
|
|
59
|
+
sizes: z.array(z.string()),
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
})
|
|
63
|
+
.meta({
|
|
64
|
+
title: 'Products',
|
|
65
|
+
description: 'Things to buy',
|
|
66
|
+
files: ['json'],
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Deep nesting: object with array, array of objects with nested arrays of objects
|
|
71
|
+
const courses = defineCollection({
|
|
72
|
+
loader: glob({ base: './src/content/courses', pattern: '**/*.json' }),
|
|
73
|
+
schema: z
|
|
74
|
+
.object({
|
|
75
|
+
title: z.string(),
|
|
76
|
+
instructor: z.object({
|
|
77
|
+
name: z.string(),
|
|
78
|
+
credentials: z.array(
|
|
79
|
+
z.object({
|
|
80
|
+
institution: z.string(),
|
|
81
|
+
year: z.number(),
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
}),
|
|
85
|
+
modules: z.array(
|
|
86
|
+
z.object({
|
|
87
|
+
title: z.string(),
|
|
88
|
+
lessons: z.array(
|
|
89
|
+
z.object({
|
|
90
|
+
title: z.string(),
|
|
91
|
+
duration: z.number(),
|
|
92
|
+
resources: z.array(
|
|
93
|
+
z.object({
|
|
94
|
+
type: z.string(),
|
|
95
|
+
url: z.string(),
|
|
96
|
+
}),
|
|
97
|
+
),
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
102
|
+
})
|
|
103
|
+
.meta({
|
|
104
|
+
title: 'Courses',
|
|
105
|
+
description: 'Things to learn',
|
|
106
|
+
files: ['json'],
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// How-to guides authored in MDX
|
|
111
|
+
const guides = defineCollection({
|
|
112
|
+
loader: glob({ base: './src/content/guides', pattern: '**/*.mdx' }),
|
|
113
|
+
schema: z
|
|
114
|
+
.object({
|
|
115
|
+
title: z.string(),
|
|
116
|
+
order: z.number(),
|
|
117
|
+
})
|
|
118
|
+
.meta({
|
|
119
|
+
title: 'Guides',
|
|
120
|
+
description: 'How-to guides in MDX',
|
|
121
|
+
files: ['mdx'],
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Cooking recipes authored in Markdoc
|
|
126
|
+
const recipes = defineCollection({
|
|
127
|
+
loader: glob({
|
|
128
|
+
base: './src/content/recipes',
|
|
129
|
+
pattern: '**/*.{mdoc,markdoc}',
|
|
130
|
+
}),
|
|
131
|
+
schema: z
|
|
132
|
+
.object({
|
|
133
|
+
title: z.string(),
|
|
134
|
+
servings: z.number(),
|
|
135
|
+
})
|
|
136
|
+
.meta({
|
|
137
|
+
title: 'Recipes',
|
|
138
|
+
description: 'Cooking recipes in Markdoc',
|
|
139
|
+
files: ['markdoc'],
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Site configuration stored as YAML
|
|
144
|
+
const settings = defineCollection({
|
|
145
|
+
loader: glob({ base: './src/content/settings', pattern: '**/*.{yml,yaml}' }),
|
|
146
|
+
schema: z
|
|
147
|
+
.object({
|
|
148
|
+
siteName: z.string(),
|
|
149
|
+
maxPosts: z.number(),
|
|
150
|
+
})
|
|
151
|
+
.meta({
|
|
152
|
+
title: 'Settings',
|
|
153
|
+
description: 'Site configuration in YAML',
|
|
154
|
+
files: ['yaml'],
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Build configuration stored as TOML
|
|
159
|
+
const config = defineCollection({
|
|
160
|
+
loader: glob({ base: './src/content/config', pattern: '**/*.toml' }),
|
|
161
|
+
schema: z
|
|
162
|
+
.object({
|
|
163
|
+
name: z.string(),
|
|
164
|
+
debug: z.boolean(),
|
|
165
|
+
})
|
|
166
|
+
.meta({
|
|
167
|
+
title: 'Config',
|
|
168
|
+
description: 'Build configuration in TOML',
|
|
169
|
+
files: ['toml'],
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Documentation pages supporting both MD and MDX
|
|
174
|
+
const docs = defineCollection({
|
|
175
|
+
loader: glob({ base: './src/content/docs', pattern: '**/*.{md,mdx}' }),
|
|
176
|
+
schema: z
|
|
177
|
+
.object({
|
|
178
|
+
title: z.string(),
|
|
179
|
+
category: z.string(),
|
|
180
|
+
})
|
|
181
|
+
.meta({
|
|
182
|
+
title: 'Documentation',
|
|
183
|
+
description: 'Docs in MD and MDX',
|
|
184
|
+
files: ['md', 'mdx'],
|
|
185
|
+
}),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
export const collections = {
|
|
189
|
+
posts,
|
|
190
|
+
authors,
|
|
191
|
+
products,
|
|
192
|
+
courses,
|
|
193
|
+
guides,
|
|
194
|
+
recipes,
|
|
195
|
+
settings,
|
|
196
|
+
config,
|
|
197
|
+
docs,
|
|
198
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="nebula-cms/virtual" />
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<html lang="en">
|
|
2
|
+
<head>
|
|
3
|
+
<meta charset="utf-8" />
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5
|
+
<title>Nebula CMS playground</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Nebula CMS</h1>
|
|
9
|
+
<a href="/nebula">Nebula Admin Page</a>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Admin from 'nebula-cms/client';
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="utf-8" />
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
9
|
+
<title>nebula-cms playground</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<Admin client:only="svelte" />
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Fetches a subsetted Material Symbols Outlined woff2 font from Google Fonts
|
|
3
|
+
* and inlines it as base64 in a self-contained CSS file. Run this script
|
|
4
|
+
* whenever the icon set changes to regenerate src/client/css/icons.css.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node scripts/subset-icons.mjs
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
|
|
13
|
+
//////////////////////////////
|
|
14
|
+
// Icon set definition
|
|
15
|
+
//////////////////////////////
|
|
16
|
+
|
|
17
|
+
// Icons used across the admin UI — keep sorted alphabetically.
|
|
18
|
+
const ICON_NAMES = [
|
|
19
|
+
'add',
|
|
20
|
+
'arrow_downward',
|
|
21
|
+
'arrow_upward',
|
|
22
|
+
'brightness_auto',
|
|
23
|
+
'chevron_right',
|
|
24
|
+
'close',
|
|
25
|
+
'dark_mode',
|
|
26
|
+
'drag_indicator',
|
|
27
|
+
'hourglass_arrow_down',
|
|
28
|
+
'hourglass_arrow_up',
|
|
29
|
+
'info',
|
|
30
|
+
'light_mode',
|
|
31
|
+
'logout',
|
|
32
|
+
'sort_by_alpha',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
//////////////////////////////
|
|
36
|
+
// Path resolution
|
|
37
|
+
//////////////////////////////
|
|
38
|
+
|
|
39
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
40
|
+
const __dirname = dirname(__filename);
|
|
41
|
+
|
|
42
|
+
// Absolute path where the generated CSS will be written.
|
|
43
|
+
const OUTPUT_PATH = join(__dirname, '../src/client/css/icons.css');
|
|
44
|
+
|
|
45
|
+
//////////////////////////////
|
|
46
|
+
// Font fetch helpers
|
|
47
|
+
//////////////////////////////
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Builds the Google Fonts CSS2 API URL for the subsetted icon font.
|
|
51
|
+
* @param {string[]} names - Sorted array of icon ligature names
|
|
52
|
+
* @return {string} The fully-formed Google Fonts API URL
|
|
53
|
+
*/
|
|
54
|
+
function buildFontsURL(names) {
|
|
55
|
+
const joined = names.sort().join(',');
|
|
56
|
+
return `https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&icon_names=${joined}&display=block`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fetches the Google Fonts CSS stylesheet for the icon subset.
|
|
61
|
+
* @param {string} url - The Google Fonts API URL to fetch
|
|
62
|
+
* @return {Promise<string>} The CSS text returned by Google Fonts
|
|
63
|
+
*/
|
|
64
|
+
async function fetchFontCSS(url) {
|
|
65
|
+
// Spoof a modern browser UA — Google Fonts returns woff2 for modern UAs.
|
|
66
|
+
const res = await fetch(url, {
|
|
67
|
+
headers: {
|
|
68
|
+
'User-Agent':
|
|
69
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Google Fonts API returned ${res.status}: ${res.statusText}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return res.text();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extracts the woff2 font binary URL from the Google Fonts CSS response.
|
|
84
|
+
* Google Fonts returns unquoted urls in the form: url(https://fonts.gstatic.com/...)
|
|
85
|
+
* The URL does not end in .woff2 — it is a signed CDN path — so we match on
|
|
86
|
+
* the format hint that follows it instead.
|
|
87
|
+
* @param {string} css - The CSS text returned by Google Fonts
|
|
88
|
+
* @return {string} The absolute woff2 URL
|
|
89
|
+
*/
|
|
90
|
+
function extractWoff2URL(css) {
|
|
91
|
+
// Match unquoted url(...) followed by format('woff2')
|
|
92
|
+
const match = css.match(/url\((https:\/\/[^)]+)\)\s+format\('woff2'\)/);
|
|
93
|
+
if (!match) {
|
|
94
|
+
throw new Error('Could not find woff2 URL in Google Fonts CSS response');
|
|
95
|
+
}
|
|
96
|
+
return match[1];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Downloads the woff2 binary and returns it as a base64-encoded string.
|
|
101
|
+
* @param {string} url - The absolute URL to the woff2 font file
|
|
102
|
+
* @return {Promise<string>} Base64-encoded woff2 binary data
|
|
103
|
+
*/
|
|
104
|
+
async function fetchWoff2AsBase64(url) {
|
|
105
|
+
const res = await fetch(url);
|
|
106
|
+
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Font binary fetch returned ${res.status}: ${res.statusText}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
114
|
+
return Buffer.from(arrayBuffer).toString('base64');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//////////////////////////////
|
|
118
|
+
// CSS generation
|
|
119
|
+
//////////////////////////////
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Generates the full icons.css content with an inlined base64 woff2 font.
|
|
123
|
+
* @param {string} base64Font - The base64-encoded woff2 font data
|
|
124
|
+
* @return {string} Complete CSS file content
|
|
125
|
+
*/
|
|
126
|
+
function generateCSS(base64Font) {
|
|
127
|
+
return `/* Material Symbols Outlined, Subsetted
|
|
128
|
+
* Icons: ${ICON_NAMES.sort().join(', ')}
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
@font-face {
|
|
132
|
+
font-family: 'Material Symbols Outlined';
|
|
133
|
+
font-style: normal;
|
|
134
|
+
font-weight: 400;
|
|
135
|
+
font-display: block;
|
|
136
|
+
src: url('data:font/woff2;base64,${base64Font}') format('woff2');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.icon {
|
|
140
|
+
font-family: 'Material Symbols Outlined';
|
|
141
|
+
font-weight: normal;
|
|
142
|
+
font-style: normal;
|
|
143
|
+
font-size: 1.5rem;
|
|
144
|
+
line-height: 1;
|
|
145
|
+
letter-spacing: normal;
|
|
146
|
+
text-transform: none;
|
|
147
|
+
display: inline-block;
|
|
148
|
+
white-space: nowrap;
|
|
149
|
+
word-wrap: normal;
|
|
150
|
+
direction: ltr;
|
|
151
|
+
-webkit-font-smoothing: antialiased;
|
|
152
|
+
text-rendering: optimizeLegibility;
|
|
153
|
+
-moz-osx-font-smoothing: grayscale;
|
|
154
|
+
font-feature-settings: 'liga';
|
|
155
|
+
}
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//////////////////////////////
|
|
160
|
+
// Entry point
|
|
161
|
+
//////////////////////////////
|
|
162
|
+
|
|
163
|
+
const fontsURL = buildFontsURL(ICON_NAMES);
|
|
164
|
+
console.log(`Fetching icon subset CSS from Google Fonts...`);
|
|
165
|
+
console.log(`URL: ${fontsURL}`);
|
|
166
|
+
|
|
167
|
+
const fontCSS = await fetchFontCSS(fontsURL);
|
|
168
|
+
const woff2URL = extractWoff2URL(fontCSS);
|
|
169
|
+
console.log(`Found woff2 URL: ${woff2URL}`);
|
|
170
|
+
|
|
171
|
+
console.log(`Downloading woff2 binary...`);
|
|
172
|
+
const base64Font = await fetchWoff2AsBase64(woff2URL);
|
|
173
|
+
console.log(`Base64-encoded font: ${base64Font.length} characters`);
|
|
174
|
+
|
|
175
|
+
const css = generateCSS(base64Font);
|
|
176
|
+
mkdirSync(dirname(OUTPUT_PATH), { recursive: true });
|
|
177
|
+
writeFileSync(OUTPUT_PATH, css, 'utf8');
|
|
178
|
+
console.log(`Written to: ${OUTPUT_PATH}`);
|