nebula-cms 0.1.0 → 0.1.4
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/README.md +2 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.d.ts.map +1 -0
- package/dist/astro/index.js +223 -0
- package/dist/client/Admin.svelte.d.ts +11 -0
- package/dist/client/Admin.svelte.d.ts.map +1 -0
- package/dist/client/components/BackendPicker.svelte.d.ts +4 -0
- package/dist/client/components/BackendPicker.svelte.d.ts.map +1 -0
- package/dist/client/components/DraftChip.svelte.d.ts +10 -0
- package/dist/client/components/DraftChip.svelte.d.ts.map +1 -0
- package/dist/client/components/MetadataForm.svelte.d.ts +12 -0
- package/dist/client/components/MetadataForm.svelte.d.ts.map +1 -0
- package/dist/client/components/ThemeToggle.svelte.d.ts +19 -0
- package/dist/client/components/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts +11 -0
- package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts.map +1 -0
- package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts +13 -0
- package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts.map +1 -0
- package/dist/client/components/editor/EditorPane.svelte.d.ts +4 -0
- package/dist/client/components/editor/EditorPane.svelte.d.ts.map +1 -0
- package/dist/client/components/editor/EditorTabs.svelte.d.ts +8 -0
- package/dist/client/components/editor/EditorTabs.svelte.d.ts.map +1 -0
- package/dist/client/components/editor/EditorToolbar.svelte.d.ts +4 -0
- package/dist/client/components/editor/EditorToolbar.svelte.d.ts.map +1 -0
- package/dist/client/components/editor/FormatSelector.svelte.d.ts +4 -0
- package/dist/client/components/editor/FormatSelector.svelte.d.ts.map +1 -0
- package/dist/client/components/editor/Toolbar.svelte.d.ts +19 -0
- package/dist/client/components/editor/Toolbar.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/ArrayField.svelte.d.ts +15 -0
- package/dist/client/components/fields/ArrayField.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/ArrayItem.svelte.d.ts +28 -0
- package/dist/client/components/fields/ArrayItem.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/BooleanField.svelte.d.ts +16 -0
- package/dist/client/components/fields/BooleanField.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/DateField.svelte.d.ts +16 -0
- package/dist/client/components/fields/DateField.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/EnumField.svelte.d.ts +17 -0
- package/dist/client/components/fields/EnumField.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/FieldWrapper.svelte.d.ts +18 -0
- package/dist/client/components/fields/FieldWrapper.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/NumberField.svelte.d.ts +16 -0
- package/dist/client/components/fields/NumberField.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/ObjectField.svelte.d.ts +16 -0
- package/dist/client/components/fields/ObjectField.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/SchemaField.svelte.d.ts +16 -0
- package/dist/client/components/fields/SchemaField.svelte.d.ts.map +1 -0
- package/dist/client/components/fields/StringField.svelte.d.ts +16 -0
- package/dist/client/components/fields/StringField.svelte.d.ts.map +1 -0
- package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts +19 -0
- package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts.map +1 -0
- package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts +12 -0
- package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts.map +1 -0
- package/dist/client/css/icons.css +29 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/js/drafts/merge.svelte.d.ts +24 -0
- package/dist/client/js/drafts/merge.svelte.d.ts.map +1 -0
- package/dist/client/js/drafts/merge.svelte.js +106 -0
- package/dist/client/js/drafts/ops.svelte.d.ts +31 -0
- package/dist/client/js/drafts/ops.svelte.d.ts.map +1 -0
- package/dist/client/js/drafts/ops.svelte.js +182 -0
- package/dist/client/js/drafts/storage.d.ts +45 -0
- package/dist/client/js/drafts/storage.d.ts.map +1 -0
- package/dist/client/js/drafts/storage.js +76 -0
- package/dist/client/js/drafts/workers/diff.d.ts +2 -0
- package/dist/client/js/drafts/workers/diff.d.ts.map +1 -0
- package/dist/client/js/drafts/workers/diff.js +20 -0
- package/dist/client/js/editor/editor.svelte.d.ts +124 -0
- package/dist/client/js/editor/editor.svelte.d.ts.map +1 -0
- package/dist/client/js/editor/editor.svelte.js +294 -0
- package/dist/client/js/editor/languages.d.ts +11 -0
- package/dist/client/js/editor/languages.d.ts.map +1 -0
- package/dist/client/js/editor/languages.js +93 -0
- package/dist/client/js/editor/link-wrap.d.ts +6 -0
- package/dist/client/js/editor/link-wrap.d.ts.map +1 -0
- package/{src/client/js/editor/link-wrap.ts → dist/client/js/editor/link-wrap.js} +17 -24
- package/dist/client/js/editor/markdown-shortcuts.d.ts +4 -0
- package/dist/client/js/editor/markdown-shortcuts.d.ts.map +1 -0
- package/dist/client/js/editor/markdown-shortcuts.js +219 -0
- package/dist/client/js/handlers/admin.d.ts +64 -0
- package/dist/client/js/handlers/admin.d.ts.map +1 -0
- package/dist/client/js/handlers/admin.js +186 -0
- package/dist/client/js/state/dialogs.svelte.d.ts +16 -0
- package/dist/client/js/state/dialogs.svelte.d.ts.map +1 -0
- package/dist/client/js/state/dialogs.svelte.js +28 -0
- package/dist/client/js/state/router.svelte.d.ts +44 -0
- package/dist/client/js/state/router.svelte.d.ts.map +1 -0
- package/dist/client/js/state/router.svelte.js +135 -0
- package/dist/client/js/state/schema.svelte.d.ts +51 -0
- package/dist/client/js/state/schema.svelte.d.ts.map +1 -0
- package/{src/client/js/state/schema.svelte.ts → dist/client/js/state/schema.svelte.js} +55 -70
- package/dist/client/js/state/state.svelte.d.ts +68 -0
- package/dist/client/js/state/state.svelte.d.ts.map +1 -0
- package/dist/client/js/state/state.svelte.js +291 -0
- package/dist/client/js/state/theme.svelte.d.ts +24 -0
- package/dist/client/js/state/theme.svelte.d.ts.map +1 -0
- package/{src/client/js/state/theme.svelte.ts → dist/client/js/state/theme.svelte.js} +54 -91
- package/dist/client/js/storage/adapter.d.ts +130 -0
- package/dist/client/js/storage/adapter.d.ts.map +1 -0
- package/dist/client/js/storage/adapter.js +5 -0
- package/dist/client/js/storage/client.d.ts +72 -0
- package/dist/client/js/storage/client.d.ts.map +1 -0
- package/dist/client/js/storage/client.js +121 -0
- package/dist/client/js/storage/db.d.ts +8 -0
- package/dist/client/js/storage/db.d.ts.map +1 -0
- package/dist/client/js/storage/db.js +35 -0
- package/dist/client/js/storage/fsa.d.ts +51 -0
- package/dist/client/js/storage/fsa.d.ts.map +1 -0
- package/dist/client/js/storage/fsa.js +91 -0
- package/dist/client/js/storage/github.d.ts +62 -0
- package/dist/client/js/storage/github.d.ts.map +1 -0
- package/dist/client/js/storage/github.js +216 -0
- package/dist/client/js/storage/storage.d.ts +32 -0
- package/dist/client/js/storage/storage.d.ts.map +1 -0
- package/dist/client/js/storage/storage.js +68 -0
- package/dist/client/js/storage/workers/frontmatter.d.ts +2 -0
- package/dist/client/js/storage/workers/frontmatter.d.ts.map +1 -0
- package/dist/client/js/storage/workers/frontmatter.js +253 -0
- package/dist/client/js/storage/workers/storage.d.ts +2 -0
- package/dist/client/js/storage/workers/storage.d.ts.map +1 -0
- package/dist/client/js/storage/workers/storage.js +167 -0
- package/dist/client/js/storage/workers/toml-parser.d.ts +2 -0
- package/dist/client/js/storage/workers/toml-parser.d.ts.map +1 -0
- package/dist/client/js/storage/workers/toml-parser.js +75 -0
- package/dist/client/js/storage/workers/yaml-parser.d.ts +2 -0
- package/dist/client/js/storage/workers/yaml-parser.d.ts.map +1 -0
- package/dist/client/js/storage/workers/yaml-parser.js +100 -0
- package/dist/client/js/utils/file-types.d.ts +58 -0
- package/dist/client/js/utils/file-types.d.ts.map +1 -0
- package/{src/client/js/utils/file-types.ts → dist/client/js/utils/file-types.js} +75 -107
- package/dist/client/js/utils/format.d.ts +8 -0
- package/dist/client/js/utils/format.d.ts.map +1 -0
- package/{src/client/js/utils/format.ts → dist/client/js/utils/format.js} +5 -6
- package/dist/client/js/utils/frontmatter.d.ts +12 -0
- package/dist/client/js/utils/frontmatter.d.ts.map +1 -0
- package/dist/client/js/utils/frontmatter.js +29 -0
- package/dist/client/js/utils/schema-utils.d.ts +110 -0
- package/dist/client/js/utils/schema-utils.d.ts.map +1 -0
- package/dist/client/js/utils/schema-utils.js +242 -0
- package/dist/client/js/utils/slug.d.ts +8 -0
- package/dist/client/js/utils/slug.d.ts.map +1 -0
- package/{src/client/js/utils/slug.ts → dist/client/js/utils/slug.js} +6 -7
- package/dist/client/js/utils/sort.d.ts +41 -0
- package/dist/client/js/utils/sort.d.ts.map +1 -0
- package/dist/client/js/utils/sort.js +65 -0
- package/dist/client/js/utils/stable-stringify.d.ts +8 -0
- package/dist/client/js/utils/stable-stringify.d.ts.map +1 -0
- package/dist/client/js/utils/stable-stringify.js +23 -0
- package/dist/client/js/utils/url-utils.d.ts +11 -0
- package/dist/client/js/utils/url-utils.d.ts.map +1 -0
- package/{src/client/js/utils/url-utils.ts → dist/client/js/utils/url-utils.js} +22 -23
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +8 -1
- package/.claude/settings.local.json +0 -42
- package/.github/workflows/ci.yml +0 -31
- package/.mcp.json +0 -12
- package/.prettierignore +0 -5
- package/.prettierrc.cjs +0 -22
- package/AGENTS.md +0 -183
- package/playground/.claude/settings.local.json +0 -5
- package/playground/astro.config.mjs +0 -7
- package/playground/node_modules/.bin/astro +0 -21
- package/playground/node_modules/.bin/rollup +0 -21
- package/playground/node_modules/.bin/tsc +0 -21
- package/playground/node_modules/.bin/tsserver +0 -21
- package/playground/node_modules/.bin/vite +0 -21
- package/playground/node_modules/.vite/_svelte_metadata.json +0 -1
- package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +0 -80
- package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +0 -7
- package/playground/node_modules/.vite/deps/_metadata.json +0 -184
- package/playground/node_modules/.vite/deps/astro___aria-query.js +0 -6776
- package/playground/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
- package/playground/node_modules/.vite/deps/astro___axobject-query.js +0 -3754
- package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
- package/playground/node_modules/.vite/deps/astro___html-escaper.js +0 -34
- package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
- package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js +0 -1005
- package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +0 -8
- package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +0 -21
- package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +0 -223
- package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js +0 -204
- package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js +0 -27
- package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js +0 -4376
- package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js +0 -688
- package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js +0 -5099
- package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +0 -23
- package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +0 -148
- package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +0 -7
- package/playground/node_modules/.vite/deps/package.json +0 -3
- package/playground/node_modules/.vite/deps/smol-toml.js +0 -843
- package/playground/node_modules/.vite/deps/smol-toml.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte.js +0 -55
- package/playground/node_modules/.vite/deps/svelte.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte___clsx.js +0 -9
- package/playground/node_modules/.vite/deps/svelte___clsx.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_animate.js +0 -57
- package/playground/node_modules/.vite/deps/svelte_animate.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_attachments.js +0 -15
- package/playground/node_modules/.vite/deps/svelte_attachments.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_easing.js +0 -67
- package/playground/node_modules/.vite/deps/svelte_easing.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_events.js +0 -11
- package/playground/node_modules/.vite/deps/svelte_events.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_internal.js +0 -5
- package/playground/node_modules/.vite/deps/svelte_internal.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_internal_client.js +0 -402
- package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +0 -10
- package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +0 -8
- package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +0 -8
- package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +0 -8
- package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_legacy.js +0 -35
- package/playground/node_modules/.vite/deps/svelte_legacy.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_motion.js +0 -545
- package/playground/node_modules/.vite/deps/svelte_motion.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_reactivity.js +0 -29
- package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +0 -127
- package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_store.js +0 -103
- package/playground/node_modules/.vite/deps/svelte_store.js.map +0 -7
- package/playground/node_modules/.vite/deps/svelte_transition.js +0 -208
- package/playground/node_modules/.vite/deps/svelte_transition.js.map +0 -7
- package/playground/package.json +0 -16
- package/playground/pnpm-lock.yaml +0 -3167
- package/playground/src/content/authors/jane-doe.json +0 -8
- package/playground/src/content/config/build.toml +0 -2
- package/playground/src/content/courses/web-fundamentals.json +0 -29
- package/playground/src/content/docs/advanced.mdx +0 -6
- package/playground/src/content/docs/intro.md +0 -6
- package/playground/src/content/guides/getting-started.mdx +0 -6
- package/playground/src/content/posts/hello-world.md +0 -7
- package/playground/src/content/products/t-shirt.json +0 -16
- package/playground/src/content/recipes/pancakes.mdoc +0 -8
- package/playground/src/content/settings/site.yml +0 -2
- package/playground/src/content.config.ts +0 -198
- package/playground/src/env.d.ts +0 -1
- package/playground/src/pages/index.astro +0 -11
- package/playground/src/pages/nebula.astro +0 -14
- package/pnpm-workspace.yaml +0 -2
- package/scripts/subset-icons.mjs +0 -178
- package/src/astro/index.ts +0 -295
- package/src/client/js/drafts/merge.svelte.ts +0 -121
- package/src/client/js/drafts/ops.svelte.ts +0 -227
- package/src/client/js/drafts/storage.ts +0 -108
- package/src/client/js/drafts/workers/diff.ts +0 -40
- package/src/client/js/editor/editor.svelte.ts +0 -343
- package/src/client/js/editor/languages.ts +0 -98
- package/src/client/js/editor/markdown-shortcuts.ts +0 -261
- package/src/client/js/handlers/admin.ts +0 -246
- package/src/client/js/state/dialogs.svelte.ts +0 -35
- package/src/client/js/state/router.svelte.ts +0 -156
- package/src/client/js/state/state.svelte.ts +0 -334
- package/src/client/js/storage/adapter.ts +0 -102
- package/src/client/js/storage/client.ts +0 -150
- package/src/client/js/storage/db.ts +0 -36
- package/src/client/js/storage/fsa.ts +0 -110
- package/src/client/js/storage/github.ts +0 -297
- package/src/client/js/storage/storage.ts +0 -83
- package/src/client/js/storage/workers/frontmatter.ts +0 -320
- package/src/client/js/storage/workers/storage.ts +0 -177
- package/src/client/js/storage/workers/toml-parser.ts +0 -106
- package/src/client/js/storage/workers/yaml-parser.ts +0 -132
- package/src/client/js/utils/frontmatter.ts +0 -38
- package/src/client/js/utils/schema-utils.ts +0 -295
- package/src/client/js/utils/sort.ts +0 -84
- package/src/client/js/utils/stable-stringify.ts +0 -27
- package/src/types.ts +0 -25
- package/svelte.config.js +0 -4
- package/tests/astro/build.test.ts +0 -63
- package/tests/astro/index.test.ts +0 -689
- package/tests/client/components/Admin.test.ts +0 -446
- package/tests/client/components/BackendPicker.test.ts +0 -239
- package/tests/client/components/DraftChip.test.ts +0 -53
- package/tests/client/components/MetadataForm.test.ts +0 -164
- package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +0 -91
- package/tests/client/components/dialogs/FilenameDialog.test.ts +0 -209
- package/tests/client/components/dialogs/dialog-stubs.ts +0 -19
- package/tests/client/components/editor/EditorPane.test.ts +0 -100
- package/tests/client/components/editor/EditorTabs.test.ts +0 -253
- package/tests/client/components/editor/EditorToolbar.test.ts +0 -252
- package/tests/client/components/editor/fixtures.ts +0 -31
- package/tests/client/components/fields/ArrayField.test.ts +0 -197
- package/tests/client/components/fields/BooleanField.test.ts +0 -206
- package/tests/client/components/fields/DateField.test.ts +0 -210
- package/tests/client/components/fields/EnumField.test.ts +0 -246
- package/tests/client/components/fields/NumberField.test.ts +0 -240
- package/tests/client/components/fields/ObjectField.test.ts +0 -157
- package/tests/client/components/fields/SchemaField.test.ts +0 -190
- package/tests/client/components/fields/StringField.test.ts +0 -223
- package/tests/client/components/sidebar/AdminSidebar.test.ts +0 -285
- package/tests/client/components/sidebar/AdminSidebarSort.test.ts +0 -135
- package/tests/client/components/sidebar/sort-mock.ts +0 -23
- package/tests/client/js/drafts/fixtures.ts +0 -22
- package/tests/client/js/drafts/merge.test.ts +0 -282
- package/tests/client/js/drafts/ops.test.ts +0 -658
- package/tests/client/js/drafts/storage.test.ts +0 -200
- package/tests/client/js/drafts/workers/diff.test.ts +0 -165
- package/tests/client/js/editor/editor.test.ts +0 -616
- package/tests/client/js/editor/link-wrap.test.ts +0 -225
- package/tests/client/js/editor/markdown-shortcuts.test.ts +0 -370
- package/tests/client/js/handlers/admin.test.ts +0 -467
- package/tests/client/js/state/router.test.ts +0 -619
- package/tests/client/js/state/schema.test.ts +0 -266
- package/tests/client/js/state/state.test.ts +0 -328
- package/tests/client/js/storage/adapter.test.ts +0 -115
- package/tests/client/js/storage/client.test.ts +0 -250
- package/tests/client/js/storage/db.test.ts +0 -59
- package/tests/client/js/storage/fsa.test.ts +0 -284
- package/tests/client/js/storage/github.test.ts +0 -349
- package/tests/client/js/storage/mock-port.ts +0 -95
- package/tests/client/js/storage/storage.test.ts +0 -77
- package/tests/client/js/storage/workers/frontmatter.test.ts +0 -479
- package/tests/client/js/storage/workers/storage.test.ts +0 -299
- package/tests/client/js/storage/workers/toml-parser.test.ts +0 -169
- package/tests/client/js/storage/workers/yaml-parser.test.ts +0 -168
- package/tests/client/js/utils/file-types.test.ts +0 -268
- package/tests/client/js/utils/frontmatter.test.ts +0 -87
- package/tests/client/js/utils/schema-utils.test.ts +0 -318
- package/tests/client/js/utils/slug.test.ts +0 -58
- package/tests/client/js/utils/sort.test.ts +0 -276
- package/tests/client/js/utils/stable-stringify.test.ts +0 -68
- package/tests/client/js/utils/url-utils.test.ts +0 -70
- package/tests/e2e/backend-connection.test.ts +0 -301
- package/tests/e2e/draft-lifecycle.test.ts +0 -388
- package/tests/e2e/editing.test.ts +0 -355
- package/tests/e2e/github-adapter.test.ts +0 -330
- package/tests/e2e/helpers/mock-adapter.ts +0 -166
- package/tests/e2e/helpers/test-app.ts +0 -155
- package/tests/e2e/navigation.test.ts +0 -358
- package/tests/e2e/publishing.test.ts +0 -345
- package/tests/e2e/unsaved-changes.test.ts +0 -317
- package/tests/setup.ts +0 -2
- package/tests/stubs/codemirror.ts +0 -197
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -178
- /package/{src → dist}/client/Admin.svelte +0 -0
- /package/{src → dist}/client/components/BackendPicker.svelte +0 -0
- /package/{src → dist}/client/components/DraftChip.svelte +0 -0
- /package/{src → dist}/client/components/MetadataForm.svelte +0 -0
- /package/{src → dist}/client/components/ThemeToggle.svelte +0 -0
- /package/{src → dist}/client/components/dialogs/DeleteDraftDialog.svelte +0 -0
- /package/{src → dist}/client/components/dialogs/FilenameDialog.svelte +0 -0
- /package/{src → dist}/client/components/editor/EditorPane.svelte +0 -0
- /package/{src → dist}/client/components/editor/EditorTabs.svelte +0 -0
- /package/{src → dist}/client/components/editor/EditorToolbar.svelte +0 -0
- /package/{src → dist}/client/components/editor/FormatSelector.svelte +0 -0
- /package/{src → dist}/client/components/editor/Toolbar.svelte +0 -0
- /package/{src → dist}/client/components/fields/ArrayField.svelte +0 -0
- /package/{src → dist}/client/components/fields/ArrayItem.svelte +0 -0
- /package/{src → dist}/client/components/fields/BooleanField.svelte +0 -0
- /package/{src → dist}/client/components/fields/DateField.svelte +0 -0
- /package/{src → dist}/client/components/fields/EnumField.svelte +0 -0
- /package/{src → dist}/client/components/fields/FieldWrapper.svelte +0 -0
- /package/{src → dist}/client/components/fields/NumberField.svelte +0 -0
- /package/{src → dist}/client/components/fields/ObjectField.svelte +0 -0
- /package/{src → dist}/client/components/fields/SchemaField.svelte +0 -0
- /package/{src → dist}/client/components/fields/StringField.svelte +0 -0
- /package/{src → dist}/client/components/sidebar/AdminSidebar.svelte +0 -0
- /package/{src → dist}/client/components/sidebar/AdminSidebarSort.svelte +0 -0
- /package/{src → dist}/client/css/a11y.css +0 -0
- /package/{src → dist}/client/css/btn.css +0 -0
- /package/{src → dist}/client/css/dialog.css +0 -0
- /package/{src → dist}/client/css/field-input.css +0 -0
- /package/{src → dist}/client/css/reset.css +0 -0
- /package/{src → dist}/client/css/theme.css +0 -0
- /package/{src/client/index.ts → dist/client/index.js} +0 -0
- /package/{src → dist}/virtual.d.ts +0 -0
package/src/astro/index.ts
DELETED
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Astro integration entry point for Nebula CMS.
|
|
3
|
-
* Exposes content collection JSON schemas and CMS configuration to
|
|
4
|
-
* client-side JavaScript via virtual modules, dev middleware, and
|
|
5
|
-
* build-time file copy.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
copyFileSync,
|
|
10
|
-
existsSync,
|
|
11
|
-
mkdirSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
readdirSync,
|
|
14
|
-
} from 'node:fs';
|
|
15
|
-
import { resolve } from 'node:path';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import type { AstroIntegration, AstroIntegrationLogger } from 'astro';
|
|
18
|
-
import type { NebulaCMSConfig } from '../types.js';
|
|
19
|
-
|
|
20
|
-
// Vite virtual module IDs
|
|
21
|
-
const CONFIG_VIRTUAL_ID = 'virtual:nebula/config';
|
|
22
|
-
const CONFIG_RESOLVED_ID = '\0' + CONFIG_VIRTUAL_ID;
|
|
23
|
-
const COLLECTIONS_VIRTUAL_ID = 'virtual:nebula/collections';
|
|
24
|
-
const COLLECTIONS_RESOLVED_ID = '\0' + COLLECTIONS_VIRTUAL_ID;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Normalizes a path config value to an absolute path using the URL API.
|
|
28
|
-
* Handles leading-slash prepending, consecutive-slash collapsing,
|
|
29
|
-
* trailing-slash stripping, and rejects paths with characters that
|
|
30
|
-
* require percent-encoding (e.g. spaces, angle brackets).
|
|
31
|
-
* @param {string} label - Config field name for error messages (e.g. 'basePath')
|
|
32
|
-
* @param {string} value - The raw config value
|
|
33
|
-
* @return {string} The normalized absolute path
|
|
34
|
-
*/
|
|
35
|
-
function normalizePath(label: string, value: string): string {
|
|
36
|
-
/*
|
|
37
|
-
* Reject protocol-relative inputs ('//admin') — the URL API interprets
|
|
38
|
-
* these as hostnames, silently producing pathname '/' instead of '/admin'.
|
|
39
|
-
*/
|
|
40
|
-
if (value.startsWith('//')) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`Invalid ${label} "${value}". Path must not start with "//".`,
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
/*
|
|
46
|
-
* URL constructor with a dummy base handles both relative ('admin') and
|
|
47
|
-
* absolute ('/admin') inputs identically. Collapse consecutive slashes
|
|
48
|
-
* manually since the URL API preserves them in pathnames.
|
|
49
|
-
*/
|
|
50
|
-
const collapsed = new URL(value, 'http://x').pathname.replace(/\/\/+/g, '/');
|
|
51
|
-
// Strip trailing slash unless root
|
|
52
|
-
const normalized =
|
|
53
|
-
collapsed.length > 1 && collapsed.endsWith('/')
|
|
54
|
-
? collapsed.slice(0, -1)
|
|
55
|
-
: collapsed;
|
|
56
|
-
/*
|
|
57
|
-
* The URL API percent-encodes special characters rather than rejecting them
|
|
58
|
-
* (e.g. '/admin/<script>' → '/admin/%3Cscript%3E'). Comparing the decoded
|
|
59
|
-
* form to the normalized result catches any input that required encoding.
|
|
60
|
-
*/
|
|
61
|
-
if (decodeURIComponent(normalized) !== normalized) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`Invalid ${label} "${value}". Path contains characters that require URL encoding.`,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
return normalized;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Astro integration that exposes CMS configuration and content collection JSON schemas to client-side JavaScript via virtual modules, dev middleware, and build-time file copy.
|
|
71
|
-
* @param {NebulaCMSConfig} config - Optional configuration object
|
|
72
|
-
* @return {AstroIntegration} The configured Astro integration object
|
|
73
|
-
*/
|
|
74
|
-
export default function NebulaCMS(
|
|
75
|
-
config: NebulaCMSConfig = {},
|
|
76
|
-
): AstroIntegration {
|
|
77
|
-
if (config.basePath === '') {
|
|
78
|
-
throw new Error(
|
|
79
|
-
'Invalid basePath "". Provide a path like "/admin" or "/".',
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (config.collectionsPath === '') {
|
|
84
|
-
throw new Error(
|
|
85
|
-
'Invalid collectionsPath "". Provide a path like "/collections".',
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const basePath = normalizePath('basePath', config.basePath ?? '/admin');
|
|
90
|
-
const collectionsPath = normalizePath(
|
|
91
|
-
'collectionsPath',
|
|
92
|
-
config.collectionsPath ?? '/collections',
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
if (collectionsPath === '/') {
|
|
96
|
-
throw new Error(
|
|
97
|
-
'Invalid collectionsPath "/". Collections require a path prefix.',
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Normalized config passed to the Vite plugin
|
|
102
|
-
const normalizedConfig = { basePath, collectionsPath };
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
name: 'nebula-cms',
|
|
106
|
-
hooks: {
|
|
107
|
-
'astro:config:setup': ({ updateConfig, logger }) => {
|
|
108
|
-
updateConfig({
|
|
109
|
-
vite: {
|
|
110
|
-
plugins: [
|
|
111
|
-
nebulaVitePlugin(logger, process.cwd(), normalizedConfig),
|
|
112
|
-
],
|
|
113
|
-
/*
|
|
114
|
-
* Workers use dynamic imports (e.g. storage worker lazy-loads
|
|
115
|
-
* adapters), which require code splitting. The default 'iife'
|
|
116
|
-
* format does not support code splitting, so use ES modules.
|
|
117
|
-
*/
|
|
118
|
-
worker: { format: 'es' },
|
|
119
|
-
/*
|
|
120
|
-
* smol-toml is only imported inside the TOML parser sub-worker,
|
|
121
|
-
* never on the main thread. Without this, Vite discovers it late
|
|
122
|
-
* and re-optimizes mid-session, causing the worker to request a
|
|
123
|
-
* stale dep hash (504 Outdated Optimize Dep).
|
|
124
|
-
*/
|
|
125
|
-
optimizeDeps: {
|
|
126
|
-
include: ['smol-toml'],
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
},
|
|
131
|
-
// Copy schema files into the build output after Astro finishes.
|
|
132
|
-
'astro:build:done': ({ dir, logger }) => {
|
|
133
|
-
const source = resolve(process.cwd(), '.astro/collections');
|
|
134
|
-
if (!existsSync(source)) {
|
|
135
|
-
logger.warn(
|
|
136
|
-
'`.astro/collections` not found — schema files will not be in the build output.',
|
|
137
|
-
);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const outDir = fileURLToPath(dir);
|
|
141
|
-
// Strip leading slash for filesystem path resolution
|
|
142
|
-
const target = resolve(outDir, collectionsPath.slice(1));
|
|
143
|
-
mkdirSync(target, { recursive: true });
|
|
144
|
-
const files = readdirSync(source).filter((f) =>
|
|
145
|
-
f.endsWith('.schema.json'),
|
|
146
|
-
);
|
|
147
|
-
for (const f of files) {
|
|
148
|
-
copyFileSync(resolve(source, f), resolve(target, f));
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Vite plugin that serves collection schemas and CMS config via virtual modules.
|
|
157
|
-
* @internal Not part of the public API — exported for testing only
|
|
158
|
-
* @param {AstroIntegrationLogger} logger - Astro integration logger for warnings
|
|
159
|
-
* @param {string} root - Project root directory
|
|
160
|
-
* @param {Required<NebulaCMSConfig>} config - Normalized CMS configuration (both paths absolute, no trailing slash)
|
|
161
|
-
* @return {object} A Vite plugin object with configureServer, resolveId, and load hooks
|
|
162
|
-
*/
|
|
163
|
-
export function nebulaVitePlugin(
|
|
164
|
-
logger: AstroIntegrationLogger,
|
|
165
|
-
root: string,
|
|
166
|
-
config: Required<NebulaCMSConfig>,
|
|
167
|
-
) {
|
|
168
|
-
return {
|
|
169
|
-
name: 'vite-plugin-nebula-cms',
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Registers dev middleware: serves schema files from .astro/collections/
|
|
173
|
-
* and rewrites SPA sub-routes under basePath to the basePath page.
|
|
174
|
-
* @param {{ middlewares: { use: Function } }} server - The Vite dev server
|
|
175
|
-
* @return {void}
|
|
176
|
-
*/
|
|
177
|
-
configureServer(server: { middlewares: { use: Function } }) {
|
|
178
|
-
const prefix = config.collectionsPath + '/';
|
|
179
|
-
const collectionsDir = resolve(root, '.astro/collections');
|
|
180
|
-
|
|
181
|
-
// Serve collection schema JSON files
|
|
182
|
-
server.middlewares.use(
|
|
183
|
-
(
|
|
184
|
-
req: { url?: string },
|
|
185
|
-
res: { setHeader: Function; end: Function },
|
|
186
|
-
next: Function,
|
|
187
|
-
) => {
|
|
188
|
-
// Extract pathname, stripping query strings and fragments
|
|
189
|
-
const url = new URL(req.url ?? '', 'http://x').pathname;
|
|
190
|
-
if (!url.startsWith(prefix) || !url.endsWith('.schema.json')) {
|
|
191
|
-
return next();
|
|
192
|
-
}
|
|
193
|
-
const filename = url.slice(prefix.length);
|
|
194
|
-
const filePath = resolve(collectionsDir, filename);
|
|
195
|
-
// Reject path traversal attempts (e.g. /../../../etc/passwd.schema.json)
|
|
196
|
-
if (!filePath.startsWith(collectionsDir + '/')) return next();
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
res.setHeader('Content-Type', 'application/json');
|
|
200
|
-
res.end(readFileSync(filePath, 'utf-8'));
|
|
201
|
-
} catch (err: unknown) {
|
|
202
|
-
// File not found — fall through to Vite's default handler
|
|
203
|
-
if (
|
|
204
|
-
err instanceof Error &&
|
|
205
|
-
(err as NodeJS.ErrnoException).code === 'ENOENT'
|
|
206
|
-
) {
|
|
207
|
-
return next();
|
|
208
|
-
}
|
|
209
|
-
// Re-throw permission errors, I/O errors, etc. so they surface
|
|
210
|
-
throw err;
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// SPA fallback: rewrite HTML requests under basePath to basePath
|
|
216
|
-
server.middlewares.use(
|
|
217
|
-
(
|
|
218
|
-
req: { url?: string; headers?: Record<string, string | undefined> },
|
|
219
|
-
_res: unknown,
|
|
220
|
-
next: Function,
|
|
221
|
-
) => {
|
|
222
|
-
const rawURL = req.url ?? '';
|
|
223
|
-
const accept = req.headers?.accept ?? '';
|
|
224
|
-
|
|
225
|
-
// Only rewrite document requests
|
|
226
|
-
if (!accept.includes('text/html')) return next();
|
|
227
|
-
|
|
228
|
-
const parsed = new URL(rawURL, 'http://x');
|
|
229
|
-
|
|
230
|
-
/*
|
|
231
|
-
* Check segment boundary: /admin/foo rewrites, /administrator does not.
|
|
232
|
-
* Root basePath '/' needs special handling — every path is a sub-path
|
|
233
|
-
* except '/' itself, mirroring router.svelte.ts isUnderBasePath.
|
|
234
|
-
*/
|
|
235
|
-
const isSubPath =
|
|
236
|
-
config.basePath === '/'
|
|
237
|
-
? parsed.pathname !== '/' && parsed.pathname.startsWith('/')
|
|
238
|
-
: parsed.pathname !== config.basePath &&
|
|
239
|
-
parsed.pathname.startsWith(config.basePath + '/');
|
|
240
|
-
|
|
241
|
-
if (isSubPath) {
|
|
242
|
-
// Preserve query string and hash for deep-linking and OAuth callbacks
|
|
243
|
-
req.url = config.basePath + parsed.search + parsed.hash;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return next();
|
|
247
|
-
},
|
|
248
|
-
);
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Resolves virtual:nebula/* imports to Vite-internal IDs.
|
|
253
|
-
* @param {string} id - The module ID being resolved
|
|
254
|
-
* @return {string | undefined} The resolved internal ID, or undefined if not handled
|
|
255
|
-
*/
|
|
256
|
-
resolveId(id: string) {
|
|
257
|
-
if (id === CONFIG_VIRTUAL_ID) return CONFIG_RESOLVED_ID;
|
|
258
|
-
if (id === COLLECTIONS_VIRTUAL_ID) return COLLECTIONS_RESOLVED_ID;
|
|
259
|
-
},
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Generates virtual module source code for config and collections.
|
|
263
|
-
* @param {string} id - The resolved module ID to load
|
|
264
|
-
* @return {string | undefined} Generated module source code, or undefined if not handled
|
|
265
|
-
*/
|
|
266
|
-
load(id: string) {
|
|
267
|
-
if (id === CONFIG_RESOLVED_ID) {
|
|
268
|
-
return `export default ${JSON.stringify(config)};`;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (id !== COLLECTIONS_RESOLVED_ID) return;
|
|
272
|
-
|
|
273
|
-
const collectionsDir = resolve(root, '.astro/collections');
|
|
274
|
-
|
|
275
|
-
// Guard: return empty object if directory doesn't exist
|
|
276
|
-
if (!existsSync(collectionsDir)) {
|
|
277
|
-
logger.warn(
|
|
278
|
-
'`.astro/collections` not found — virtual:nebula/collections will be empty.',
|
|
279
|
-
);
|
|
280
|
-
return 'export default {};';
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const files = readdirSync(collectionsDir).filter((f) =>
|
|
284
|
-
f.endsWith('.schema.json'),
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
const entries = files.map((f) => {
|
|
288
|
-
const name = f.replace('.schema.json', '');
|
|
289
|
-
return ` ${JSON.stringify(name)}: ${JSON.stringify(config.collectionsPath + '/' + f)}`;
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
return `export default {\n${entries.join(',\n')}\n};`;
|
|
293
|
-
},
|
|
294
|
-
};
|
|
295
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { loadDrafts, type Draft } from './storage';
|
|
2
|
-
import { splitFrontmatter } from '../utils/frontmatter';
|
|
3
|
-
import { storageClient } from '../state/state.svelte';
|
|
4
|
-
|
|
5
|
-
// Drafts for the current collection
|
|
6
|
-
let draftList = $state<Draft[]>([]);
|
|
7
|
-
// Map of draftId → whether the live content has diverged from the draft's snapshot
|
|
8
|
-
let outdatedMap = $state<Record<string, boolean>>({});
|
|
9
|
-
|
|
10
|
-
export const drafts = {
|
|
11
|
-
// All drafts for the active collection.
|
|
12
|
-
get all(): Draft[] {
|
|
13
|
-
return draftList;
|
|
14
|
-
},
|
|
15
|
-
// Map of draft ID to whether live content has diverged.
|
|
16
|
-
get outdated(): Record<string, boolean> {
|
|
17
|
-
return outdatedMap;
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
// Worker for off-thread snapshot comparison
|
|
21
|
-
let diffWorker: Worker | null = null;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Initializes the diff worker singleton and wires up the result handler.
|
|
25
|
-
* @return {Worker} The existing or newly created diff worker
|
|
26
|
-
*/
|
|
27
|
-
function ensureDiffWorker(): Worker {
|
|
28
|
-
if (diffWorker) return diffWorker;
|
|
29
|
-
// Uses .js extension because svelte-package does not rewrite URL string literals
|
|
30
|
-
diffWorker = new Worker(new URL('./workers/diff.js', import.meta.url), {
|
|
31
|
-
type: 'module',
|
|
32
|
-
});
|
|
33
|
-
diffWorker.addEventListener('message', (event) => {
|
|
34
|
-
const data = event.data;
|
|
35
|
-
if (data.type === 'diff-result') {
|
|
36
|
-
outdatedMap = data.results;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
return diffWorker;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Loads drafts for a collection from IndexedDB and dispatches snapshot comparisons to the diff worker for any drafts linked to live files. Reads live file contents via the StorageClient.
|
|
44
|
-
* @param {string} collection - The collection to load drafts for
|
|
45
|
-
* @return {Promise<void>}
|
|
46
|
-
*/
|
|
47
|
-
export async function mergeDrafts(collection: string): Promise<void> {
|
|
48
|
-
draftList = await loadDrafts(collection);
|
|
49
|
-
|
|
50
|
-
/*
|
|
51
|
-
* Filter to drafts that need outdated checking:
|
|
52
|
-
* must be linked to a live file (not new), have a snapshot, and have a filename.
|
|
53
|
-
*/
|
|
54
|
-
const candidates = draftList.filter(
|
|
55
|
-
(d) => !d.isNew && d.snapshot && d.filename,
|
|
56
|
-
);
|
|
57
|
-
if (candidates.length === 0) {
|
|
58
|
-
outdatedMap = {};
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/*
|
|
63
|
-
* Read all candidate files in parallel instead of sequentially.
|
|
64
|
-
* js-yaml is dynamically imported because it's a transitive dep — hoist
|
|
65
|
-
* before the loop so the import is evaluated once, not per-candidate.
|
|
66
|
-
*/
|
|
67
|
-
const { load } = await import('js-yaml');
|
|
68
|
-
const settled = await Promise.all(
|
|
69
|
-
candidates.map(async (d) => {
|
|
70
|
-
try {
|
|
71
|
-
const text = await storageClient.readFile(collection, d.filename!);
|
|
72
|
-
const { rawFrontmatter, body } = splitFrontmatter(text);
|
|
73
|
-
const liveFormData = (load(rawFrontmatter) ?? {}) as Record<
|
|
74
|
-
string,
|
|
75
|
-
unknown
|
|
76
|
-
>;
|
|
77
|
-
const liveBody = body.replace(/^\n+/, '').replace(/\n+$/, '');
|
|
78
|
-
return {
|
|
79
|
-
draftId: d.id,
|
|
80
|
-
snapshot: d.snapshot!,
|
|
81
|
-
liveFormData,
|
|
82
|
-
liveBody,
|
|
83
|
-
};
|
|
84
|
-
} catch {
|
|
85
|
-
// File not found or unreadable — skip
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
const entries = settled.filter((e): e is NonNullable<typeof e> => e !== null);
|
|
91
|
-
|
|
92
|
-
if (entries.length === 0) {
|
|
93
|
-
outdatedMap = {};
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Dispatch to the diff worker for off-thread comparison
|
|
98
|
-
const worker = ensureDiffWorker();
|
|
99
|
-
worker.postMessage({ type: 'diff', entries });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Re-reads drafts from IndexedDB for the given collection and updates the reactive drafts list.
|
|
104
|
-
* Used after saving/deleting a draft so the sidebar reflects changes immediately without a full collection reload.
|
|
105
|
-
* @param {string} collection - The collection to refresh drafts for
|
|
106
|
-
* @return {Promise<void>}
|
|
107
|
-
*/
|
|
108
|
-
export async function refreshDrafts(collection: string): Promise<void> {
|
|
109
|
-
draftList = await loadDrafts(collection);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Resets draft-related state and terminates the diff worker. Called during disconnect.
|
|
114
|
-
* @return {void}
|
|
115
|
-
*/
|
|
116
|
-
export function resetDraftMerge(): void {
|
|
117
|
-
diffWorker?.terminate();
|
|
118
|
-
diffWorker = null;
|
|
119
|
-
draftList = [];
|
|
120
|
-
outdatedMap = {};
|
|
121
|
-
}
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
saveDraft as persistDraft,
|
|
3
|
-
loadDraft as fetchDraft,
|
|
4
|
-
deleteDraft as removeDraft,
|
|
5
|
-
type Draft,
|
|
6
|
-
} from './storage';
|
|
7
|
-
import { stableStringify } from '../utils/stable-stringify';
|
|
8
|
-
import {
|
|
9
|
-
applyEditorState,
|
|
10
|
-
_getDraftState,
|
|
11
|
-
_setDraftState,
|
|
12
|
-
} from '../editor/editor.svelte';
|
|
13
|
-
import { storageClient } from '../state/state.svelte';
|
|
14
|
-
import { getFileCategory, getDataFormat } from '../utils/file-types';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Loads a draft by ID from IndexedDB and populates the editor. Falls back to empty state if the draft is not found (safety fallback for the "Add" button flow).
|
|
18
|
-
* @param {string} id - The draft UUID to load
|
|
19
|
-
* @param {string} collection - The collection this draft belongs to
|
|
20
|
-
* @return {Promise<void>}
|
|
21
|
-
*/
|
|
22
|
-
export async function loadDraftById(
|
|
23
|
-
id: string,
|
|
24
|
-
collection: string,
|
|
25
|
-
): Promise<void> {
|
|
26
|
-
const draft = await fetchDraft(id);
|
|
27
|
-
|
|
28
|
-
if (!draft) {
|
|
29
|
-
applyEditorState(
|
|
30
|
-
{
|
|
31
|
-
body: '',
|
|
32
|
-
formData: {},
|
|
33
|
-
filename: '',
|
|
34
|
-
bodyLoaded: true,
|
|
35
|
-
draftId: id,
|
|
36
|
-
isNewDraft: true,
|
|
37
|
-
snapshot: null,
|
|
38
|
-
collection,
|
|
39
|
-
draftCreatedAt: new Date().toISOString(),
|
|
40
|
-
},
|
|
41
|
-
true,
|
|
42
|
-
);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
applyEditorState(
|
|
47
|
-
{
|
|
48
|
-
body: draft.body,
|
|
49
|
-
formData: draft.formData,
|
|
50
|
-
filename: draft.filename ?? '',
|
|
51
|
-
bodyLoaded: true,
|
|
52
|
-
draftId: draft.id,
|
|
53
|
-
isNewDraft: draft.isNew,
|
|
54
|
-
snapshot: draft.snapshot,
|
|
55
|
-
collection,
|
|
56
|
-
draftCreatedAt: draft.createdAt,
|
|
57
|
-
},
|
|
58
|
-
true,
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Saves the current editor content as a draft in IndexedDB. On first save, generates a UUID and createdAt timestamp. For live content edits, captures a snapshot of the original data.
|
|
64
|
-
* @return {Promise<void>}
|
|
65
|
-
*/
|
|
66
|
-
export async function saveDraftToIDB(): Promise<void> {
|
|
67
|
-
const s = _getDraftState();
|
|
68
|
-
_setDraftState({ saving: true });
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
let { draftId, draftCreatedAt, snapshot } = s;
|
|
72
|
-
|
|
73
|
-
// Generate draft ID and timestamp on first save
|
|
74
|
-
if (!draftId) {
|
|
75
|
-
draftId = crypto.randomUUID();
|
|
76
|
-
draftCreatedAt = new Date().toISOString();
|
|
77
|
-
|
|
78
|
-
// For live content edits, capture a snapshot of the original saved data
|
|
79
|
-
if (!s.isNewDraft) {
|
|
80
|
-
snapshot = stableStringify({
|
|
81
|
-
formData: JSON.parse(s.lastSavedFormData),
|
|
82
|
-
body: s.lastSavedBody,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
_setDraftState({ draftId, draftCreatedAt, snapshot });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const draft: Draft = {
|
|
90
|
-
id: draftId,
|
|
91
|
-
collection: s.currentCollection,
|
|
92
|
-
filename: s.filename || null,
|
|
93
|
-
isNew: s.isNewDraft,
|
|
94
|
-
formData: $state.snapshot(s.formData) as Record<string, unknown>,
|
|
95
|
-
body: s.body,
|
|
96
|
-
snapshot,
|
|
97
|
-
createdAt: draftCreatedAt!,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
await persistDraft(draft);
|
|
101
|
-
_setDraftState({
|
|
102
|
-
lastSavedBody: s.body,
|
|
103
|
-
lastSavedFormData: JSON.stringify(s.formData),
|
|
104
|
-
dirty: false,
|
|
105
|
-
});
|
|
106
|
-
} finally {
|
|
107
|
-
_setDraftState({ saving: false });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Legacy alias for saveDraftToIDB — preserves existing test mock call sites.
|
|
113
|
-
* @return {Promise<void>}
|
|
114
|
-
*/
|
|
115
|
-
export async function saveFile(): Promise<void> {
|
|
116
|
-
return saveDraftToIDB();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Serializes editor content based on the target file format. Data files produce pure JSON/YAML/TOML; frontmatter files produce the standard `---` delimited format.
|
|
121
|
-
* @param {string} filename - The target filename, used to determine format
|
|
122
|
-
* @param {Record<string, unknown>} formData - The structured data to serialize
|
|
123
|
-
* @param {string} body - The body content (only used for frontmatter files)
|
|
124
|
-
* @return {Promise<string>} The serialized file content
|
|
125
|
-
*/
|
|
126
|
-
async function serializeContent(
|
|
127
|
-
filename: string,
|
|
128
|
-
formData: Record<string, unknown>,
|
|
129
|
-
body: string,
|
|
130
|
-
): Promise<string> {
|
|
131
|
-
const category = getFileCategory(filename);
|
|
132
|
-
|
|
133
|
-
// Lazy-load js-yaml once for both data and frontmatter YAML paths
|
|
134
|
-
const loadYAML = () => import('js-yaml');
|
|
135
|
-
|
|
136
|
-
if (category === 'data') {
|
|
137
|
-
const format = getDataFormat(filename);
|
|
138
|
-
switch (format) {
|
|
139
|
-
case 'json':
|
|
140
|
-
return JSON.stringify(formData, null, 2) + '\n';
|
|
141
|
-
case 'yaml': {
|
|
142
|
-
const { dump } = await loadYAML();
|
|
143
|
-
return dump(formData, { lineWidth: -1 });
|
|
144
|
-
}
|
|
145
|
-
case 'toml': {
|
|
146
|
-
const { stringify } = await import('smol-toml');
|
|
147
|
-
return stringify(formData);
|
|
148
|
-
}
|
|
149
|
-
default:
|
|
150
|
-
throw new Error(`Unsupported data format: ${format}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Frontmatter files: ---\nyaml\n---\n\nbody\n
|
|
155
|
-
const { dump } = await loadYAML();
|
|
156
|
-
// dump() adds a trailing newline, so the template omits a \n before ---
|
|
157
|
-
const yaml = dump(formData, { lineWidth: -1 });
|
|
158
|
-
return `---\n${yaml}---\n\n${body}\n`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Writes editor content to the storage backend via StorageClient. Dispatches serialization by file format. If originalFilename is provided and differs from filename, deletes the old file (file type conversion). Deletes the associated draft from IndexedDB after a successful write.
|
|
163
|
-
* @param {string} collection - The collection the file belongs to
|
|
164
|
-
* @param {string} filename - The filename to write within the collection
|
|
165
|
-
* @param {string} [originalFilename] - The previous filename if the file was renamed/converted
|
|
166
|
-
* @return {Promise<void>}
|
|
167
|
-
*/
|
|
168
|
-
export async function publishFile(
|
|
169
|
-
collection: string,
|
|
170
|
-
filename: string,
|
|
171
|
-
originalFilename?: string,
|
|
172
|
-
): Promise<void> {
|
|
173
|
-
const s = _getDraftState();
|
|
174
|
-
_setDraftState({ saving: true });
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const content = await serializeContent(filename, s.formData, s.body);
|
|
178
|
-
await storageClient.writeFile(collection, filename, content);
|
|
179
|
-
|
|
180
|
-
// Remove the old file if the filename changed (file type conversion)
|
|
181
|
-
if (originalFilename && originalFilename !== filename) {
|
|
182
|
-
await storageClient.deleteFile(collection, originalFilename);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Clean up the draft from IndexedDB after successful publish
|
|
186
|
-
if (s.draftId) {
|
|
187
|
-
await removeDraft(s.draftId);
|
|
188
|
-
_setDraftState({
|
|
189
|
-
draftId: null,
|
|
190
|
-
isNewDraft: false,
|
|
191
|
-
snapshot: null,
|
|
192
|
-
draftCreatedAt: null,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/*
|
|
197
|
-
* Update originalFilename so subsequent publishes know the current on-disk name.
|
|
198
|
-
* Without this, a second format change would not detect the rename because
|
|
199
|
-
* originalFilename would still point to the pre-first-publish name.
|
|
200
|
-
*/
|
|
201
|
-
_setDraftState({
|
|
202
|
-
lastSavedBody: s.body,
|
|
203
|
-
lastSavedFormData: JSON.stringify(s.formData),
|
|
204
|
-
dirty: false,
|
|
205
|
-
originalFilename: filename,
|
|
206
|
-
});
|
|
207
|
-
} finally {
|
|
208
|
-
_setDraftState({ saving: false });
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Deletes the current draft from IndexedDB and resets draft-related state fields.
|
|
214
|
-
* @return {Promise<void>}
|
|
215
|
-
*/
|
|
216
|
-
export async function deleteCurrentDraft(): Promise<void> {
|
|
217
|
-
const { draftId } = _getDraftState();
|
|
218
|
-
if (draftId) {
|
|
219
|
-
await removeDraft(draftId);
|
|
220
|
-
}
|
|
221
|
-
_setDraftState({
|
|
222
|
-
draftId: null,
|
|
223
|
-
isNewDraft: false,
|
|
224
|
-
snapshot: null,
|
|
225
|
-
draftCreatedAt: null,
|
|
226
|
-
});
|
|
227
|
-
}
|