nebula-cms 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) 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 +8 -1
  156. package/.claude/settings.local.json +0 -42
  157. package/.github/workflows/ci.yml +0 -31
  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/.claude/settings.local.json +0 -5
  163. package/playground/astro.config.mjs +0 -7
  164. package/playground/node_modules/.bin/astro +0 -21
  165. package/playground/node_modules/.bin/rollup +0 -21
  166. package/playground/node_modules/.bin/tsc +0 -21
  167. package/playground/node_modules/.bin/tsserver +0 -21
  168. package/playground/node_modules/.bin/vite +0 -21
  169. package/playground/node_modules/.vite/_svelte_metadata.json +0 -1
  170. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +0 -80
  171. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +0 -7
  172. package/playground/node_modules/.vite/deps/_metadata.json +0 -184
  173. package/playground/node_modules/.vite/deps/astro___aria-query.js +0 -6776
  174. package/playground/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
  175. package/playground/node_modules/.vite/deps/astro___axobject-query.js +0 -3754
  176. package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
  177. package/playground/node_modules/.vite/deps/astro___html-escaper.js +0 -34
  178. package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
  179. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
  180. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +0 -7
  181. package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js +0 -1005
  182. package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js.map +0 -7
  183. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +0 -8
  184. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +0 -7
  185. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +0 -21
  186. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +0 -7
  187. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +0 -223
  188. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +0 -7
  189. package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js +0 -204
  190. package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js.map +0 -7
  191. package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js +0 -27
  192. package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js.map +0 -7
  193. package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js +0 -4376
  194. package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js.map +0 -7
  195. package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js +0 -688
  196. package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js.map +0 -7
  197. package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js +0 -5099
  198. package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js.map +0 -7
  199. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +0 -23
  200. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +0 -7
  201. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +0 -148
  202. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +0 -7
  203. package/playground/node_modules/.vite/deps/package.json +0 -3
  204. package/playground/node_modules/.vite/deps/smol-toml.js +0 -843
  205. package/playground/node_modules/.vite/deps/smol-toml.js.map +0 -7
  206. package/playground/node_modules/.vite/deps/svelte.js +0 -55
  207. package/playground/node_modules/.vite/deps/svelte.js.map +0 -7
  208. package/playground/node_modules/.vite/deps/svelte___clsx.js +0 -9
  209. package/playground/node_modules/.vite/deps/svelte___clsx.js.map +0 -7
  210. package/playground/node_modules/.vite/deps/svelte_animate.js +0 -57
  211. package/playground/node_modules/.vite/deps/svelte_animate.js.map +0 -7
  212. package/playground/node_modules/.vite/deps/svelte_attachments.js +0 -15
  213. package/playground/node_modules/.vite/deps/svelte_attachments.js.map +0 -7
  214. package/playground/node_modules/.vite/deps/svelte_easing.js +0 -67
  215. package/playground/node_modules/.vite/deps/svelte_easing.js.map +0 -7
  216. package/playground/node_modules/.vite/deps/svelte_events.js +0 -11
  217. package/playground/node_modules/.vite/deps/svelte_events.js.map +0 -7
  218. package/playground/node_modules/.vite/deps/svelte_internal.js +0 -5
  219. package/playground/node_modules/.vite/deps/svelte_internal.js.map +0 -7
  220. package/playground/node_modules/.vite/deps/svelte_internal_client.js +0 -402
  221. package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +0 -7
  222. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +0 -10
  223. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +0 -7
  224. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +0 -8
  225. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +0 -7
  226. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +0 -8
  227. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +0 -7
  228. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +0 -8
  229. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +0 -7
  230. package/playground/node_modules/.vite/deps/svelte_legacy.js +0 -35
  231. package/playground/node_modules/.vite/deps/svelte_legacy.js.map +0 -7
  232. package/playground/node_modules/.vite/deps/svelte_motion.js +0 -545
  233. package/playground/node_modules/.vite/deps/svelte_motion.js.map +0 -7
  234. package/playground/node_modules/.vite/deps/svelte_reactivity.js +0 -29
  235. package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +0 -7
  236. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +0 -127
  237. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +0 -7
  238. package/playground/node_modules/.vite/deps/svelte_store.js +0 -103
  239. package/playground/node_modules/.vite/deps/svelte_store.js.map +0 -7
  240. package/playground/node_modules/.vite/deps/svelte_transition.js +0 -208
  241. package/playground/node_modules/.vite/deps/svelte_transition.js.map +0 -7
  242. package/playground/package.json +0 -16
  243. package/playground/pnpm-lock.yaml +0 -3167
  244. package/playground/src/content/authors/jane-doe.json +0 -8
  245. package/playground/src/content/config/build.toml +0 -2
  246. package/playground/src/content/courses/web-fundamentals.json +0 -29
  247. package/playground/src/content/docs/advanced.mdx +0 -6
  248. package/playground/src/content/docs/intro.md +0 -6
  249. package/playground/src/content/guides/getting-started.mdx +0 -6
  250. package/playground/src/content/posts/hello-world.md +0 -7
  251. package/playground/src/content/products/t-shirt.json +0 -16
  252. package/playground/src/content/recipes/pancakes.mdoc +0 -8
  253. package/playground/src/content/settings/site.yml +0 -2
  254. package/playground/src/content.config.ts +0 -198
  255. package/playground/src/env.d.ts +0 -1
  256. package/playground/src/pages/index.astro +0 -11
  257. package/playground/src/pages/nebula.astro +0 -14
  258. package/pnpm-workspace.yaml +0 -2
  259. package/scripts/subset-icons.mjs +0 -178
  260. package/src/astro/index.ts +0 -295
  261. package/src/client/js/drafts/merge.svelte.ts +0 -121
  262. package/src/client/js/drafts/ops.svelte.ts +0 -227
  263. package/src/client/js/drafts/storage.ts +0 -108
  264. package/src/client/js/drafts/workers/diff.ts +0 -40
  265. package/src/client/js/editor/editor.svelte.ts +0 -343
  266. package/src/client/js/editor/languages.ts +0 -98
  267. package/src/client/js/editor/markdown-shortcuts.ts +0 -261
  268. package/src/client/js/handlers/admin.ts +0 -246
  269. package/src/client/js/state/dialogs.svelte.ts +0 -35
  270. package/src/client/js/state/router.svelte.ts +0 -156
  271. package/src/client/js/state/state.svelte.ts +0 -334
  272. package/src/client/js/storage/adapter.ts +0 -102
  273. package/src/client/js/storage/client.ts +0 -150
  274. package/src/client/js/storage/db.ts +0 -36
  275. package/src/client/js/storage/fsa.ts +0 -110
  276. package/src/client/js/storage/github.ts +0 -297
  277. package/src/client/js/storage/storage.ts +0 -83
  278. package/src/client/js/storage/workers/frontmatter.ts +0 -320
  279. package/src/client/js/storage/workers/storage.ts +0 -177
  280. package/src/client/js/storage/workers/toml-parser.ts +0 -106
  281. package/src/client/js/storage/workers/yaml-parser.ts +0 -132
  282. package/src/client/js/utils/frontmatter.ts +0 -38
  283. package/src/client/js/utils/schema-utils.ts +0 -295
  284. package/src/client/js/utils/sort.ts +0 -84
  285. package/src/client/js/utils/stable-stringify.ts +0 -27
  286. package/src/types.ts +0 -25
  287. package/svelte.config.js +0 -4
  288. package/tests/astro/build.test.ts +0 -63
  289. package/tests/astro/index.test.ts +0 -689
  290. package/tests/client/components/Admin.test.ts +0 -446
  291. package/tests/client/components/BackendPicker.test.ts +0 -239
  292. package/tests/client/components/DraftChip.test.ts +0 -53
  293. package/tests/client/components/MetadataForm.test.ts +0 -164
  294. package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +0 -91
  295. package/tests/client/components/dialogs/FilenameDialog.test.ts +0 -209
  296. package/tests/client/components/dialogs/dialog-stubs.ts +0 -19
  297. package/tests/client/components/editor/EditorPane.test.ts +0 -100
  298. package/tests/client/components/editor/EditorTabs.test.ts +0 -253
  299. package/tests/client/components/editor/EditorToolbar.test.ts +0 -252
  300. package/tests/client/components/editor/fixtures.ts +0 -31
  301. package/tests/client/components/fields/ArrayField.test.ts +0 -197
  302. package/tests/client/components/fields/BooleanField.test.ts +0 -206
  303. package/tests/client/components/fields/DateField.test.ts +0 -210
  304. package/tests/client/components/fields/EnumField.test.ts +0 -246
  305. package/tests/client/components/fields/NumberField.test.ts +0 -240
  306. package/tests/client/components/fields/ObjectField.test.ts +0 -157
  307. package/tests/client/components/fields/SchemaField.test.ts +0 -190
  308. package/tests/client/components/fields/StringField.test.ts +0 -223
  309. package/tests/client/components/sidebar/AdminSidebar.test.ts +0 -285
  310. package/tests/client/components/sidebar/AdminSidebarSort.test.ts +0 -135
  311. package/tests/client/components/sidebar/sort-mock.ts +0 -23
  312. package/tests/client/js/drafts/fixtures.ts +0 -22
  313. package/tests/client/js/drafts/merge.test.ts +0 -282
  314. package/tests/client/js/drafts/ops.test.ts +0 -658
  315. package/tests/client/js/drafts/storage.test.ts +0 -200
  316. package/tests/client/js/drafts/workers/diff.test.ts +0 -165
  317. package/tests/client/js/editor/editor.test.ts +0 -616
  318. package/tests/client/js/editor/link-wrap.test.ts +0 -225
  319. package/tests/client/js/editor/markdown-shortcuts.test.ts +0 -370
  320. package/tests/client/js/handlers/admin.test.ts +0 -467
  321. package/tests/client/js/state/router.test.ts +0 -619
  322. package/tests/client/js/state/schema.test.ts +0 -266
  323. package/tests/client/js/state/state.test.ts +0 -328
  324. package/tests/client/js/storage/adapter.test.ts +0 -115
  325. package/tests/client/js/storage/client.test.ts +0 -250
  326. package/tests/client/js/storage/db.test.ts +0 -59
  327. package/tests/client/js/storage/fsa.test.ts +0 -284
  328. package/tests/client/js/storage/github.test.ts +0 -349
  329. package/tests/client/js/storage/mock-port.ts +0 -95
  330. package/tests/client/js/storage/storage.test.ts +0 -77
  331. package/tests/client/js/storage/workers/frontmatter.test.ts +0 -479
  332. package/tests/client/js/storage/workers/storage.test.ts +0 -299
  333. package/tests/client/js/storage/workers/toml-parser.test.ts +0 -169
  334. package/tests/client/js/storage/workers/yaml-parser.test.ts +0 -168
  335. package/tests/client/js/utils/file-types.test.ts +0 -268
  336. package/tests/client/js/utils/frontmatter.test.ts +0 -87
  337. package/tests/client/js/utils/schema-utils.test.ts +0 -318
  338. package/tests/client/js/utils/slug.test.ts +0 -58
  339. package/tests/client/js/utils/sort.test.ts +0 -276
  340. package/tests/client/js/utils/stable-stringify.test.ts +0 -68
  341. package/tests/client/js/utils/url-utils.test.ts +0 -70
  342. package/tests/e2e/backend-connection.test.ts +0 -301
  343. package/tests/e2e/draft-lifecycle.test.ts +0 -388
  344. package/tests/e2e/editing.test.ts +0 -355
  345. package/tests/e2e/github-adapter.test.ts +0 -330
  346. package/tests/e2e/helpers/mock-adapter.ts +0 -166
  347. package/tests/e2e/helpers/test-app.ts +0 -155
  348. package/tests/e2e/navigation.test.ts +0 -358
  349. package/tests/e2e/publishing.test.ts +0 -345
  350. package/tests/e2e/unsaved-changes.test.ts +0 -317
  351. package/tests/setup.ts +0 -2
  352. package/tests/stubs/codemirror.ts +0 -197
  353. package/tsconfig.json +0 -19
  354. package/vitest.config.ts +0 -178
  355. /package/{src → dist}/client/Admin.svelte +0 -0
  356. /package/{src → dist}/client/components/BackendPicker.svelte +0 -0
  357. /package/{src → dist}/client/components/DraftChip.svelte +0 -0
  358. /package/{src → dist}/client/components/MetadataForm.svelte +0 -0
  359. /package/{src → dist}/client/components/ThemeToggle.svelte +0 -0
  360. /package/{src → dist}/client/components/dialogs/DeleteDraftDialog.svelte +0 -0
  361. /package/{src → dist}/client/components/dialogs/FilenameDialog.svelte +0 -0
  362. /package/{src → dist}/client/components/editor/EditorPane.svelte +0 -0
  363. /package/{src → dist}/client/components/editor/EditorTabs.svelte +0 -0
  364. /package/{src → dist}/client/components/editor/EditorToolbar.svelte +0 -0
  365. /package/{src → dist}/client/components/editor/FormatSelector.svelte +0 -0
  366. /package/{src → dist}/client/components/editor/Toolbar.svelte +0 -0
  367. /package/{src → dist}/client/components/fields/ArrayField.svelte +0 -0
  368. /package/{src → dist}/client/components/fields/ArrayItem.svelte +0 -0
  369. /package/{src → dist}/client/components/fields/BooleanField.svelte +0 -0
  370. /package/{src → dist}/client/components/fields/DateField.svelte +0 -0
  371. /package/{src → dist}/client/components/fields/EnumField.svelte +0 -0
  372. /package/{src → dist}/client/components/fields/FieldWrapper.svelte +0 -0
  373. /package/{src → dist}/client/components/fields/NumberField.svelte +0 -0
  374. /package/{src → dist}/client/components/fields/ObjectField.svelte +0 -0
  375. /package/{src → dist}/client/components/fields/SchemaField.svelte +0 -0
  376. /package/{src → dist}/client/components/fields/StringField.svelte +0 -0
  377. /package/{src → dist}/client/components/sidebar/AdminSidebar.svelte +0 -0
  378. /package/{src → dist}/client/components/sidebar/AdminSidebarSort.svelte +0 -0
  379. /package/{src → dist}/client/css/a11y.css +0 -0
  380. /package/{src → dist}/client/css/btn.css +0 -0
  381. /package/{src → dist}/client/css/dialog.css +0 -0
  382. /package/{src → dist}/client/css/field-input.css +0 -0
  383. /package/{src → dist}/client/css/reset.css +0 -0
  384. /package/{src → dist}/client/css/theme.css +0 -0
  385. /package/{src/client/index.ts → dist/client/index.js} +0 -0
  386. /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
- }