nebula-cms 0.1.3 → 0.1.5
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 +3 -1
- 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/{src → dist}/client/Admin.svelte +20 -11
- 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/{src → dist}/client/components/dialogs/FilenameDialog.svelte +1 -2
- 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/{src → dist}/client/components/sidebar/AdminSidebar.svelte +2 -4
- 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 +141 -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 +300 -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/client/types/browser-apis.d.ts +39 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +13 -3
- package/.github/workflows/ci.yml +0 -27
- package/.github/workflows/publish.yml +0 -34
- package/.mcp.json +0 -12
- package/.prettierignore +0 -5
- package/.prettierrc.cjs +0 -22
- package/AGENTS.md +0 -183
- package/playground/astro.config.mjs +0 -7
- package/playground/node_modules/.bin/astro +0 -21
- package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +0 -85
- 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-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-FPEUJ7DG.js +0 -27
- package/playground/node_modules/.vite/deps/chunk-FPEUJ7DG.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js +0 -1005
- package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js +0 -204
- package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js +0 -688
- package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js +0 -5099
- package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js.map +0 -7
- package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.js +0 -4376
- package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.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/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/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/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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Storage adapter backed by the GitHub REST API.
|
|
3
|
+
* Uses a Personal Access Token for authentication. All file operations
|
|
4
|
+
* target src/content/{collection}/ within the repository.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Encodes a Uint8Array to a base64 string without using deprecated unescape().
|
|
8
|
+
* @param {Uint8Array} bytes - The bytes to encode
|
|
9
|
+
* @return {string} Base64-encoded string
|
|
10
|
+
*/
|
|
11
|
+
function uint8ToBase64(bytes) {
|
|
12
|
+
let binary = '';
|
|
13
|
+
for (const byte of bytes)
|
|
14
|
+
binary += String.fromCharCode(byte);
|
|
15
|
+
return btoa(binary);
|
|
16
|
+
}
|
|
17
|
+
export class GitHubAdapter {
|
|
18
|
+
token;
|
|
19
|
+
owner;
|
|
20
|
+
repo;
|
|
21
|
+
// Cached on validate(); defaults to 'main' before validation
|
|
22
|
+
defaultBranch = 'main';
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} token - GitHub Personal Access Token
|
|
25
|
+
* @param {string} repoSlug - Repository in "owner/repo" format
|
|
26
|
+
*/
|
|
27
|
+
constructor(token, repoSlug) {
|
|
28
|
+
this.token = token;
|
|
29
|
+
const [owner, repo] = repoSlug.split('/');
|
|
30
|
+
this.owner = owner;
|
|
31
|
+
this.repo = repo;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validates credentials by fetching repo metadata and caches the default branch.
|
|
35
|
+
* @return {Promise<void>}
|
|
36
|
+
*/
|
|
37
|
+
async validate() {
|
|
38
|
+
const res = await this.request('GET', `/repos/${this.owner}/${this.repo}`);
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const status = res.status;
|
|
41
|
+
if (status === 401)
|
|
42
|
+
throw new Error('Invalid or expired token');
|
|
43
|
+
if (status === 403)
|
|
44
|
+
throw new Error('Token lacks repository access');
|
|
45
|
+
if (status === 404)
|
|
46
|
+
throw new Error(`Repository "${this.owner}/${this.repo}" not found`);
|
|
47
|
+
throw new Error(`GitHub API error: ${status}`);
|
|
48
|
+
}
|
|
49
|
+
const data = await res.json();
|
|
50
|
+
this.defaultBranch = data.default_branch;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Lists files in a collection matching the given extensions, with their raw content.
|
|
54
|
+
* @param {string} collection - The collection name
|
|
55
|
+
* @param {string[]} extensions - File extensions to include (e.g. ['.md', '.mdx'])
|
|
56
|
+
* @return {Promise<FileEntry[]>} Array of filename + content pairs
|
|
57
|
+
*/
|
|
58
|
+
async listFiles(collection, extensions) {
|
|
59
|
+
const path = `src/content/${collection}`;
|
|
60
|
+
// Get directory listing
|
|
61
|
+
const listRes = await this.request('GET', `/repos/${this.owner}/${this.repo}/contents/${path}?ref=${this.defaultBranch}`);
|
|
62
|
+
if (!listRes.ok) {
|
|
63
|
+
if (listRes.status === 404)
|
|
64
|
+
return [];
|
|
65
|
+
throw new Error(`Failed to list files: ${listRes.status}`);
|
|
66
|
+
}
|
|
67
|
+
const listing = await listRes.json();
|
|
68
|
+
// Filter to requested extensions and fetch all files in parallel
|
|
69
|
+
const filtered = listing.filter((f) => extensions.some((ext) => f.name.endsWith(ext)));
|
|
70
|
+
return Promise.all(filtered.map(async (file) => ({
|
|
71
|
+
filename: file.name,
|
|
72
|
+
content: await this.readFile(collection, file.name),
|
|
73
|
+
})));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Deletes a file from the repository via the Contents API.
|
|
77
|
+
* @param {string} collection - The collection name
|
|
78
|
+
* @param {string} filename - The filename to delete
|
|
79
|
+
* @return {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
async deleteFile(collection, filename) {
|
|
82
|
+
const path = `src/content/${collection}/${filename}`;
|
|
83
|
+
// Get the current SHA (required by the GitHub Contents API for deletion)
|
|
84
|
+
const existing = await this.request('GET', `/repos/${this.owner}/${this.repo}/contents/${path}?ref=${this.defaultBranch}`);
|
|
85
|
+
if (!existing.ok)
|
|
86
|
+
throw new Error(`File not found for deletion: ${path}`);
|
|
87
|
+
const data = await existing.json();
|
|
88
|
+
const res = await this.request('DELETE', `/repos/${this.owner}/${this.repo}/contents/${path}`, undefined, {
|
|
89
|
+
message: `Delete ${path}`,
|
|
90
|
+
sha: data.sha,
|
|
91
|
+
branch: this.defaultBranch,
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok)
|
|
94
|
+
throw new Error(`Failed to delete ${path}: ${res.status}`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reads a single file's raw content via the raw+json Accept header.
|
|
98
|
+
* @param {string} collection - The collection name
|
|
99
|
+
* @param {string} filename - The filename
|
|
100
|
+
* @return {Promise<string>} The file content
|
|
101
|
+
*/
|
|
102
|
+
async readFile(collection, filename) {
|
|
103
|
+
const path = `src/content/${collection}/${filename}`;
|
|
104
|
+
const res = await this.request('GET', `/repos/${this.owner}/${this.repo}/contents/${path}?ref=${this.defaultBranch}`, { Accept: 'application/vnd.github.raw+json' });
|
|
105
|
+
if (!res.ok) {
|
|
106
|
+
throw new Error(`Failed to read ${path}: ${res.status}`);
|
|
107
|
+
}
|
|
108
|
+
return res.text();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Writes a single file via the Contents API. Fetches the current SHA for updates.
|
|
112
|
+
* @param {string} collection - The collection name
|
|
113
|
+
* @param {string} filename - The filename
|
|
114
|
+
* @param {string} content - The content to write
|
|
115
|
+
* @return {Promise<void>}
|
|
116
|
+
*/
|
|
117
|
+
async writeFile(collection, filename, content) {
|
|
118
|
+
const path = `src/content/${collection}/${filename}`;
|
|
119
|
+
// Get current SHA if file exists (required by GitHub API for updates)
|
|
120
|
+
let sha;
|
|
121
|
+
const existing = await this.request('GET', `/repos/${this.owner}/${this.repo}/contents/${path}?ref=${this.defaultBranch}`);
|
|
122
|
+
if (existing.ok) {
|
|
123
|
+
const data = await existing.json();
|
|
124
|
+
sha = data.sha;
|
|
125
|
+
}
|
|
126
|
+
const body = {
|
|
127
|
+
message: `Update ${path}`,
|
|
128
|
+
content: uint8ToBase64(new TextEncoder().encode(content)),
|
|
129
|
+
branch: this.defaultBranch,
|
|
130
|
+
};
|
|
131
|
+
if (sha)
|
|
132
|
+
body.sha = sha;
|
|
133
|
+
const res = await this.request('PUT', `/repos/${this.owner}/${this.repo}/contents/${path}`, undefined, body);
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
const errText = await res.text();
|
|
136
|
+
throw new Error(`Failed to write ${path}: ${res.status} ${errText}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Writes multiple files in a single atomic commit using the Git Trees + Commits API.
|
|
141
|
+
* @param {FileWrite[]} files - Array of files to write
|
|
142
|
+
* @return {Promise<void>}
|
|
143
|
+
*/
|
|
144
|
+
async writeFiles(files) {
|
|
145
|
+
if (files.length === 0)
|
|
146
|
+
return;
|
|
147
|
+
if (files.length === 1) {
|
|
148
|
+
await this.writeFile(files[0].collection, files[0].filename, files[0].content);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// 1. Get current commit SHA for the default branch
|
|
152
|
+
const refRes = await this.request('GET', `/repos/${this.owner}/${this.repo}/git/ref/heads/${this.defaultBranch}`);
|
|
153
|
+
if (!refRes.ok)
|
|
154
|
+
throw new Error(`Failed to get branch ref: ${refRes.status}`);
|
|
155
|
+
const refData = await refRes.json();
|
|
156
|
+
const baseCommitSHA = refData.object.sha;
|
|
157
|
+
// Get the base tree SHA from the current commit
|
|
158
|
+
const commitRes = await this.request('GET', `/repos/${this.owner}/${this.repo}/git/commits/${baseCommitSHA}`);
|
|
159
|
+
if (!commitRes.ok)
|
|
160
|
+
throw new Error(`Failed to get commit: ${commitRes.status}`);
|
|
161
|
+
const commitData = await commitRes.json();
|
|
162
|
+
const baseTreeSHA = commitData.tree.sha;
|
|
163
|
+
// 2. Create a new tree containing all file changes
|
|
164
|
+
const tree = files.map((f) => ({
|
|
165
|
+
path: `src/content/${f.collection}/${f.filename}`,
|
|
166
|
+
mode: '100644',
|
|
167
|
+
type: 'blob',
|
|
168
|
+
content: f.content,
|
|
169
|
+
}));
|
|
170
|
+
const treeRes = await this.request('POST', `/repos/${this.owner}/${this.repo}/git/trees`, undefined, { base_tree: baseTreeSHA, tree });
|
|
171
|
+
if (!treeRes.ok)
|
|
172
|
+
throw new Error(`Failed to create tree: ${treeRes.status}`);
|
|
173
|
+
const treeData = await treeRes.json();
|
|
174
|
+
// 3. Create a new commit pointing at the new tree
|
|
175
|
+
const paths = files.map((f) => `${f.collection}/${f.filename}`).join(', ');
|
|
176
|
+
const newCommitRes = await this.request('POST', `/repos/${this.owner}/${this.repo}/git/commits`, undefined, {
|
|
177
|
+
message: `Update ${paths}`,
|
|
178
|
+
tree: treeData.sha,
|
|
179
|
+
parents: [baseCommitSHA],
|
|
180
|
+
});
|
|
181
|
+
if (!newCommitRes.ok)
|
|
182
|
+
throw new Error(`Failed to create commit: ${newCommitRes.status}`);
|
|
183
|
+
const newCommitData = await newCommitRes.json();
|
|
184
|
+
// 4. Advance the branch ref to the new commit
|
|
185
|
+
const updateRefRes = await this.request('PATCH', `/repos/${this.owner}/${this.repo}/git/refs/heads/${this.defaultBranch}`, undefined, { sha: newCommitData.sha });
|
|
186
|
+
if (!updateRefRes.ok)
|
|
187
|
+
throw new Error(`Failed to update ref: ${updateRefRes.status}`);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Makes an authenticated request to the GitHub API.
|
|
191
|
+
* @param {string} method - HTTP method
|
|
192
|
+
* @param {string} path - API path (appended to https://api.github.com)
|
|
193
|
+
* @param {Record<string, string>} [extraHeaders] - Additional headers to merge
|
|
194
|
+
* @param {unknown} [body] - JSON body to send
|
|
195
|
+
* @return {Promise<Response>} The fetch response
|
|
196
|
+
*/
|
|
197
|
+
async request(method, path, extraHeaders, body) {
|
|
198
|
+
const headers = {
|
|
199
|
+
Authorization: `Bearer ${this.token}`,
|
|
200
|
+
'X-GitHub-Api-Version': '2026-03-10',
|
|
201
|
+
// Default to JSON so the browser cache distinguishes from raw content requests
|
|
202
|
+
Accept: 'application/vnd.github+json',
|
|
203
|
+
...extraHeaders,
|
|
204
|
+
};
|
|
205
|
+
if (body) {
|
|
206
|
+
headers['Content-Type'] = 'application/json';
|
|
207
|
+
}
|
|
208
|
+
return fetch(`https://api.github.com${path}`, {
|
|
209
|
+
method,
|
|
210
|
+
headers,
|
|
211
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
212
|
+
// API responses must never be served from browser cache
|
|
213
|
+
cache: 'no-store',
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend configuration stored in IndexedDB. Tagged union discriminated on `type`.
|
|
3
|
+
* Security note: the GitHub token is stored in plaintext in IndexedDB. This is a
|
|
4
|
+
* deliberate trade-off for a client-only app with no server to proxy through.
|
|
5
|
+
* Same-origin policy protects it from other sites, but any XSS vulnerability
|
|
6
|
+
* would expose the token.
|
|
7
|
+
*/
|
|
8
|
+
export type BackendConfig = {
|
|
9
|
+
type: 'fsa';
|
|
10
|
+
handle: FileSystemDirectoryHandle;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'github';
|
|
13
|
+
token: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Stores backend configuration in IndexedDB for persistence across sessions.
|
|
18
|
+
* @param {BackendConfig} config - The backend config to store
|
|
19
|
+
* @return {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
export declare function saveBackend(config: BackendConfig): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves stored backend configuration from IndexedDB. Handles migration from the old format where a bare FileSystemDirectoryHandle was stored.
|
|
24
|
+
* @return {Promise<BackendConfig | null>} The stored config, or null if none exists
|
|
25
|
+
*/
|
|
26
|
+
export declare function loadBackend(): Promise<BackendConfig | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Removes the stored backend configuration from IndexedDB. Also clears the legacy key if present.
|
|
29
|
+
* @return {Promise<void>}
|
|
30
|
+
*/
|
|
31
|
+
export declare function clearBackend(): Promise<void>;
|
|
32
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../src/client/js/storage/storage.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtE;AAED;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAyBjE;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAWlD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Backend configuration persistence using IndexedDB.
|
|
3
|
+
* Stores and retrieves the active storage backend (FSA or GitHub).
|
|
4
|
+
*/
|
|
5
|
+
import { openDB } from './db';
|
|
6
|
+
// Fixed key for the backend config
|
|
7
|
+
const BACKEND_KEY = 'backend';
|
|
8
|
+
/**
|
|
9
|
+
* Stores backend configuration in IndexedDB for persistence across sessions.
|
|
10
|
+
* @param {BackendConfig} config - The backend config to store
|
|
11
|
+
* @return {Promise<void>}
|
|
12
|
+
*/
|
|
13
|
+
export async function saveBackend(config) {
|
|
14
|
+
const db = await openDB();
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const tx = db.transaction('handles', 'readwrite');
|
|
17
|
+
tx.objectStore('handles').put(config, BACKEND_KEY);
|
|
18
|
+
tx.oncomplete = () => resolve();
|
|
19
|
+
tx.onerror = () => reject(tx.error);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves stored backend configuration from IndexedDB. Handles migration from the old format where a bare FileSystemDirectoryHandle was stored.
|
|
24
|
+
* @return {Promise<BackendConfig | null>} The stored config, or null if none exists
|
|
25
|
+
*/
|
|
26
|
+
export async function loadBackend() {
|
|
27
|
+
const db = await openDB();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const tx = db.transaction('handles', 'readonly');
|
|
30
|
+
const request = tx.objectStore('handles').get(BACKEND_KEY);
|
|
31
|
+
request.onsuccess = () => {
|
|
32
|
+
const result = request.result;
|
|
33
|
+
if (!result) {
|
|
34
|
+
// Check for old-format handle stored under the legacy key
|
|
35
|
+
const legacyRequest = tx.objectStore('handles').get('projectRoot');
|
|
36
|
+
legacyRequest.onsuccess = () => {
|
|
37
|
+
const legacy = legacyRequest.result;
|
|
38
|
+
if (legacy && legacy instanceof FileSystemDirectoryHandle) {
|
|
39
|
+
resolve({ type: 'fsa', handle: legacy });
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
resolve(null);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
legacyRequest.onerror = () => resolve(null);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
resolve(result);
|
|
49
|
+
};
|
|
50
|
+
request.onerror = () => reject(request.error);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Removes the stored backend configuration from IndexedDB. Also clears the legacy key if present.
|
|
55
|
+
* @return {Promise<void>}
|
|
56
|
+
*/
|
|
57
|
+
export async function clearBackend() {
|
|
58
|
+
const db = await openDB();
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const tx = db.transaction('handles', 'readwrite');
|
|
61
|
+
const store = tx.objectStore('handles');
|
|
62
|
+
store.delete(BACKEND_KEY);
|
|
63
|
+
// Clean up legacy key if present
|
|
64
|
+
store.delete('projectRoot');
|
|
65
|
+
tx.oncomplete = () => resolve();
|
|
66
|
+
tx.onerror = () => reject(tx.error);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../../../src/client/js/storage/workers/frontmatter.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Orchestrator Worker
|
|
3
|
+
*
|
|
4
|
+
* Routes file parsing by category: frontmatter files have their YAML block
|
|
5
|
+
* extracted via string manipulation and sent to the YAML parser worker;
|
|
6
|
+
* JSON data files are parsed inline; YAML/TOML data files are sent to their
|
|
7
|
+
* respective parser workers. Parser workers are lazily spawned on first need.
|
|
8
|
+
*/
|
|
9
|
+
import { StorageClient } from '../client';
|
|
10
|
+
import { getFileCategory, getDataFormat } from '../../utils/file-types';
|
|
11
|
+
import { splitFrontmatter } from '../../utils/frontmatter';
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the raw YAML block from a frontmatter-delimited file (markdown/MDX/Markdoc).
|
|
14
|
+
* Delegates BOM stripping, CRLF normalization, and delimiter logic to splitFrontmatter.
|
|
15
|
+
* Returns the raw YAML string without parsing it — parsing is delegated to the YAML parser worker.
|
|
16
|
+
* @param {string} content - Raw file content
|
|
17
|
+
* @return {string | null} The raw YAML string between --- delimiters, or null if none found
|
|
18
|
+
*/
|
|
19
|
+
function extractYamlBlock(content) {
|
|
20
|
+
const { rawFrontmatter } = splitFrontmatter(content);
|
|
21
|
+
return rawFrontmatter.trim() ? rawFrontmatter : null;
|
|
22
|
+
}
|
|
23
|
+
/*
|
|
24
|
+
//////////////////////////////
|
|
25
|
+
// Parser worker management
|
|
26
|
+
//////////////////////////////
|
|
27
|
+
*/
|
|
28
|
+
// Lazily-spawned parser workers
|
|
29
|
+
let yamlWorker = null;
|
|
30
|
+
let tomlWorker = null;
|
|
31
|
+
// Incrementing ID for correlating batch requests with responses
|
|
32
|
+
let batchIdCounter = 0;
|
|
33
|
+
// Pending batch response promises keyed by ID
|
|
34
|
+
const pendingBatches = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* Returns the lazily-spawned YAML parser worker, creating it on first call.
|
|
37
|
+
* Uses `.js` extension in the URL because svelte-package doesn't rewrite URL strings.
|
|
38
|
+
* @return {Worker} The YAML parser worker instance
|
|
39
|
+
*/
|
|
40
|
+
function getYamlWorker() {
|
|
41
|
+
if (!yamlWorker) {
|
|
42
|
+
yamlWorker = new Worker(new URL('./yaml-parser.js', import.meta.url), {
|
|
43
|
+
type: 'module',
|
|
44
|
+
});
|
|
45
|
+
yamlWorker.onmessage = handleParserResponse;
|
|
46
|
+
yamlWorker.onerror = handleWorkerError;
|
|
47
|
+
}
|
|
48
|
+
return yamlWorker;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the lazily-spawned TOML parser worker, creating it on first call.
|
|
52
|
+
* Uses `.js` extension in the URL because svelte-package doesn't rewrite URL strings.
|
|
53
|
+
* @return {Worker} The TOML parser worker instance
|
|
54
|
+
*/
|
|
55
|
+
function getTomlWorker() {
|
|
56
|
+
if (!tomlWorker) {
|
|
57
|
+
tomlWorker = new Worker(new URL('./toml-parser.js', import.meta.url), {
|
|
58
|
+
type: 'module',
|
|
59
|
+
});
|
|
60
|
+
tomlWorker.onmessage = handleParserResponse;
|
|
61
|
+
tomlWorker.onerror = handleWorkerError;
|
|
62
|
+
}
|
|
63
|
+
return tomlWorker;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Handles fatal errors from parser workers by rejecting all pending batch promises
|
|
67
|
+
* so callers don't hang indefinitely waiting for a response that will never arrive.
|
|
68
|
+
* @param {ErrorEvent} e - The error event from the worker
|
|
69
|
+
* @return {void}
|
|
70
|
+
*/
|
|
71
|
+
function handleWorkerError(e) {
|
|
72
|
+
for (const [id, { reject }] of pendingBatches) {
|
|
73
|
+
reject(new Error(`Parser worker error: ${e.message}`));
|
|
74
|
+
pendingBatches.delete(id);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Handles responses from parser workers by resolving/rejecting the corresponding
|
|
79
|
+
* pending batch promise based on the response ID.
|
|
80
|
+
* @param {MessageEvent} event - The message event from the parser worker
|
|
81
|
+
* @return {void}
|
|
82
|
+
*/
|
|
83
|
+
function handleParserResponse(event) {
|
|
84
|
+
const { type, id, ok, results, error } = event.data;
|
|
85
|
+
// Ignore messages that aren't batch results (e.g. single parse results, stringify results)
|
|
86
|
+
if (type !== 'parse-batch-result')
|
|
87
|
+
return;
|
|
88
|
+
const pending = pendingBatches.get(id);
|
|
89
|
+
if (!pending)
|
|
90
|
+
return;
|
|
91
|
+
pendingBatches.delete(id);
|
|
92
|
+
if (ok) {
|
|
93
|
+
pending.resolve(results);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
pending.reject(new Error(error));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Sends a batch of key/content pairs to a parser worker and returns a promise
|
|
101
|
+
* that resolves with the parsed results map. Uses an incrementing ID for
|
|
102
|
+
* request/response correlation, following the same pattern as StorageClient.
|
|
103
|
+
* @param {Worker} worker - The parser worker to send the batch to
|
|
104
|
+
* @param {BatchItem[]} items - Array of key/content pairs to parse
|
|
105
|
+
* @return {Promise<Record<string, Record<string, unknown>>>} Map of key to parsed data
|
|
106
|
+
*/
|
|
107
|
+
function sendBatch(worker, items) {
|
|
108
|
+
const id = String(++batchIdCounter);
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
pendingBatches.set(id, { resolve, reject });
|
|
111
|
+
worker.postMessage({ type: 'parse-batch', id, items });
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/*
|
|
115
|
+
//////////////////////////////
|
|
116
|
+
// File categorization and parsing
|
|
117
|
+
//////////////////////////////
|
|
118
|
+
*/
|
|
119
|
+
/**
|
|
120
|
+
* Processes a list of files by categorizing each file, collecting batch items
|
|
121
|
+
* for YAML/TOML parser workers, parsing JSON inline, and assembling the final
|
|
122
|
+
* items array with parsed data.
|
|
123
|
+
* @param {FileEntry[]} files - The files returned by the storage adapter
|
|
124
|
+
* @return {Promise<Array<{ filename: string; data: Record<string, unknown> }>>} Parsed items
|
|
125
|
+
*/
|
|
126
|
+
async function processFiles(files) {
|
|
127
|
+
// Inline-parsed results (JSON files)
|
|
128
|
+
const inlineResults = [];
|
|
129
|
+
// Batch items for YAML worker (frontmatter YAML blocks + YAML data files)
|
|
130
|
+
const yamlBatch = [];
|
|
131
|
+
// Batch items for TOML worker
|
|
132
|
+
const tomlBatch = [];
|
|
133
|
+
// Files with no frontmatter get empty data
|
|
134
|
+
const emptyDataFiles = [];
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const category = getFileCategory(file.filename);
|
|
137
|
+
if (category === 'frontmatter') {
|
|
138
|
+
const yamlBlock = extractYamlBlock(file.content);
|
|
139
|
+
if (yamlBlock) {
|
|
140
|
+
yamlBatch.push({ key: file.filename, content: yamlBlock });
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// No frontmatter found — include with empty data
|
|
144
|
+
emptyDataFiles.push(file.filename);
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (category === 'data') {
|
|
149
|
+
const format = getDataFormat(file.filename);
|
|
150
|
+
if (format === 'json') {
|
|
151
|
+
try {
|
|
152
|
+
const data = JSON.parse(file.content);
|
|
153
|
+
inlineResults.push({ filename: file.filename, data });
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Invalid JSON — include with empty data
|
|
157
|
+
inlineResults.push({ filename: file.filename, data: {} });
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (format === 'yaml') {
|
|
162
|
+
yamlBatch.push({ key: file.filename, content: file.content });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (format === 'toml') {
|
|
166
|
+
tomlBatch.push({ key: file.filename, content: file.content });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Unrecognised file type — include with empty data
|
|
171
|
+
emptyDataFiles.push(file.filename);
|
|
172
|
+
}
|
|
173
|
+
// Send batches to parser workers in parallel
|
|
174
|
+
const promises = [];
|
|
175
|
+
let yamlPromiseIdx = -1;
|
|
176
|
+
let tomlPromiseIdx = -1;
|
|
177
|
+
if (yamlBatch.length > 0) {
|
|
178
|
+
yamlPromiseIdx = promises.length;
|
|
179
|
+
promises.push(sendBatch(getYamlWorker(), yamlBatch));
|
|
180
|
+
}
|
|
181
|
+
if (tomlBatch.length > 0) {
|
|
182
|
+
tomlPromiseIdx = promises.length;
|
|
183
|
+
promises.push(sendBatch(getTomlWorker(), tomlBatch));
|
|
184
|
+
}
|
|
185
|
+
const batchResults = await Promise.all(promises);
|
|
186
|
+
// Assemble final items from all sources
|
|
187
|
+
const items = [];
|
|
188
|
+
// Add inline results (JSON)
|
|
189
|
+
items.push(...inlineResults);
|
|
190
|
+
// Add YAML results — filenames are derived from the batch result keys
|
|
191
|
+
if (yamlPromiseIdx >= 0) {
|
|
192
|
+
const yamlResults = batchResults[yamlPromiseIdx];
|
|
193
|
+
for (const [filename, data] of Object.entries(yamlResults)) {
|
|
194
|
+
items.push({ filename, data });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Add TOML results — filenames are derived from the batch result keys
|
|
198
|
+
if (tomlPromiseIdx >= 0) {
|
|
199
|
+
const tomlResults = batchResults[tomlPromiseIdx];
|
|
200
|
+
for (const [filename, data] of Object.entries(tomlResults)) {
|
|
201
|
+
items.push({ filename, data });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Add empty-data files
|
|
205
|
+
for (const filename of emptyDataFiles) {
|
|
206
|
+
items.push({ filename, data: {} });
|
|
207
|
+
}
|
|
208
|
+
return items;
|
|
209
|
+
}
|
|
210
|
+
/*
|
|
211
|
+
//////////////////////////////
|
|
212
|
+
// Main message handler
|
|
213
|
+
//////////////////////////////
|
|
214
|
+
*/
|
|
215
|
+
// Storage client, initialized when the main thread transfers a port
|
|
216
|
+
let storageClient = null;
|
|
217
|
+
// Handle messages from main thread
|
|
218
|
+
self.addEventListener('message', async (event) => {
|
|
219
|
+
const { type } = event.data;
|
|
220
|
+
if (type === 'port') {
|
|
221
|
+
// Main thread is transferring a MessagePort connected to the storage SharedWorker
|
|
222
|
+
const port = event.ports[0];
|
|
223
|
+
storageClient = new StorageClient(port);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (type === 'parse') {
|
|
227
|
+
const { collection } = event.data;
|
|
228
|
+
if (!storageClient) {
|
|
229
|
+
self.postMessage({
|
|
230
|
+
type: 'error',
|
|
231
|
+
message: 'Storage port not initialized',
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
// Pass extensions from the message, defaulting to markdown for backward compatibility
|
|
237
|
+
const extensions = event.data.extensions ?? ['.md', '.mdx'];
|
|
238
|
+
const files = await storageClient.listFiles(collection, extensions);
|
|
239
|
+
const items = await processFiles(files);
|
|
240
|
+
// Sort alphabetically by title, falling back to filename
|
|
241
|
+
items.sort((a, b) => {
|
|
242
|
+
const aTitle = typeof a.data.title === 'string' ? a.data.title : a.filename;
|
|
243
|
+
const bTitle = typeof b.data.title === 'string' ? b.data.title : b.filename;
|
|
244
|
+
return aTitle.toLowerCase().localeCompare(bTitle.toLowerCase());
|
|
245
|
+
});
|
|
246
|
+
self.postMessage({ type: 'result', collection, items });
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
250
|
+
self.postMessage({ type: 'error', message });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../src/client/js/storage/workers/storage.ts"],"names":[],"mappings":""}
|