nebula-cms 0.1.3 → 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.
Files changed (380) hide show
  1. package/README.md +2 -0
  2. package/dist/astro/index.d.ts +43 -0
  3. package/dist/astro/index.d.ts.map +1 -0
  4. package/dist/astro/index.js +223 -0
  5. package/dist/client/Admin.svelte.d.ts +11 -0
  6. package/dist/client/Admin.svelte.d.ts.map +1 -0
  7. package/dist/client/components/BackendPicker.svelte.d.ts +4 -0
  8. package/dist/client/components/BackendPicker.svelte.d.ts.map +1 -0
  9. package/dist/client/components/DraftChip.svelte.d.ts +10 -0
  10. package/dist/client/components/DraftChip.svelte.d.ts.map +1 -0
  11. package/dist/client/components/MetadataForm.svelte.d.ts +12 -0
  12. package/dist/client/components/MetadataForm.svelte.d.ts.map +1 -0
  13. package/dist/client/components/ThemeToggle.svelte.d.ts +19 -0
  14. package/dist/client/components/ThemeToggle.svelte.d.ts.map +1 -0
  15. package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts +11 -0
  16. package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts.map +1 -0
  17. package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts +13 -0
  18. package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts.map +1 -0
  19. package/dist/client/components/editor/EditorPane.svelte.d.ts +4 -0
  20. package/dist/client/components/editor/EditorPane.svelte.d.ts.map +1 -0
  21. package/dist/client/components/editor/EditorTabs.svelte.d.ts +8 -0
  22. package/dist/client/components/editor/EditorTabs.svelte.d.ts.map +1 -0
  23. package/dist/client/components/editor/EditorToolbar.svelte.d.ts +4 -0
  24. package/dist/client/components/editor/EditorToolbar.svelte.d.ts.map +1 -0
  25. package/dist/client/components/editor/FormatSelector.svelte.d.ts +4 -0
  26. package/dist/client/components/editor/FormatSelector.svelte.d.ts.map +1 -0
  27. package/dist/client/components/editor/Toolbar.svelte.d.ts +19 -0
  28. package/dist/client/components/editor/Toolbar.svelte.d.ts.map +1 -0
  29. package/dist/client/components/fields/ArrayField.svelte.d.ts +15 -0
  30. package/dist/client/components/fields/ArrayField.svelte.d.ts.map +1 -0
  31. package/dist/client/components/fields/ArrayItem.svelte.d.ts +28 -0
  32. package/dist/client/components/fields/ArrayItem.svelte.d.ts.map +1 -0
  33. package/dist/client/components/fields/BooleanField.svelte.d.ts +16 -0
  34. package/dist/client/components/fields/BooleanField.svelte.d.ts.map +1 -0
  35. package/dist/client/components/fields/DateField.svelte.d.ts +16 -0
  36. package/dist/client/components/fields/DateField.svelte.d.ts.map +1 -0
  37. package/dist/client/components/fields/EnumField.svelte.d.ts +17 -0
  38. package/dist/client/components/fields/EnumField.svelte.d.ts.map +1 -0
  39. package/dist/client/components/fields/FieldWrapper.svelte.d.ts +18 -0
  40. package/dist/client/components/fields/FieldWrapper.svelte.d.ts.map +1 -0
  41. package/dist/client/components/fields/NumberField.svelte.d.ts +16 -0
  42. package/dist/client/components/fields/NumberField.svelte.d.ts.map +1 -0
  43. package/dist/client/components/fields/ObjectField.svelte.d.ts +16 -0
  44. package/dist/client/components/fields/ObjectField.svelte.d.ts.map +1 -0
  45. package/dist/client/components/fields/SchemaField.svelte.d.ts +16 -0
  46. package/dist/client/components/fields/SchemaField.svelte.d.ts.map +1 -0
  47. package/dist/client/components/fields/StringField.svelte.d.ts +16 -0
  48. package/dist/client/components/fields/StringField.svelte.d.ts.map +1 -0
  49. package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts +19 -0
  50. package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts.map +1 -0
  51. package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts +12 -0
  52. package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts.map +1 -0
  53. package/dist/client/css/icons.css +29 -0
  54. package/dist/client/index.d.ts +2 -0
  55. package/dist/client/index.d.ts.map +1 -0
  56. package/dist/client/js/drafts/merge.svelte.d.ts +24 -0
  57. package/dist/client/js/drafts/merge.svelte.d.ts.map +1 -0
  58. package/dist/client/js/drafts/merge.svelte.js +106 -0
  59. package/dist/client/js/drafts/ops.svelte.d.ts +31 -0
  60. package/dist/client/js/drafts/ops.svelte.d.ts.map +1 -0
  61. package/dist/client/js/drafts/ops.svelte.js +182 -0
  62. package/dist/client/js/drafts/storage.d.ts +45 -0
  63. package/dist/client/js/drafts/storage.d.ts.map +1 -0
  64. package/dist/client/js/drafts/storage.js +76 -0
  65. package/dist/client/js/drafts/workers/diff.d.ts +2 -0
  66. package/dist/client/js/drafts/workers/diff.d.ts.map +1 -0
  67. package/dist/client/js/drafts/workers/diff.js +20 -0
  68. package/dist/client/js/editor/editor.svelte.d.ts +124 -0
  69. package/dist/client/js/editor/editor.svelte.d.ts.map +1 -0
  70. package/dist/client/js/editor/editor.svelte.js +294 -0
  71. package/dist/client/js/editor/languages.d.ts +11 -0
  72. package/dist/client/js/editor/languages.d.ts.map +1 -0
  73. package/dist/client/js/editor/languages.js +93 -0
  74. package/dist/client/js/editor/link-wrap.d.ts +6 -0
  75. package/dist/client/js/editor/link-wrap.d.ts.map +1 -0
  76. package/{src/client/js/editor/link-wrap.ts → dist/client/js/editor/link-wrap.js} +17 -24
  77. package/dist/client/js/editor/markdown-shortcuts.d.ts +4 -0
  78. package/dist/client/js/editor/markdown-shortcuts.d.ts.map +1 -0
  79. package/dist/client/js/editor/markdown-shortcuts.js +219 -0
  80. package/dist/client/js/handlers/admin.d.ts +64 -0
  81. package/dist/client/js/handlers/admin.d.ts.map +1 -0
  82. package/dist/client/js/handlers/admin.js +186 -0
  83. package/dist/client/js/state/dialogs.svelte.d.ts +16 -0
  84. package/dist/client/js/state/dialogs.svelte.d.ts.map +1 -0
  85. package/dist/client/js/state/dialogs.svelte.js +28 -0
  86. package/dist/client/js/state/router.svelte.d.ts +44 -0
  87. package/dist/client/js/state/router.svelte.d.ts.map +1 -0
  88. package/dist/client/js/state/router.svelte.js +135 -0
  89. package/dist/client/js/state/schema.svelte.d.ts +51 -0
  90. package/dist/client/js/state/schema.svelte.d.ts.map +1 -0
  91. package/{src/client/js/state/schema.svelte.ts → dist/client/js/state/schema.svelte.js} +55 -70
  92. package/dist/client/js/state/state.svelte.d.ts +68 -0
  93. package/dist/client/js/state/state.svelte.d.ts.map +1 -0
  94. package/dist/client/js/state/state.svelte.js +291 -0
  95. package/dist/client/js/state/theme.svelte.d.ts +24 -0
  96. package/dist/client/js/state/theme.svelte.d.ts.map +1 -0
  97. package/{src/client/js/state/theme.svelte.ts → dist/client/js/state/theme.svelte.js} +54 -91
  98. package/dist/client/js/storage/adapter.d.ts +130 -0
  99. package/dist/client/js/storage/adapter.d.ts.map +1 -0
  100. package/dist/client/js/storage/adapter.js +5 -0
  101. package/dist/client/js/storage/client.d.ts +72 -0
  102. package/dist/client/js/storage/client.d.ts.map +1 -0
  103. package/dist/client/js/storage/client.js +121 -0
  104. package/dist/client/js/storage/db.d.ts +8 -0
  105. package/dist/client/js/storage/db.d.ts.map +1 -0
  106. package/dist/client/js/storage/db.js +35 -0
  107. package/dist/client/js/storage/fsa.d.ts +51 -0
  108. package/dist/client/js/storage/fsa.d.ts.map +1 -0
  109. package/dist/client/js/storage/fsa.js +91 -0
  110. package/dist/client/js/storage/github.d.ts +62 -0
  111. package/dist/client/js/storage/github.d.ts.map +1 -0
  112. package/dist/client/js/storage/github.js +216 -0
  113. package/dist/client/js/storage/storage.d.ts +32 -0
  114. package/dist/client/js/storage/storage.d.ts.map +1 -0
  115. package/dist/client/js/storage/storage.js +68 -0
  116. package/dist/client/js/storage/workers/frontmatter.d.ts +2 -0
  117. package/dist/client/js/storage/workers/frontmatter.d.ts.map +1 -0
  118. package/dist/client/js/storage/workers/frontmatter.js +253 -0
  119. package/dist/client/js/storage/workers/storage.d.ts +2 -0
  120. package/dist/client/js/storage/workers/storage.d.ts.map +1 -0
  121. package/dist/client/js/storage/workers/storage.js +167 -0
  122. package/dist/client/js/storage/workers/toml-parser.d.ts +2 -0
  123. package/dist/client/js/storage/workers/toml-parser.d.ts.map +1 -0
  124. package/dist/client/js/storage/workers/toml-parser.js +75 -0
  125. package/dist/client/js/storage/workers/yaml-parser.d.ts +2 -0
  126. package/dist/client/js/storage/workers/yaml-parser.d.ts.map +1 -0
  127. package/dist/client/js/storage/workers/yaml-parser.js +100 -0
  128. package/dist/client/js/utils/file-types.d.ts +58 -0
  129. package/dist/client/js/utils/file-types.d.ts.map +1 -0
  130. package/{src/client/js/utils/file-types.ts → dist/client/js/utils/file-types.js} +75 -107
  131. package/dist/client/js/utils/format.d.ts +8 -0
  132. package/dist/client/js/utils/format.d.ts.map +1 -0
  133. package/{src/client/js/utils/format.ts → dist/client/js/utils/format.js} +5 -6
  134. package/dist/client/js/utils/frontmatter.d.ts +12 -0
  135. package/dist/client/js/utils/frontmatter.d.ts.map +1 -0
  136. package/dist/client/js/utils/frontmatter.js +29 -0
  137. package/dist/client/js/utils/schema-utils.d.ts +110 -0
  138. package/dist/client/js/utils/schema-utils.d.ts.map +1 -0
  139. package/dist/client/js/utils/schema-utils.js +242 -0
  140. package/dist/client/js/utils/slug.d.ts +8 -0
  141. package/dist/client/js/utils/slug.d.ts.map +1 -0
  142. package/{src/client/js/utils/slug.ts → dist/client/js/utils/slug.js} +6 -7
  143. package/dist/client/js/utils/sort.d.ts +41 -0
  144. package/dist/client/js/utils/sort.d.ts.map +1 -0
  145. package/dist/client/js/utils/sort.js +65 -0
  146. package/dist/client/js/utils/stable-stringify.d.ts +8 -0
  147. package/dist/client/js/utils/stable-stringify.d.ts.map +1 -0
  148. package/dist/client/js/utils/stable-stringify.js +23 -0
  149. package/dist/client/js/utils/url-utils.d.ts +11 -0
  150. package/dist/client/js/utils/url-utils.d.ts.map +1 -0
  151. package/{src/client/js/utils/url-utils.ts → dist/client/js/utils/url-utils.js} +22 -23
  152. package/dist/types.d.ts +22 -0
  153. package/dist/types.d.ts.map +1 -0
  154. package/dist/types.js +1 -0
  155. package/package.json +4 -1
  156. package/.github/workflows/ci.yml +0 -27
  157. package/.github/workflows/publish.yml +0 -34
  158. package/.mcp.json +0 -12
  159. package/.prettierignore +0 -5
  160. package/.prettierrc.cjs +0 -22
  161. package/AGENTS.md +0 -183
  162. package/playground/astro.config.mjs +0 -7
  163. package/playground/node_modules/.bin/astro +0 -21
  164. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +0 -85
  165. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +0 -7
  166. package/playground/node_modules/.vite/deps/_metadata.json +0 -184
  167. package/playground/node_modules/.vite/deps/astro___aria-query.js +0 -6776
  168. package/playground/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
  169. package/playground/node_modules/.vite/deps/astro___axobject-query.js +0 -3754
  170. package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
  171. package/playground/node_modules/.vite/deps/astro___html-escaper.js +0 -34
  172. package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
  173. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
  174. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +0 -7
  175. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +0 -8
  176. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +0 -7
  177. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +0 -21
  178. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +0 -7
  179. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +0 -223
  180. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +0 -7
  181. package/playground/node_modules/.vite/deps/chunk-FPEUJ7DG.js +0 -27
  182. package/playground/node_modules/.vite/deps/chunk-FPEUJ7DG.js.map +0 -7
  183. package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js +0 -1005
  184. package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js.map +0 -7
  185. package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js +0 -204
  186. package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js.map +0 -7
  187. package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js +0 -688
  188. package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js.map +0 -7
  189. package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js +0 -5099
  190. package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js.map +0 -7
  191. package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.js +0 -4376
  192. package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.js.map +0 -7
  193. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +0 -23
  194. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +0 -7
  195. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +0 -148
  196. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +0 -7
  197. package/playground/node_modules/.vite/deps/package.json +0 -3
  198. package/playground/node_modules/.vite/deps/smol-toml.js +0 -843
  199. package/playground/node_modules/.vite/deps/smol-toml.js.map +0 -7
  200. package/playground/node_modules/.vite/deps/svelte.js +0 -55
  201. package/playground/node_modules/.vite/deps/svelte.js.map +0 -7
  202. package/playground/node_modules/.vite/deps/svelte___clsx.js +0 -9
  203. package/playground/node_modules/.vite/deps/svelte___clsx.js.map +0 -7
  204. package/playground/node_modules/.vite/deps/svelte_animate.js +0 -57
  205. package/playground/node_modules/.vite/deps/svelte_animate.js.map +0 -7
  206. package/playground/node_modules/.vite/deps/svelte_attachments.js +0 -15
  207. package/playground/node_modules/.vite/deps/svelte_attachments.js.map +0 -7
  208. package/playground/node_modules/.vite/deps/svelte_easing.js +0 -67
  209. package/playground/node_modules/.vite/deps/svelte_easing.js.map +0 -7
  210. package/playground/node_modules/.vite/deps/svelte_events.js +0 -11
  211. package/playground/node_modules/.vite/deps/svelte_events.js.map +0 -7
  212. package/playground/node_modules/.vite/deps/svelte_internal.js +0 -5
  213. package/playground/node_modules/.vite/deps/svelte_internal.js.map +0 -7
  214. package/playground/node_modules/.vite/deps/svelte_internal_client.js +0 -402
  215. package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +0 -7
  216. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +0 -10
  217. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +0 -7
  218. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +0 -8
  219. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +0 -7
  220. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +0 -8
  221. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +0 -7
  222. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +0 -8
  223. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +0 -7
  224. package/playground/node_modules/.vite/deps/svelte_legacy.js +0 -35
  225. package/playground/node_modules/.vite/deps/svelte_legacy.js.map +0 -7
  226. package/playground/node_modules/.vite/deps/svelte_motion.js +0 -545
  227. package/playground/node_modules/.vite/deps/svelte_motion.js.map +0 -7
  228. package/playground/node_modules/.vite/deps/svelte_reactivity.js +0 -29
  229. package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +0 -7
  230. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +0 -127
  231. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +0 -7
  232. package/playground/node_modules/.vite/deps/svelte_store.js +0 -103
  233. package/playground/node_modules/.vite/deps/svelte_store.js.map +0 -7
  234. package/playground/node_modules/.vite/deps/svelte_transition.js +0 -208
  235. package/playground/node_modules/.vite/deps/svelte_transition.js.map +0 -7
  236. package/playground/package.json +0 -16
  237. package/playground/pnpm-lock.yaml +0 -3167
  238. package/playground/src/content/authors/jane-doe.json +0 -8
  239. package/playground/src/content/config/build.toml +0 -2
  240. package/playground/src/content/courses/web-fundamentals.json +0 -29
  241. package/playground/src/content/docs/advanced.mdx +0 -6
  242. package/playground/src/content/docs/intro.md +0 -6
  243. package/playground/src/content/guides/getting-started.mdx +0 -6
  244. package/playground/src/content/posts/hello-world.md +0 -7
  245. package/playground/src/content/products/t-shirt.json +0 -16
  246. package/playground/src/content/recipes/pancakes.mdoc +0 -8
  247. package/playground/src/content/settings/site.yml +0 -2
  248. package/playground/src/content.config.ts +0 -198
  249. package/playground/src/env.d.ts +0 -1
  250. package/playground/src/pages/index.astro +0 -11
  251. package/playground/src/pages/nebula.astro +0 -14
  252. package/pnpm-workspace.yaml +0 -2
  253. package/scripts/subset-icons.mjs +0 -178
  254. package/src/astro/index.ts +0 -295
  255. package/src/client/js/drafts/merge.svelte.ts +0 -121
  256. package/src/client/js/drafts/ops.svelte.ts +0 -227
  257. package/src/client/js/drafts/storage.ts +0 -108
  258. package/src/client/js/drafts/workers/diff.ts +0 -40
  259. package/src/client/js/editor/editor.svelte.ts +0 -343
  260. package/src/client/js/editor/languages.ts +0 -98
  261. package/src/client/js/editor/markdown-shortcuts.ts +0 -261
  262. package/src/client/js/handlers/admin.ts +0 -246
  263. package/src/client/js/state/dialogs.svelte.ts +0 -35
  264. package/src/client/js/state/router.svelte.ts +0 -156
  265. package/src/client/js/state/state.svelte.ts +0 -334
  266. package/src/client/js/storage/adapter.ts +0 -102
  267. package/src/client/js/storage/client.ts +0 -150
  268. package/src/client/js/storage/db.ts +0 -36
  269. package/src/client/js/storage/fsa.ts +0 -110
  270. package/src/client/js/storage/github.ts +0 -297
  271. package/src/client/js/storage/storage.ts +0 -83
  272. package/src/client/js/storage/workers/frontmatter.ts +0 -320
  273. package/src/client/js/storage/workers/storage.ts +0 -177
  274. package/src/client/js/storage/workers/toml-parser.ts +0 -106
  275. package/src/client/js/storage/workers/yaml-parser.ts +0 -132
  276. package/src/client/js/utils/frontmatter.ts +0 -38
  277. package/src/client/js/utils/schema-utils.ts +0 -295
  278. package/src/client/js/utils/sort.ts +0 -84
  279. package/src/client/js/utils/stable-stringify.ts +0 -27
  280. package/src/types.ts +0 -25
  281. package/svelte.config.js +0 -4
  282. package/tests/astro/build.test.ts +0 -63
  283. package/tests/astro/index.test.ts +0 -689
  284. package/tests/client/components/Admin.test.ts +0 -446
  285. package/tests/client/components/BackendPicker.test.ts +0 -239
  286. package/tests/client/components/DraftChip.test.ts +0 -53
  287. package/tests/client/components/MetadataForm.test.ts +0 -164
  288. package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +0 -91
  289. package/tests/client/components/dialogs/FilenameDialog.test.ts +0 -209
  290. package/tests/client/components/dialogs/dialog-stubs.ts +0 -19
  291. package/tests/client/components/editor/EditorPane.test.ts +0 -100
  292. package/tests/client/components/editor/EditorTabs.test.ts +0 -253
  293. package/tests/client/components/editor/EditorToolbar.test.ts +0 -252
  294. package/tests/client/components/editor/fixtures.ts +0 -31
  295. package/tests/client/components/fields/ArrayField.test.ts +0 -197
  296. package/tests/client/components/fields/BooleanField.test.ts +0 -206
  297. package/tests/client/components/fields/DateField.test.ts +0 -210
  298. package/tests/client/components/fields/EnumField.test.ts +0 -246
  299. package/tests/client/components/fields/NumberField.test.ts +0 -240
  300. package/tests/client/components/fields/ObjectField.test.ts +0 -157
  301. package/tests/client/components/fields/SchemaField.test.ts +0 -190
  302. package/tests/client/components/fields/StringField.test.ts +0 -223
  303. package/tests/client/components/sidebar/AdminSidebar.test.ts +0 -285
  304. package/tests/client/components/sidebar/AdminSidebarSort.test.ts +0 -135
  305. package/tests/client/components/sidebar/sort-mock.ts +0 -23
  306. package/tests/client/js/drafts/fixtures.ts +0 -22
  307. package/tests/client/js/drafts/merge.test.ts +0 -282
  308. package/tests/client/js/drafts/ops.test.ts +0 -658
  309. package/tests/client/js/drafts/storage.test.ts +0 -200
  310. package/tests/client/js/drafts/workers/diff.test.ts +0 -165
  311. package/tests/client/js/editor/editor.test.ts +0 -616
  312. package/tests/client/js/editor/link-wrap.test.ts +0 -225
  313. package/tests/client/js/editor/markdown-shortcuts.test.ts +0 -370
  314. package/tests/client/js/handlers/admin.test.ts +0 -467
  315. package/tests/client/js/state/router.test.ts +0 -619
  316. package/tests/client/js/state/schema.test.ts +0 -266
  317. package/tests/client/js/state/state.test.ts +0 -328
  318. package/tests/client/js/storage/adapter.test.ts +0 -115
  319. package/tests/client/js/storage/client.test.ts +0 -250
  320. package/tests/client/js/storage/db.test.ts +0 -59
  321. package/tests/client/js/storage/fsa.test.ts +0 -284
  322. package/tests/client/js/storage/github.test.ts +0 -349
  323. package/tests/client/js/storage/mock-port.ts +0 -95
  324. package/tests/client/js/storage/storage.test.ts +0 -77
  325. package/tests/client/js/storage/workers/frontmatter.test.ts +0 -479
  326. package/tests/client/js/storage/workers/storage.test.ts +0 -299
  327. package/tests/client/js/storage/workers/toml-parser.test.ts +0 -169
  328. package/tests/client/js/storage/workers/yaml-parser.test.ts +0 -168
  329. package/tests/client/js/utils/file-types.test.ts +0 -268
  330. package/tests/client/js/utils/frontmatter.test.ts +0 -87
  331. package/tests/client/js/utils/schema-utils.test.ts +0 -318
  332. package/tests/client/js/utils/slug.test.ts +0 -58
  333. package/tests/client/js/utils/sort.test.ts +0 -276
  334. package/tests/client/js/utils/stable-stringify.test.ts +0 -68
  335. package/tests/client/js/utils/url-utils.test.ts +0 -70
  336. package/tests/e2e/backend-connection.test.ts +0 -301
  337. package/tests/e2e/draft-lifecycle.test.ts +0 -388
  338. package/tests/e2e/editing.test.ts +0 -355
  339. package/tests/e2e/github-adapter.test.ts +0 -330
  340. package/tests/e2e/helpers/mock-adapter.ts +0 -166
  341. package/tests/e2e/helpers/test-app.ts +0 -155
  342. package/tests/e2e/navigation.test.ts +0 -358
  343. package/tests/e2e/publishing.test.ts +0 -345
  344. package/tests/e2e/unsaved-changes.test.ts +0 -317
  345. package/tests/setup.ts +0 -2
  346. package/tests/stubs/codemirror.ts +0 -197
  347. package/tsconfig.json +0 -19
  348. package/vitest.config.ts +0 -178
  349. /package/{src → dist}/client/Admin.svelte +0 -0
  350. /package/{src → dist}/client/components/BackendPicker.svelte +0 -0
  351. /package/{src → dist}/client/components/DraftChip.svelte +0 -0
  352. /package/{src → dist}/client/components/MetadataForm.svelte +0 -0
  353. /package/{src → dist}/client/components/ThemeToggle.svelte +0 -0
  354. /package/{src → dist}/client/components/dialogs/DeleteDraftDialog.svelte +0 -0
  355. /package/{src → dist}/client/components/dialogs/FilenameDialog.svelte +0 -0
  356. /package/{src → dist}/client/components/editor/EditorPane.svelte +0 -0
  357. /package/{src → dist}/client/components/editor/EditorTabs.svelte +0 -0
  358. /package/{src → dist}/client/components/editor/EditorToolbar.svelte +0 -0
  359. /package/{src → dist}/client/components/editor/FormatSelector.svelte +0 -0
  360. /package/{src → dist}/client/components/editor/Toolbar.svelte +0 -0
  361. /package/{src → dist}/client/components/fields/ArrayField.svelte +0 -0
  362. /package/{src → dist}/client/components/fields/ArrayItem.svelte +0 -0
  363. /package/{src → dist}/client/components/fields/BooleanField.svelte +0 -0
  364. /package/{src → dist}/client/components/fields/DateField.svelte +0 -0
  365. /package/{src → dist}/client/components/fields/EnumField.svelte +0 -0
  366. /package/{src → dist}/client/components/fields/FieldWrapper.svelte +0 -0
  367. /package/{src → dist}/client/components/fields/NumberField.svelte +0 -0
  368. /package/{src → dist}/client/components/fields/ObjectField.svelte +0 -0
  369. /package/{src → dist}/client/components/fields/SchemaField.svelte +0 -0
  370. /package/{src → dist}/client/components/fields/StringField.svelte +0 -0
  371. /package/{src → dist}/client/components/sidebar/AdminSidebar.svelte +0 -0
  372. /package/{src → dist}/client/components/sidebar/AdminSidebarSort.svelte +0 -0
  373. /package/{src → dist}/client/css/a11y.css +0 -0
  374. /package/{src → dist}/client/css/btn.css +0 -0
  375. /package/{src → dist}/client/css/dialog.css +0 -0
  376. /package/{src → dist}/client/css/field-input.css +0 -0
  377. /package/{src → dist}/client/css/reset.css +0 -0
  378. /package/{src → dist}/client/css/theme.css +0 -0
  379. /package/{src/client/index.ts → dist/client/index.js} +0 -0
  380. /package/{src → dist}/virtual.d.ts +0 -0
@@ -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
- }