create-starlight-cms 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/THIRD_PARTY_NOTICES.md +4 -0
  4. package/bin/index.js +147 -0
  5. package/package.json +46 -0
  6. package/template/.nvmrc +1 -0
  7. package/template/LICENSE +21 -0
  8. package/template/README.md +68 -0
  9. package/template/THIRD_PARTY_NOTICES.md +12 -0
  10. package/template/_gitignore +14 -0
  11. package/template/astro.config.mjs +26 -0
  12. package/template/migrations/0001_schema.sql +73 -0
  13. package/template/migrations/0002_publish_delivery.sql +16 -0
  14. package/template/package-lock.json.template +9123 -0
  15. package/template/package.json +89 -0
  16. package/template/scripts/build-admin.mjs +38 -0
  17. package/template/scripts/check-linux-bindings.mjs +18 -0
  18. package/template/scripts/dev.mjs +94 -0
  19. package/template/src/admin/admin.scss +182 -0
  20. package/template/src/admin/app.ts +130 -0
  21. package/template/src/admin/client.tsx +684 -0
  22. package/template/src/admin/components/tiptap-extension/node-background-extension.ts +150 -0
  23. package/template/src/admin/components/tiptap-icons/align-center-icon.tsx +38 -0
  24. package/template/src/admin/components/tiptap-icons/align-justify-icon.tsx +38 -0
  25. package/template/src/admin/components/tiptap-icons/align-left-icon.tsx +38 -0
  26. package/template/src/admin/components/tiptap-icons/align-right-icon.tsx +38 -0
  27. package/template/src/admin/components/tiptap-icons/arrow-left-icon.tsx +24 -0
  28. package/template/src/admin/components/tiptap-icons/arrow-right-icon.tsx +26 -0
  29. package/template/src/admin/components/tiptap-icons/ban-icon.tsx +26 -0
  30. package/template/src/admin/components/tiptap-icons/blockquote-icon.tsx +44 -0
  31. package/template/src/admin/components/tiptap-icons/bold-icon.tsx +26 -0
  32. package/template/src/admin/components/tiptap-icons/case-sensitive-icon.tsx +32 -0
  33. package/template/src/admin/components/tiptap-icons/check-icon.tsx +26 -0
  34. package/template/src/admin/components/tiptap-icons/chevron-down-icon.tsx +26 -0
  35. package/template/src/admin/components/tiptap-icons/chevron-up-icon.tsx +22 -0
  36. package/template/src/admin/components/tiptap-icons/close-icon.tsx +24 -0
  37. package/template/src/admin/components/tiptap-icons/code-block-icon.tsx +38 -0
  38. package/template/src/admin/components/tiptap-icons/code2-icon.tsx +32 -0
  39. package/template/src/admin/components/tiptap-icons/corner-down-left-icon.tsx +26 -0
  40. package/template/src/admin/components/tiptap-icons/external-link-icon.tsx +28 -0
  41. package/template/src/admin/components/tiptap-icons/heading-five-icon.tsx +28 -0
  42. package/template/src/admin/components/tiptap-icons/heading-four-icon.tsx +28 -0
  43. package/template/src/admin/components/tiptap-icons/heading-icon.tsx +24 -0
  44. package/template/src/admin/components/tiptap-icons/heading-one-icon.tsx +28 -0
  45. package/template/src/admin/components/tiptap-icons/heading-six-icon.tsx +30 -0
  46. package/template/src/admin/components/tiptap-icons/heading-three-icon.tsx +36 -0
  47. package/template/src/admin/components/tiptap-icons/heading-two-icon.tsx +28 -0
  48. package/template/src/admin/components/tiptap-icons/highlighter-icon.tsx +26 -0
  49. package/template/src/admin/components/tiptap-icons/image-plus-icon.tsx +26 -0
  50. package/template/src/admin/components/tiptap-icons/italic-icon.tsx +24 -0
  51. package/template/src/admin/components/tiptap-icons/link-icon.tsx +28 -0
  52. package/template/src/admin/components/tiptap-icons/list-icon.tsx +56 -0
  53. package/template/src/admin/components/tiptap-icons/list-ordered-icon.tsx +56 -0
  54. package/template/src/admin/components/tiptap-icons/list-todo-icon.tsx +50 -0
  55. package/template/src/admin/components/tiptap-icons/moon-star-icon.tsx +30 -0
  56. package/template/src/admin/components/tiptap-icons/redo2-icon.tsx +26 -0
  57. package/template/src/admin/components/tiptap-icons/search-icon.tsx +26 -0
  58. package/template/src/admin/components/tiptap-icons/strike-icon.tsx +28 -0
  59. package/template/src/admin/components/tiptap-icons/subscript-icon.tsx +38 -0
  60. package/template/src/admin/components/tiptap-icons/sun-icon.tsx +58 -0
  61. package/template/src/admin/components/tiptap-icons/superscript-icon.tsx +38 -0
  62. package/template/src/admin/components/tiptap-icons/trash-icon.tsx +26 -0
  63. package/template/src/admin/components/tiptap-icons/underline-icon.tsx +26 -0
  64. package/template/src/admin/components/tiptap-icons/undo2-icon.tsx +26 -0
  65. package/template/src/admin/components/tiptap-icons/whole-word-icon.tsx +36 -0
  66. package/template/src/admin/components/tiptap-node/blockquote-node/blockquote-node.scss +37 -0
  67. package/template/src/admin/components/tiptap-node/code-block-node/code-block-node.scss +54 -0
  68. package/template/src/admin/components/tiptap-node/heading-node/heading-node.scss +45 -0
  69. package/template/src/admin/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +14 -0
  70. package/template/src/admin/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss +25 -0
  71. package/template/src/admin/components/tiptap-node/image-node/image-node.scss +35 -0
  72. package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node-extension.ts +161 -0
  73. package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node.scss +249 -0
  74. package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node.tsx +554 -0
  75. package/template/src/admin/components/tiptap-node/image-upload-node/index.tsx +1 -0
  76. package/template/src/admin/components/tiptap-node/list-node/list-node.scss +208 -0
  77. package/template/src/admin/components/tiptap-node/paragraph-node/paragraph-node.scss +217 -0
  78. package/template/src/admin/components/tiptap-templates/simple/NOTICE.md +14 -0
  79. package/template/src/admin/components/tiptap-templates/simple/data/content.json +477 -0
  80. package/template/src/admin/components/tiptap-templates/simple/simple-editor.scss +97 -0
  81. package/template/src/admin/components/tiptap-templates/simple/simple-editor.tsx +348 -0
  82. package/template/src/admin/components/tiptap-templates/simple/theme-toggle.tsx +44 -0
  83. package/template/src/admin/components/tiptap-ui/blockquote-button/blockquote-button.tsx +122 -0
  84. package/template/src/admin/components/tiptap-ui/blockquote-button/index.tsx +2 -0
  85. package/template/src/admin/components/tiptap-ui/blockquote-button/use-blockquote.ts +273 -0
  86. package/template/src/admin/components/tiptap-ui/code-block-button/code-block-button.tsx +122 -0
  87. package/template/src/admin/components/tiptap-ui/code-block-button/index.tsx +2 -0
  88. package/template/src/admin/components/tiptap-ui/code-block-button/use-code-block.ts +283 -0
  89. package/template/src/admin/components/tiptap-ui/color-highlight-button/color-highlight-button.scss +49 -0
  90. package/template/src/admin/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx +170 -0
  91. package/template/src/admin/components/tiptap-ui/color-highlight-button/index.tsx +2 -0
  92. package/template/src/admin/components/tiptap-ui/color-highlight-button/use-color-highlight.ts +380 -0
  93. package/template/src/admin/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +231 -0
  94. package/template/src/admin/components/tiptap-ui/color-highlight-popover/index.tsx +1 -0
  95. package/template/src/admin/components/tiptap-ui/heading-button/heading-button.tsx +124 -0
  96. package/template/src/admin/components/tiptap-ui/heading-button/index.tsx +2 -0
  97. package/template/src/admin/components/tiptap-ui/heading-button/use-heading.ts +348 -0
  98. package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +128 -0
  99. package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/index.tsx +2 -0
  100. package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts +132 -0
  101. package/template/src/admin/components/tiptap-ui/image-upload-button/image-upload-button.tsx +130 -0
  102. package/template/src/admin/components/tiptap-ui/image-upload-button/index.tsx +2 -0
  103. package/template/src/admin/components/tiptap-ui/image-upload-button/use-image-upload.ts +197 -0
  104. package/template/src/admin/components/tiptap-ui/link-popover/index.tsx +2 -0
  105. package/template/src/admin/components/tiptap-ui/link-popover/link-popover.scss +16 -0
  106. package/template/src/admin/components/tiptap-ui/link-popover/link-popover.tsx +315 -0
  107. package/template/src/admin/components/tiptap-ui/link-popover/use-link-popover.ts +297 -0
  108. package/template/src/admin/components/tiptap-ui/list-button/index.tsx +2 -0
  109. package/template/src/admin/components/tiptap-ui/list-button/list-button.tsx +120 -0
  110. package/template/src/admin/components/tiptap-ui/list-button/use-list.ts +353 -0
  111. package/template/src/admin/components/tiptap-ui/list-dropdown-menu/index.tsx +2 -0
  112. package/template/src/admin/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +126 -0
  113. package/template/src/admin/components/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts +220 -0
  114. package/template/src/admin/components/tiptap-ui/mark-button/index.tsx +2 -0
  115. package/template/src/admin/components/tiptap-ui/mark-button/mark-button.tsx +122 -0
  116. package/template/src/admin/components/tiptap-ui/mark-button/use-mark.ts +219 -0
  117. package/template/src/admin/components/tiptap-ui/search-and-replace/index.tsx +2 -0
  118. package/template/src/admin/components/tiptap-ui/search-and-replace/search-and-replace.scss +300 -0
  119. package/template/src/admin/components/tiptap-ui/search-and-replace/search-and-replace.tsx +718 -0
  120. package/template/src/admin/components/tiptap-ui/search-and-replace/use-search-and-replace.ts +528 -0
  121. package/template/src/admin/components/tiptap-ui/text-align-button/index.tsx +2 -0
  122. package/template/src/admin/components/tiptap-ui/text-align-button/text-align-button.tsx +144 -0
  123. package/template/src/admin/components/tiptap-ui/text-align-button/use-text-align.ts +237 -0
  124. package/template/src/admin/components/tiptap-ui/undo-redo-button/index.tsx +2 -0
  125. package/template/src/admin/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx +125 -0
  126. package/template/src/admin/components/tiptap-ui/undo-redo-button/use-undo-redo.ts +200 -0
  127. package/template/src/admin/components/tiptap-ui-primitive/badge/badge-colors.scss +395 -0
  128. package/template/src/admin/components/tiptap-ui-primitive/badge/badge-group.scss +16 -0
  129. package/template/src/admin/components/tiptap-ui-primitive/badge/badge.scss +99 -0
  130. package/template/src/admin/components/tiptap-ui-primitive/badge/badge.tsx +44 -0
  131. package/template/src/admin/components/tiptap-ui-primitive/badge/index.tsx +1 -0
  132. package/template/src/admin/components/tiptap-ui-primitive/button/button-colors.scss +613 -0
  133. package/template/src/admin/components/tiptap-ui-primitive/button/button.scss +390 -0
  134. package/template/src/admin/components/tiptap-ui-primitive/button/button.tsx +158 -0
  135. package/template/src/admin/components/tiptap-ui-primitive/button/index.tsx +1 -0
  136. package/template/src/admin/components/tiptap-ui-primitive/button-group/button-group.scss +89 -0
  137. package/template/src/admin/components/tiptap-ui-primitive/button-group/button-group.tsx +72 -0
  138. package/template/src/admin/components/tiptap-ui-primitive/button-group/index.tsx +1 -0
  139. package/template/src/admin/components/tiptap-ui-primitive/card/card.scss +74 -0
  140. package/template/src/admin/components/tiptap-ui-primitive/card/card.tsx +79 -0
  141. package/template/src/admin/components/tiptap-ui-primitive/card/index.tsx +1 -0
  142. package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss +152 -0
  143. package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +262 -0
  144. package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
  145. package/template/src/admin/components/tiptap-ui-primitive/input/index.tsx +1 -0
  146. package/template/src/admin/components/tiptap-ui-primitive/input/input.scss +41 -0
  147. package/template/src/admin/components/tiptap-ui-primitive/input/input.tsx +22 -0
  148. package/template/src/admin/components/tiptap-ui-primitive/input-group/index.tsx +1 -0
  149. package/template/src/admin/components/tiptap-ui-primitive/input-group/input-group.scss +225 -0
  150. package/template/src/admin/components/tiptap-ui-primitive/input-group/input-group.tsx +135 -0
  151. package/template/src/admin/components/tiptap-ui-primitive/popover/index.tsx +1 -0
  152. package/template/src/admin/components/tiptap-ui-primitive/popover/popover.scss +64 -0
  153. package/template/src/admin/components/tiptap-ui-primitive/popover/popover.tsx +35 -0
  154. package/template/src/admin/components/tiptap-ui-primitive/separator/index.tsx +1 -0
  155. package/template/src/admin/components/tiptap-ui-primitive/separator/separator.scss +23 -0
  156. package/template/src/admin/components/tiptap-ui-primitive/separator/separator.tsx +28 -0
  157. package/template/src/admin/components/tiptap-ui-primitive/spacer/index.tsx +1 -0
  158. package/template/src/admin/components/tiptap-ui-primitive/spacer/spacer.tsx +24 -0
  159. package/template/src/admin/components/tiptap-ui-primitive/switch/index.tsx +1 -0
  160. package/template/src/admin/components/tiptap-ui-primitive/switch/switch.scss +86 -0
  161. package/template/src/admin/components/tiptap-ui-primitive/switch/switch.tsx +29 -0
  162. package/template/src/admin/components/tiptap-ui-primitive/textarea/index.tsx +1 -0
  163. package/template/src/admin/components/tiptap-ui-primitive/textarea/textarea.scss +42 -0
  164. package/template/src/admin/components/tiptap-ui-primitive/textarea/textarea.tsx +16 -0
  165. package/template/src/admin/components/tiptap-ui-primitive/toolbar/index.tsx +1 -0
  166. package/template/src/admin/components/tiptap-ui-primitive/toolbar/toolbar.scss +98 -0
  167. package/template/src/admin/components/tiptap-ui-primitive/toolbar/toolbar.tsx +203 -0
  168. package/template/src/admin/components/tiptap-ui-primitive/tooltip/index.tsx +1 -0
  169. package/template/src/admin/components/tiptap-ui-primitive/tooltip/tooltip.scss +43 -0
  170. package/template/src/admin/components/tiptap-ui-primitive/tooltip/tooltip.tsx +241 -0
  171. package/template/src/admin/hooks/use-composed-ref.ts +46 -0
  172. package/template/src/admin/hooks/use-cursor-visibility.ts +69 -0
  173. package/template/src/admin/hooks/use-element-rect.ts +166 -0
  174. package/template/src/admin/hooks/use-is-breakpoint.ts +35 -0
  175. package/template/src/admin/hooks/use-menu-navigation.ts +204 -0
  176. package/template/src/admin/hooks/use-scrolling.ts +75 -0
  177. package/template/src/admin/hooks/use-throttled-callback.ts +48 -0
  178. package/template/src/admin/hooks/use-tiptap-editor.ts +69 -0
  179. package/template/src/admin/hooks/use-unmount.ts +21 -0
  180. package/template/src/admin/hooks/use-window-size.ts +93 -0
  181. package/template/src/admin/html.ts +17 -0
  182. package/template/src/admin/lib/tiptap-utils.ts +652 -0
  183. package/template/src/admin/navigation-tree.tsx +82 -0
  184. package/template/src/admin/preview.ts +73 -0
  185. package/template/src/admin/styles/_cms-theme.scss +58 -0
  186. package/template/src/admin/styles/_keyframe-animations.scss +91 -0
  187. package/template/src/admin/styles/_variables.scss +298 -0
  188. package/template/src/assets/favicon.svg +1 -0
  189. package/template/src/assets/logo.svg +1 -0
  190. package/template/src/astro-env.d.ts +1 -0
  191. package/template/src/content.config.ts +12 -0
  192. package/template/src/db/bootstrap.ts +36 -0
  193. package/template/src/db/client.ts +7 -0
  194. package/template/src/db/schema.ts +45 -0
  195. package/template/src/documents/service.ts +178 -0
  196. package/template/src/documents/validation.ts +9 -0
  197. package/template/src/env.ts +9 -0
  198. package/template/src/index.ts +10 -0
  199. package/template/src/locales.ts +10 -0
  200. package/template/src/media/service.ts +120 -0
  201. package/template/src/navigation/service.ts +171 -0
  202. package/template/src/pages/[locale]/cms-preview-shell.astro +12 -0
  203. package/template/src/pages/cms-preview-shell.astro +5 -0
  204. package/template/src/publish/deploy-hook.ts +42 -0
  205. package/template/src/publish/record.ts +20 -0
  206. package/template/src/publish/service.ts +135 -0
  207. package/template/src/scss.d.ts +5 -0
  208. package/template/src/site.config.ts +7 -0
  209. package/template/src/sql.d.ts +4 -0
  210. package/template/src/starlight/cms-origin.ts +4 -0
  211. package/template/src/starlight/loader.ts +21 -0
  212. package/template/src/starlight/preview-render.ts +162 -0
  213. package/template/src/starlight/preview-shell.astro +14 -0
  214. package/template/src/starlight/render.ts +59 -0
  215. package/template/src/starlight/schema.ts +34 -0
  216. package/template/src/starlight/sidebar.ts +35 -0
  217. package/template/src/starlight/snapshot.ts +47 -0
  218. package/template/src/starlight/source.ts +26 -0
  219. package/template/src/starlight/tiptap.ts +29 -0
  220. package/template/src/styles/starlight.css +12 -0
  221. package/template/test-files/content.test.template.ts +82 -0
  222. package/template/test-files/deploy-hook.test.template.ts +32 -0
  223. package/template/test-files/media.test.template.ts +39 -0
  224. package/template/test-files/publishing.integration.test.template.ts +156 -0
  225. package/template/tsconfig.admin.json +18 -0
  226. package/template/tsconfig.json +23 -0
  227. package/template/worker-configuration.d.ts +15389 -0
  228. package/template/wrangler.jsonc +26 -0
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" zoomAndPan="magnify" viewBox="0 0 224.87999 56.249996" height="75" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><g/></defs><path fill="#8b5cf6" d="M 21.675781 41.367188 L 21.675781 13.332031 C 21.675781 9.726562 24.589844 7.226562 28.578125 7.226562 L 45.523438 7.289062 L 53.867188 15.785156 L 53.871094 38.3125 Z M 21.675781 41.367188 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 25.144531 41.367188 L 25.144531 14.167969 C 25.144531 12.085938 26.773438 10.695312 28.855469 10.695312 L 43.070312 10.695312 C 43.070312 14.570312 46.226562 17.695312 50.402344 17.695312 L 50.402344 38.683594 Z M 25.144531 41.367188 " fill-opacity="1" fill-rule="nonzero"/><path fill="#8b5cf6" d="M 45.523438 7.289062 L 53.867188 15.785156 L 50.402344 17.695312 C 46.226562 17.695312 43.070312 14.570312 43.070312 10.695312 Z M 45.523438 7.289062 " fill-opacity="1" fill-rule="nonzero"/><path fill="#8b5cf6" d="M 30.988281 19.191406 L 39.3125 19.191406 C 39.535156 19.191406 39.746094 19.226562 39.953125 19.300781 C 40.15625 19.375 40.335938 19.480469 40.492188 19.617188 C 40.648438 19.753906 40.769531 19.914062 40.851562 20.089844 C 40.9375 20.269531 40.980469 20.457031 40.980469 20.648438 C 40.980469 20.84375 40.9375 21.027344 40.851562 21.207031 C 40.769531 21.382812 40.648438 21.542969 40.492188 21.679688 C 40.335938 21.816406 40.15625 21.921875 39.953125 21.996094 C 39.746094 22.070312 39.535156 22.105469 39.3125 22.105469 L 30.988281 22.105469 C 30.765625 22.105469 30.554688 22.070312 30.351562 21.996094 C 30.144531 21.921875 29.964844 21.816406 29.808594 21.679688 C 29.652344 21.542969 29.535156 21.382812 29.449219 21.207031 C 29.363281 21.027344 29.324219 20.84375 29.324219 20.648438 C 29.324219 20.457031 29.363281 20.269531 29.449219 20.089844 C 29.535156 19.914062 29.652344 19.753906 29.808594 19.617188 C 29.964844 19.480469 30.144531 19.375 30.351562 19.300781 C 30.554688 19.226562 30.765625 19.191406 30.988281 19.191406 Z M 30.988281 19.191406 " fill-opacity="1" fill-rule="nonzero"/><path fill="#8b5cf6" d="M 30.988281 24.046875 L 45.421875 24.046875 C 45.640625 24.046875 45.851562 24.085938 46.058594 24.160156 C 46.261719 24.234375 46.441406 24.339844 46.597656 24.476562 C 46.753906 24.613281 46.875 24.769531 46.957031 24.949219 C 47.042969 25.125 47.085938 25.3125 47.085938 25.503906 C 47.085938 25.699219 47.042969 25.886719 46.957031 26.0625 C 46.875 26.242188 46.753906 26.398438 46.597656 26.535156 C 46.441406 26.671875 46.261719 26.777344 46.058594 26.851562 C 45.851562 26.925781 45.640625 26.960938 45.421875 26.960938 L 30.988281 26.960938 C 30.765625 26.960938 30.554688 26.925781 30.351562 26.851562 C 30.144531 26.777344 29.964844 26.671875 29.808594 26.535156 C 29.652344 26.398438 29.535156 26.242188 29.449219 26.0625 C 29.363281 25.886719 29.324219 25.699219 29.324219 25.503906 C 29.324219 25.3125 29.363281 25.125 29.449219 24.949219 C 29.535156 24.769531 29.652344 24.613281 29.808594 24.476562 C 29.964844 24.339844 30.144531 24.234375 30.351562 24.160156 C 30.554688 24.085938 30.765625 24.046875 30.988281 24.046875 Z M 30.988281 24.046875 " fill-opacity="1" fill-rule="nonzero"/><path fill="#8b5cf6" d="M 30.988281 28.699219 L 38.203125 28.699219 C 38.425781 28.699219 38.636719 28.734375 38.839844 28.808594 C 39.046875 28.882812 39.226562 28.988281 39.382812 29.125 C 39.539062 29.261719 39.65625 29.417969 39.742188 29.597656 C 39.828125 29.777344 39.871094 29.960938 39.871094 30.15625 C 39.871094 30.347656 39.828125 30.535156 39.742188 30.710938 C 39.65625 30.890625 39.539062 31.046875 39.382812 31.183594 C 39.226562 31.320312 39.046875 31.425781 38.839844 31.5 C 38.636719 31.574219 38.425781 31.613281 38.203125 31.613281 L 30.988281 31.613281 C 30.765625 31.613281 30.554688 31.574219 30.351562 31.5 C 30.144531 31.425781 29.964844 31.320312 29.808594 31.183594 C 29.652344 31.046875 29.535156 30.890625 29.449219 30.710938 C 29.363281 30.535156 29.324219 30.347656 29.324219 30.15625 C 29.324219 29.960938 29.363281 29.777344 29.449219 29.597656 C 29.535156 29.417969 29.652344 29.261719 29.808594 29.125 C 29.964844 28.988281 30.144531 28.882812 30.351562 28.808594 C 30.554688 28.734375 30.765625 28.699219 30.988281 28.699219 Z M 30.988281 28.699219 " fill-opacity="1" fill-rule="nonzero"/><path fill="#8b5cf6" d="M 19.382812 51.664062 C 15.914062 51.664062 13.554688 48.957031 13.554688 44.65625 C 13.554688 39.3125 17.230469 35.011719 22.503906 33.484375 C 27.570312 32.097656 31.871094 34.039062 35.0625 36.816406 C 38.117188 30.15625 43.460938 28.976562 49.289062 28.976562 C 56.15625 28.976562 61.222656 33.902344 61.640625 38.757812 C 65.59375 39.382812 67.257812 42.714844 67.257812 44.933594 C 67.257812 49.097656 64.136719 51.664062 59.625 51.664062 Z M 19.382812 51.664062 " fill-opacity="1" fill-rule="nonzero"/><path fill="#8b5cf6" d="M 57.980469 4.066406 C 58.949219 8.296875 59.992188 9.683594 64.433594 10.585938 C 59.921875 11.488281 58.949219 12.808594 57.980469 17.039062 C 56.9375 12.808594 55.96875 11.628906 51.457031 10.585938 C 55.96875 9.617188 56.9375 8.296875 57.980469 4.066406 Z M 57.980469 4.066406 " fill-opacity="1" fill-rule="nonzero"/><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(70.589457, 21.93013)"><g><path d="M 7.546875 0.328125 C 6.234375 0.328125 5.0625 0.0195312 4.03125 -0.59375 C 3 -1.21875 2.191406 -2.097656 1.609375 -3.234375 C 1.035156 -4.378906 0.75 -5.71875 0.75 -7.25 C 0.75 -8.800781 1.035156 -10.144531 1.609375 -11.28125 C 2.191406 -12.414062 3 -13.296875 4.03125 -13.921875 C 5.0625 -14.554688 6.234375 -14.875 7.546875 -14.875 C 8.523438 -14.875 9.425781 -14.691406 10.25 -14.328125 C 11.070312 -13.972656 11.785156 -13.472656 12.390625 -12.828125 C 12.660156 -12.523438 12.75 -12.21875 12.65625 -11.90625 C 12.5625 -11.59375 12.375 -11.351562 12.09375 -11.1875 L 12 -11.125 C 11.820312 -11.007812 11.566406 -10.96875 11.234375 -11 C 10.898438 -11.03125 10.59375 -11.175781 10.3125 -11.4375 C 9.957031 -11.789062 9.546875 -12.0625 9.078125 -12.25 C 8.617188 -12.4375 8.109375 -12.53125 7.546875 -12.53125 C 6.679688 -12.53125 5.925781 -12.3125 5.28125 -11.875 C 4.632812 -11.445312 4.132812 -10.832031 3.78125 -10.03125 C 3.425781 -9.238281 3.25 -8.3125 3.25 -7.25 C 3.25 -6.195312 3.425781 -5.273438 3.78125 -4.484375 C 4.132812 -3.691406 4.632812 -3.078125 5.28125 -2.640625 C 5.925781 -2.203125 6.679688 -1.984375 7.546875 -1.984375 C 8.628906 -1.984375 9.550781 -2.351562 10.3125 -3.09375 C 10.59375 -3.351562 10.878906 -3.503906 11.171875 -3.546875 C 11.460938 -3.585938 11.738281 -3.546875 12 -3.421875 L 12.03125 -3.375 C 12.320312 -3.238281 12.523438 -3.007812 12.640625 -2.6875 C 12.765625 -2.363281 12.695312 -2.046875 12.4375 -1.734375 C 11.820312 -1.078125 11.097656 -0.566406 10.265625 -0.203125 C 9.441406 0.148438 8.535156 0.328125 7.546875 0.328125 Z M 7.546875 0.328125 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(84.515835, 21.93013)"><g><path d="M 2.4375 0 C 1.65625 0 1.265625 -0.40625 1.265625 -1.21875 L 1.265625 -13.546875 C 1.265625 -14.359375 1.65625 -14.765625 2.4375 -14.765625 C 3.21875 -14.765625 3.609375 -14.359375 3.609375 -13.546875 L 3.609375 -1.21875 C 3.609375 -0.40625 3.21875 0 2.4375 0 Z M 2.4375 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(89.385924, 21.93013)"><g><path d="M 5.546875 0.234375 C 4.640625 0.234375 3.820312 0.015625 3.09375 -0.421875 C 2.375 -0.859375 1.800781 -1.46875 1.375 -2.25 C 0.957031 -3.03125 0.75 -3.945312 0.75 -5 C 0.75 -6.039062 0.957031 -6.953125 1.375 -7.734375 C 1.800781 -8.515625 2.375 -9.125 3.09375 -9.5625 C 3.820312 -10 4.640625 -10.21875 5.546875 -10.21875 C 6.492188 -10.21875 7.328125 -10 8.046875 -9.5625 C 8.773438 -9.125 9.34375 -8.515625 9.75 -7.734375 C 10.164062 -6.953125 10.375 -6.039062 10.375 -5 C 10.375 -3.945312 10.164062 -3.03125 9.75 -2.25 C 9.34375 -1.46875 8.773438 -0.859375 8.046875 -0.421875 C 7.328125 0.015625 6.492188 0.234375 5.546875 0.234375 Z M 5.546875 -1.890625 C 6.285156 -1.890625 6.882812 -2.15625 7.34375 -2.6875 C 7.800781 -3.226562 8.03125 -4 8.03125 -5 C 8.03125 -5.988281 7.800781 -6.75 7.34375 -7.28125 C 6.882812 -7.820312 6.285156 -8.09375 5.546875 -8.09375 C 4.828125 -8.09375 4.238281 -7.820312 3.78125 -7.28125 C 3.332031 -6.75 3.109375 -5.988281 3.109375 -5 C 3.109375 -4 3.332031 -3.226562 3.78125 -2.6875 C 4.238281 -2.15625 4.828125 -1.890625 5.546875 -1.890625 Z M 5.546875 -1.890625 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(100.514583, 21.93013)"><g><path d="M 4.625 0.234375 C 3.582031 0.234375 2.734375 -0.0976562 2.078125 -0.765625 C 1.421875 -1.429688 1.09375 -2.410156 1.09375 -3.703125 L 1.09375 -8.75 C 1.09375 -9.5625 1.484375 -9.96875 2.265625 -9.96875 C 3.046875 -9.96875 3.4375 -9.5625 3.4375 -8.75 L 3.4375 -4.234375 C 3.4375 -3.472656 3.597656 -2.890625 3.921875 -2.484375 C 4.242188 -2.085938 4.671875 -1.890625 5.203125 -1.890625 C 5.796875 -1.890625 6.273438 -2.09375 6.640625 -2.5 C 7.015625 -2.914062 7.203125 -3.523438 7.203125 -4.328125 L 7.203125 -8.75 C 7.203125 -9.5625 7.597656 -9.96875 8.390625 -9.96875 C 9.160156 -9.96875 9.546875 -9.5625 9.546875 -8.75 L 9.546875 -1.21875 C 9.546875 -0.832031 9.457031 -0.53125 9.28125 -0.3125 C 9.101562 -0.101562 8.84375 0 8.5 0 C 7.800781 0 7.441406 -0.175781 7.421875 -0.53125 L 7.328125 -1.1875 C 7.066406 -0.8125 6.707031 -0.476562 6.25 -0.1875 C 5.800781 0.09375 5.257812 0.234375 4.625 0.234375 Z M 4.625 0.234375 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(111.332389, 21.93013)"><g><path d="M 5.546875 0.234375 C 4.640625 0.234375 3.820312 0.0234375 3.09375 -0.390625 C 2.375 -0.804688 1.800781 -1.40625 1.375 -2.1875 C 0.957031 -2.96875 0.75 -3.90625 0.75 -5 C 0.75 -6.082031 0.957031 -7.015625 1.375 -7.796875 C 1.800781 -8.578125 2.375 -9.175781 3.09375 -9.59375 C 3.820312 -10.007812 4.640625 -10.21875 5.546875 -10.21875 C 6.597656 -10.21875 7.460938 -9.835938 8.140625 -9.078125 L 8.140625 -13.546875 C 8.140625 -14.359375 8.523438 -14.765625 9.296875 -14.765625 C 10.085938 -14.765625 10.484375 -14.359375 10.484375 -13.546875 L 10.484375 -1.21875 C 10.484375 -0.40625 10.144531 0 9.46875 0 C 8.800781 0 8.445312 -0.15625 8.40625 -0.46875 L 8.34375 -1.140625 C 8 -0.710938 7.585938 -0.375 7.109375 -0.125 C 6.640625 0.113281 6.117188 0.234375 5.546875 0.234375 Z M 5.65625 -1.890625 C 6.382812 -1.890625 6.988281 -2.15625 7.46875 -2.6875 C 7.957031 -3.226562 8.203125 -4 8.203125 -5 C 8.203125 -5.988281 7.957031 -6.75 7.46875 -7.28125 C 6.988281 -7.820312 6.382812 -8.09375 5.65625 -8.09375 C 4.925781 -8.09375 4.316406 -7.820312 3.828125 -7.28125 C 3.347656 -6.75 3.109375 -5.988281 3.109375 -5 C 3.109375 -4 3.347656 -3.226562 3.828125 -2.6875 C 4.316406 -2.15625 4.925781 -1.890625 5.65625 -1.890625 Z M 5.65625 -1.890625 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(123.082767, 21.93013)"><g><path d="M 3.484375 0 C 2.691406 0 2.296875 -0.40625 2.296875 -1.21875 L 2.296875 -7.953125 L 1.640625 -7.953125 C 0.828125 -7.953125 0.421875 -8.289062 0.421875 -8.96875 C 0.421875 -9.632812 0.703125 -9.96875 1.265625 -9.96875 L 2.296875 -9.96875 L 2.296875 -11.75 C 2.296875 -12.976562 2.578125 -13.828125 3.140625 -14.296875 C 3.710938 -14.765625 4.523438 -15 5.578125 -15 C 6.097656 -15 6.535156 -14.929688 6.890625 -14.796875 C 7.242188 -14.671875 7.410156 -14.378906 7.390625 -13.921875 C 7.390625 -13.628906 7.328125 -13.382812 7.203125 -13.1875 C 7.085938 -13 6.832031 -12.90625 6.4375 -12.90625 C 6.351562 -12.90625 6.253906 -12.910156 6.140625 -12.921875 C 6.035156 -12.941406 5.941406 -12.953125 5.859375 -12.953125 C 5.035156 -12.953125 4.625 -12.453125 4.625 -11.453125 L 4.625 -9.96875 L 6.171875 -9.96875 C 6.984375 -9.96875 7.390625 -9.632812 7.390625 -8.96875 C 7.390625 -8.289062 6.988281 -7.953125 6.1875 -7.953125 L 4.625 -7.953125 L 4.625 -1.21875 C 4.625 -0.40625 4.242188 0 3.484375 0 Z M 3.484375 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(130.72984, 21.93013)"><g><path d="M 2.4375 0 C 1.65625 0 1.265625 -0.40625 1.265625 -1.21875 L 1.265625 -13.546875 C 1.265625 -14.359375 1.65625 -14.765625 2.4375 -14.765625 C 3.21875 -14.765625 3.609375 -14.359375 3.609375 -13.546875 L 3.609375 -1.21875 C 3.609375 -0.40625 3.21875 0 2.4375 0 Z M 2.4375 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(135.599929, 21.93013)"><g><path d="M 4.53125 0.140625 C 3.457031 0.179688 2.570312 -0.0507812 1.875 -0.5625 C 1.175781 -1.070312 0.828125 -1.8125 0.828125 -2.78125 C 0.828125 -3.476562 1.039062 -4.078125 1.46875 -4.578125 C 1.894531 -5.078125 2.445312 -5.46875 3.125 -5.75 C 3.800781 -6.03125 4.5 -6.175781 5.21875 -6.1875 C 5.582031 -6.21875 5.921875 -6.191406 6.234375 -6.109375 C 6.554688 -6.023438 6.835938 -5.910156 7.078125 -5.765625 L 7.078125 -6.625 C 7.078125 -7.28125 6.9375 -7.703125 6.65625 -7.890625 C 6.382812 -8.085938 5.984375 -8.1875 5.453125 -8.1875 C 5.066406 -8.1875 4.660156 -8.132812 4.234375 -8.03125 C 3.816406 -7.9375 3.40625 -7.804688 3 -7.640625 C 2.644531 -7.492188 2.335938 -7.441406 2.078125 -7.484375 C 1.828125 -7.523438 1.617188 -7.691406 1.453125 -7.984375 C 1.285156 -8.265625 1.242188 -8.539062 1.328125 -8.8125 C 1.421875 -9.082031 1.65625 -9.296875 2.03125 -9.453125 C 2.53125 -9.679688 3.070312 -9.863281 3.65625 -10 C 4.238281 -10.144531 4.835938 -10.21875 5.453125 -10.21875 C 6.734375 -10.21875 7.695312 -9.957031 8.34375 -9.4375 C 9 -8.914062 9.328125 -7.976562 9.328125 -6.625 L 9.328125 -2.046875 C 9.328125 -1.910156 9.34375 -1.800781 9.375 -1.71875 C 9.40625 -1.632812 9.4375 -1.570312 9.46875 -1.53125 C 9.59375 -1.351562 9.726562 -1.25 9.875 -1.21875 C 10.019531 -1.195312 10.144531 -1.179688 10.25 -1.171875 C 10.363281 -1.160156 10.421875 -1.09375 10.421875 -0.96875 C 10.421875 -0.71875 10.351562 -0.503906 10.21875 -0.328125 C 10.09375 -0.148438 9.878906 -0.0234375 9.578125 0.046875 C 9.253906 0.109375 8.941406 0.140625 8.640625 0.140625 C 8.210938 0.140625 7.882812 0.0664062 7.65625 -0.078125 C 7.425781 -0.222656 7.265625 -0.46875 7.171875 -0.8125 C 6.828125 -0.519531 6.425781 -0.289062 5.96875 -0.125 C 5.507812 0.03125 5.03125 0.117188 4.53125 0.140625 Z M 4.5 -1.890625 C 5.269531 -1.910156 5.90625 -2.054688 6.40625 -2.328125 C 6.914062 -2.609375 7.15625 -3.003906 7.125 -3.515625 C 7.113281 -3.847656 6.941406 -4.078125 6.609375 -4.203125 C 6.285156 -4.328125 5.882812 -4.375 5.40625 -4.34375 C 4.726562 -4.332031 4.164062 -4.164062 3.71875 -3.84375 C 3.28125 -3.519531 3.070312 -3.15625 3.09375 -2.75 C 3.113281 -2.132812 3.582031 -1.847656 4.5 -1.890625 Z M 4.5 -1.890625 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(146.666417, 21.93013)"><g><path d="M 2.4375 0 C 1.65625 0 1.265625 -0.40625 1.265625 -1.21875 L 1.265625 -8.75 C 1.265625 -9.5625 1.671875 -9.96875 2.484375 -9.96875 C 2.765625 -9.96875 3.003906 -9.898438 3.203125 -9.765625 C 3.410156 -9.640625 3.515625 -9.394531 3.515625 -9.03125 L 3.515625 -8.5625 C 3.765625 -9.050781 4.15625 -9.445312 4.6875 -9.75 C 5.21875 -10.0625 5.78125 -10.21875 6.375 -10.21875 C 6.8125 -10.21875 7.140625 -10.101562 7.359375 -9.875 C 7.585938 -9.644531 7.703125 -9.335938 7.703125 -8.953125 C 7.703125 -8.585938 7.613281 -8.320312 7.4375 -8.15625 C 7.257812 -8 7.050781 -7.929688 6.8125 -7.953125 C 6.707031 -7.972656 6.59375 -7.984375 6.46875 -7.984375 C 6.34375 -7.992188 6.203125 -8 6.046875 -8 C 5.367188 -8 4.789062 -7.773438 4.3125 -7.328125 C 3.84375 -6.890625 3.609375 -6.269531 3.609375 -5.46875 L 3.609375 -1.21875 C 3.609375 -0.40625 3.21875 0 2.4375 0 Z M 2.4375 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(154.582897, 21.93013)"><g><path d="M 5.609375 0.234375 C 4.160156 0.234375 2.988281 -0.203125 2.09375 -1.078125 C 1.195312 -1.960938 0.75 -3.269531 0.75 -5 C 0.75 -6.070312 0.945312 -7 1.34375 -7.78125 C 1.75 -8.5625 2.3125 -9.160156 3.03125 -9.578125 C 3.75 -10.003906 4.566406 -10.21875 5.484375 -10.21875 C 6.410156 -10.21875 7.195312 -10.003906 7.84375 -9.578125 C 8.5 -9.148438 9 -8.597656 9.34375 -7.921875 C 9.6875 -7.242188 9.859375 -6.535156 9.859375 -5.796875 C 9.859375 -5.285156 9.753906 -4.953125 9.546875 -4.796875 C 9.335938 -4.640625 9.035156 -4.5625 8.640625 -4.5625 L 3.046875 -4.5625 C 3.097656 -3.5625 3.359375 -2.847656 3.828125 -2.421875 C 4.296875 -2.003906 4.90625 -1.796875 5.65625 -1.796875 C 6.5 -1.796875 7.15625 -2.085938 7.625 -2.671875 C 7.894531 -2.960938 8.148438 -3.148438 8.390625 -3.234375 C 8.640625 -3.328125 8.898438 -3.28125 9.171875 -3.09375 C 9.453125 -2.894531 9.613281 -2.644531 9.65625 -2.34375 C 9.695312 -2.039062 9.570312 -1.722656 9.28125 -1.390625 C 8.8125 -0.816406 8.269531 -0.398438 7.65625 -0.140625 C 7.050781 0.109375 6.367188 0.234375 5.609375 0.234375 Z M 3.15625 -6.359375 L 7.5625 -6.359375 C 7.445312 -7.003906 7.207031 -7.503906 6.84375 -7.859375 C 6.476562 -8.210938 6.015625 -8.390625 5.453125 -8.390625 C 4.835938 -8.390625 4.332031 -8.222656 3.9375 -7.890625 C 3.550781 -7.554688 3.289062 -7.046875 3.15625 -6.359375 Z M 3.15625 -6.359375 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(70.589457, 46.650523)"><g><path d="M 6.71875 0.296875 C 5.695312 0.296875 4.742188 0.117188 3.859375 -0.234375 C 2.972656 -0.585938 2.210938 -1.179688 1.578125 -2.015625 C 1.335938 -2.335938 1.253906 -2.640625 1.328125 -2.921875 C 1.410156 -3.210938 1.617188 -3.445312 1.953125 -3.625 C 2.265625 -3.820312 2.550781 -3.878906 2.8125 -3.796875 C 3.082031 -3.722656 3.347656 -3.539062 3.609375 -3.25 C 3.984375 -2.851562 4.425781 -2.539062 4.9375 -2.3125 C 5.457031 -2.09375 6.078125 -1.984375 6.796875 -1.984375 C 7.765625 -1.984375 8.441406 -2.160156 8.828125 -2.515625 C 9.222656 -2.867188 9.421875 -3.347656 9.421875 -3.953125 C 9.421875 -4.335938 9.332031 -4.664062 9.15625 -4.9375 C 8.988281 -5.21875 8.628906 -5.476562 8.078125 -5.71875 C 7.535156 -5.96875 6.703125 -6.21875 5.578125 -6.46875 C 4.234375 -6.78125 3.238281 -7.28125 2.59375 -7.96875 C 1.957031 -8.664062 1.640625 -9.546875 1.640625 -10.609375 C 1.640625 -11.453125 1.84375 -12.191406 2.25 -12.828125 C 2.65625 -13.472656 3.226562 -13.972656 3.96875 -14.328125 C 4.707031 -14.691406 5.570312 -14.875 6.5625 -14.875 C 7.601562 -14.875 8.507812 -14.691406 9.28125 -14.328125 C 10.050781 -13.972656 10.703125 -13.515625 11.234375 -12.953125 C 11.492188 -12.660156 11.59375 -12.367188 11.53125 -12.078125 C 11.476562 -11.785156 11.300781 -11.523438 11 -11.296875 C 10.695312 -11.046875 10.40625 -10.945312 10.125 -11 C 9.851562 -11.050781 9.578125 -11.210938 9.296875 -11.484375 C 8.953125 -11.796875 8.554688 -12.054688 8.109375 -12.265625 C 7.671875 -12.472656 7.148438 -12.578125 6.546875 -12.578125 C 5.828125 -12.578125 5.242188 -12.421875 4.796875 -12.109375 C 4.347656 -11.796875 4.125 -11.332031 4.125 -10.71875 C 4.125 -10.4375 4.1875 -10.164062 4.3125 -9.90625 C 4.445312 -9.65625 4.738281 -9.410156 5.1875 -9.171875 C 5.632812 -8.941406 6.328125 -8.71875 7.265625 -8.5 C 8.929688 -8.125 10.128906 -7.5625 10.859375 -6.8125 C 11.585938 -6.070312 11.953125 -5.132812 11.953125 -4 C 11.953125 -2.613281 11.472656 -1.550781 10.515625 -0.8125 C 9.554688 -0.0703125 8.289062 0.296875 6.71875 0.296875 Z M 6.71875 0.296875 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(83.521091, 46.650523)"><g><path d="M 5.34375 0.234375 C 4.363281 0.234375 3.613281 0.0546875 3.09375 -0.296875 C 2.582031 -0.660156 2.328125 -1.382812 2.328125 -2.46875 L 2.328125 -7.953125 L 1.640625 -7.953125 C 0.828125 -7.953125 0.421875 -8.289062 0.421875 -8.96875 C 0.421875 -9.289062 0.503906 -9.535156 0.671875 -9.703125 C 0.847656 -9.878906 1.160156 -9.96875 1.609375 -9.96875 L 2.328125 -9.96875 L 2.328125 -12.25 C 2.328125 -13.0625 2.71875 -13.46875 3.5 -13.46875 C 4.257812 -13.46875 4.640625 -13.0625 4.640625 -12.25 L 4.640625 -9.96875 L 6.171875 -9.96875 C 6.984375 -9.96875 7.390625 -9.632812 7.390625 -8.96875 C 7.390625 -8.289062 6.988281 -7.953125 6.1875 -7.953125 L 4.640625 -7.953125 L 4.640625 -3.0625 C 4.640625 -2.59375 4.753906 -2.273438 4.984375 -2.109375 C 5.210938 -1.941406 5.539062 -1.859375 5.96875 -1.859375 C 6.40625 -1.859375 6.75 -1.804688 7 -1.703125 C 7.257812 -1.597656 7.390625 -1.332031 7.390625 -0.90625 C 7.390625 -0.5625 7.289062 -0.3125 7.09375 -0.15625 C 6.894531 0 6.640625 0.101562 6.328125 0.15625 C 6.015625 0.207031 5.6875 0.234375 5.34375 0.234375 Z M 5.34375 0.234375 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(91.499742, 46.650523)"><g><path d="M 4.53125 0.140625 C 3.457031 0.179688 2.570312 -0.0507812 1.875 -0.5625 C 1.175781 -1.070312 0.828125 -1.8125 0.828125 -2.78125 C 0.828125 -3.476562 1.039062 -4.078125 1.46875 -4.578125 C 1.894531 -5.078125 2.445312 -5.46875 3.125 -5.75 C 3.800781 -6.03125 4.5 -6.175781 5.21875 -6.1875 C 5.582031 -6.21875 5.921875 -6.191406 6.234375 -6.109375 C 6.554688 -6.023438 6.835938 -5.910156 7.078125 -5.765625 L 7.078125 -6.625 C 7.078125 -7.28125 6.9375 -7.703125 6.65625 -7.890625 C 6.382812 -8.085938 5.984375 -8.1875 5.453125 -8.1875 C 5.066406 -8.1875 4.660156 -8.132812 4.234375 -8.03125 C 3.816406 -7.9375 3.40625 -7.804688 3 -7.640625 C 2.644531 -7.492188 2.335938 -7.441406 2.078125 -7.484375 C 1.828125 -7.523438 1.617188 -7.691406 1.453125 -7.984375 C 1.285156 -8.265625 1.242188 -8.539062 1.328125 -8.8125 C 1.421875 -9.082031 1.65625 -9.296875 2.03125 -9.453125 C 2.53125 -9.679688 3.070312 -9.863281 3.65625 -10 C 4.238281 -10.144531 4.835938 -10.21875 5.453125 -10.21875 C 6.734375 -10.21875 7.695312 -9.957031 8.34375 -9.4375 C 9 -8.914062 9.328125 -7.976562 9.328125 -6.625 L 9.328125 -2.046875 C 9.328125 -1.910156 9.34375 -1.800781 9.375 -1.71875 C 9.40625 -1.632812 9.4375 -1.570312 9.46875 -1.53125 C 9.59375 -1.351562 9.726562 -1.25 9.875 -1.21875 C 10.019531 -1.195312 10.144531 -1.179688 10.25 -1.171875 C 10.363281 -1.160156 10.421875 -1.09375 10.421875 -0.96875 C 10.421875 -0.71875 10.351562 -0.503906 10.21875 -0.328125 C 10.09375 -0.148438 9.878906 -0.0234375 9.578125 0.046875 C 9.253906 0.109375 8.941406 0.140625 8.640625 0.140625 C 8.210938 0.140625 7.882812 0.0664062 7.65625 -0.078125 C 7.425781 -0.222656 7.265625 -0.46875 7.171875 -0.8125 C 6.828125 -0.519531 6.425781 -0.289062 5.96875 -0.125 C 5.507812 0.03125 5.03125 0.117188 4.53125 0.140625 Z M 4.5 -1.890625 C 5.269531 -1.910156 5.90625 -2.054688 6.40625 -2.328125 C 6.914062 -2.609375 7.15625 -3.003906 7.125 -3.515625 C 7.113281 -3.847656 6.941406 -4.078125 6.609375 -4.203125 C 6.285156 -4.328125 5.882812 -4.375 5.40625 -4.34375 C 4.726562 -4.332031 4.164062 -4.164062 3.71875 -3.84375 C 3.28125 -3.519531 3.070312 -3.15625 3.09375 -2.75 C 3.113281 -2.132812 3.582031 -1.847656 4.5 -1.890625 Z M 4.5 -1.890625 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(102.56623, 46.650523)"><g><path d="M 2.4375 0 C 1.65625 0 1.265625 -0.40625 1.265625 -1.21875 L 1.265625 -8.75 C 1.265625 -9.5625 1.671875 -9.96875 2.484375 -9.96875 C 2.765625 -9.96875 3.003906 -9.898438 3.203125 -9.765625 C 3.410156 -9.640625 3.515625 -9.394531 3.515625 -9.03125 L 3.515625 -8.5625 C 3.765625 -9.050781 4.15625 -9.445312 4.6875 -9.75 C 5.21875 -10.0625 5.78125 -10.21875 6.375 -10.21875 C 6.8125 -10.21875 7.140625 -10.101562 7.359375 -9.875 C 7.585938 -9.644531 7.703125 -9.335938 7.703125 -8.953125 C 7.703125 -8.585938 7.613281 -8.320312 7.4375 -8.15625 C 7.257812 -8 7.050781 -7.929688 6.8125 -7.953125 C 6.707031 -7.972656 6.59375 -7.984375 6.46875 -7.984375 C 6.34375 -7.992188 6.203125 -8 6.046875 -8 C 5.367188 -8 4.789062 -7.773438 4.3125 -7.328125 C 3.84375 -6.890625 3.609375 -6.269531 3.609375 -5.46875 L 3.609375 -1.21875 C 3.609375 -0.40625 3.21875 0 2.4375 0 Z M 2.4375 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(110.48271, 46.650523)"><g><path d="M 2.4375 0 C 1.65625 0 1.265625 -0.40625 1.265625 -1.21875 L 1.265625 -13.546875 C 1.265625 -14.359375 1.65625 -14.765625 2.4375 -14.765625 C 3.21875 -14.765625 3.609375 -14.359375 3.609375 -13.546875 L 3.609375 -1.21875 C 3.609375 -0.40625 3.21875 0 2.4375 0 Z M 2.4375 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(115.352799, 46.650523)"><g><path d="M 2.75 0 C 1.976562 0 1.59375 -0.40625 1.59375 -1.21875 L 1.59375 -8.75 C 1.59375 -9.5625 1.976562 -9.96875 2.75 -9.96875 C 3.53125 -9.96875 3.921875 -9.5625 3.921875 -8.75 L 3.921875 -1.21875 C 3.921875 -0.40625 3.53125 0 2.75 0 Z M 2.734375 -11.609375 C 2.335938 -11.609375 1.992188 -11.75 1.703125 -12.03125 C 1.410156 -12.320312 1.265625 -12.671875 1.265625 -13.078125 C 1.265625 -13.472656 1.410156 -13.816406 1.703125 -14.109375 C 1.992188 -14.398438 2.335938 -14.546875 2.734375 -14.546875 C 3.128906 -14.546875 3.472656 -14.398438 3.765625 -14.109375 C 4.054688 -13.816406 4.203125 -13.472656 4.203125 -13.078125 C 4.203125 -12.671875 4.054688 -12.320312 3.765625 -12.03125 C 3.472656 -11.75 3.128906 -11.609375 2.734375 -11.609375 Z M 2.734375 -11.609375 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(120.823872, 46.650523)"><g><path d="M 5.34375 4.125 C 3.78125 4.125 2.546875 3.664062 1.640625 2.75 C 1.347656 2.445312 1.226562 2.148438 1.28125 1.859375 C 1.34375 1.566406 1.523438 1.328125 1.828125 1.140625 C 2.128906 0.929688 2.40625 0.859375 2.65625 0.921875 C 2.90625 0.992188 3.179688 1.148438 3.484375 1.390625 C 3.960938 1.804688 4.582031 2.015625 5.34375 2.015625 C 6.15625 2.015625 6.753906 1.765625 7.140625 1.265625 C 7.535156 0.765625 7.734375 -0.03125 7.734375 -1.125 L 7.734375 -1.671875 C 7.078125 -0.992188 6.253906 -0.65625 5.265625 -0.65625 C 4.390625 -0.65625 3.609375 -0.835938 2.921875 -1.203125 C 2.242188 -1.578125 1.710938 -2.117188 1.328125 -2.828125 C 0.941406 -3.535156 0.75 -4.410156 0.75 -5.453125 C 0.75 -6.503906 0.941406 -7.378906 1.328125 -8.078125 C 1.710938 -8.785156 2.242188 -9.316406 2.921875 -9.671875 C 3.609375 -10.035156 4.390625 -10.21875 5.265625 -10.21875 C 5.804688 -10.21875 6.320312 -10.101562 6.8125 -9.875 C 7.3125 -9.644531 7.71875 -9.257812 8.03125 -8.71875 L 8.03125 -9.078125 C 8.03125 -9.421875 8.117188 -9.65625 8.296875 -9.78125 C 8.484375 -9.90625 8.734375 -9.96875 9.046875 -9.96875 C 9.710938 -9.96875 10.046875 -9.5625 10.046875 -8.75 L 10.046875 -1.125 C 10.046875 0.519531 9.660156 1.804688 8.890625 2.734375 C 8.128906 3.660156 6.945312 4.125 5.34375 4.125 Z M 5.453125 -2.71875 C 6.171875 -2.71875 6.753906 -2.960938 7.203125 -3.453125 C 7.660156 -3.941406 7.890625 -4.609375 7.890625 -5.453125 C 7.890625 -6.304688 7.660156 -6.96875 7.203125 -7.4375 C 6.753906 -7.914062 6.171875 -8.15625 5.453125 -8.15625 C 4.734375 -8.15625 4.160156 -7.914062 3.734375 -7.4375 C 3.316406 -6.96875 3.109375 -6.304688 3.109375 -5.453125 C 3.109375 -4.609375 3.316406 -3.941406 3.734375 -3.453125 C 4.160156 -2.960938 4.734375 -2.71875 5.453125 -2.71875 Z M 5.453125 -2.71875 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(132.159778, 46.650523)"><g><path d="M 2.4375 0 C 1.65625 0 1.265625 -0.40625 1.265625 -1.21875 L 1.265625 -13.546875 C 1.265625 -14.359375 1.65625 -14.765625 2.4375 -14.765625 C 3.21875 -14.765625 3.609375 -14.359375 3.609375 -13.546875 L 3.609375 -8.78125 C 3.828125 -9.164062 4.15625 -9.5 4.59375 -9.78125 C 5.039062 -10.070312 5.570312 -10.21875 6.1875 -10.21875 C 7.226562 -10.21875 8.078125 -9.882812 8.734375 -9.21875 C 9.390625 -8.550781 9.71875 -7.578125 9.71875 -6.296875 L 9.71875 -1.21875 C 9.71875 -0.40625 9.332031 0 8.5625 0 C 7.769531 0 7.375 -0.40625 7.375 -1.21875 L 7.375 -5.734375 C 7.375 -6.523438 7.210938 -7.113281 6.890625 -7.5 C 6.578125 -7.894531 6.148438 -8.09375 5.609375 -8.09375 C 5.015625 -8.09375 4.53125 -7.890625 4.15625 -7.484375 C 3.789062 -7.078125 3.609375 -6.472656 3.609375 -5.671875 L 3.609375 -1.21875 C 3.609375 -0.40625 3.21875 0 2.4375 0 Z M 2.4375 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(142.749624, 46.650523)"><g><path d="M 5.34375 0.234375 C 4.363281 0.234375 3.613281 0.0546875 3.09375 -0.296875 C 2.582031 -0.660156 2.328125 -1.382812 2.328125 -2.46875 L 2.328125 -7.953125 L 1.640625 -7.953125 C 0.828125 -7.953125 0.421875 -8.289062 0.421875 -8.96875 C 0.421875 -9.289062 0.503906 -9.535156 0.671875 -9.703125 C 0.847656 -9.878906 1.160156 -9.96875 1.609375 -9.96875 L 2.328125 -9.96875 L 2.328125 -12.25 C 2.328125 -13.0625 2.71875 -13.46875 3.5 -13.46875 C 4.257812 -13.46875 4.640625 -13.0625 4.640625 -12.25 L 4.640625 -9.96875 L 6.171875 -9.96875 C 6.984375 -9.96875 7.390625 -9.632812 7.390625 -8.96875 C 7.390625 -8.289062 6.988281 -7.953125 6.1875 -7.953125 L 4.640625 -7.953125 L 4.640625 -3.0625 C 4.640625 -2.59375 4.753906 -2.273438 4.984375 -2.109375 C 5.210938 -1.941406 5.539062 -1.859375 5.96875 -1.859375 C 6.40625 -1.859375 6.75 -1.804688 7 -1.703125 C 7.257812 -1.597656 7.390625 -1.332031 7.390625 -0.90625 C 7.390625 -0.5625 7.289062 -0.3125 7.09375 -0.15625 C 6.894531 0 6.640625 0.101562 6.328125 0.15625 C 6.015625 0.207031 5.6875 0.234375 5.34375 0.234375 Z M 5.34375 0.234375 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(150.728275, 46.650523)"><g/></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(156.530926, 46.650523)"><g><path d="M 7.546875 0.328125 C 6.234375 0.328125 5.0625 0.0195312 4.03125 -0.59375 C 3 -1.21875 2.191406 -2.097656 1.609375 -3.234375 C 1.035156 -4.378906 0.75 -5.71875 0.75 -7.25 C 0.75 -8.800781 1.035156 -10.144531 1.609375 -11.28125 C 2.191406 -12.414062 3 -13.296875 4.03125 -13.921875 C 5.0625 -14.554688 6.234375 -14.875 7.546875 -14.875 C 8.523438 -14.875 9.425781 -14.691406 10.25 -14.328125 C 11.070312 -13.972656 11.785156 -13.472656 12.390625 -12.828125 C 12.660156 -12.523438 12.75 -12.21875 12.65625 -11.90625 C 12.5625 -11.59375 12.375 -11.351562 12.09375 -11.1875 L 12 -11.125 C 11.820312 -11.007812 11.566406 -10.96875 11.234375 -11 C 10.898438 -11.03125 10.59375 -11.175781 10.3125 -11.4375 C 9.957031 -11.789062 9.546875 -12.0625 9.078125 -12.25 C 8.617188 -12.4375 8.109375 -12.53125 7.546875 -12.53125 C 6.679688 -12.53125 5.925781 -12.3125 5.28125 -11.875 C 4.632812 -11.445312 4.132812 -10.832031 3.78125 -10.03125 C 3.425781 -9.238281 3.25 -8.3125 3.25 -7.25 C 3.25 -6.195312 3.425781 -5.273438 3.78125 -4.484375 C 4.132812 -3.691406 4.632812 -3.078125 5.28125 -2.640625 C 5.925781 -2.203125 6.679688 -1.984375 7.546875 -1.984375 C 8.628906 -1.984375 9.550781 -2.351562 10.3125 -3.09375 C 10.59375 -3.351562 10.878906 -3.503906 11.171875 -3.546875 C 11.460938 -3.585938 11.738281 -3.546875 12 -3.421875 L 12.03125 -3.375 C 12.320312 -3.238281 12.523438 -3.007812 12.640625 -2.6875 C 12.765625 -2.363281 12.695312 -2.046875 12.4375 -1.734375 C 11.820312 -1.078125 11.097656 -0.566406 10.265625 -0.203125 C 9.441406 0.148438 8.535156 0.328125 7.546875 0.328125 Z M 7.546875 0.328125 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(170.457304, 46.650523)"><g><path d="M 2.671875 0 C 1.867188 0 1.46875 -0.40625 1.46875 -1.21875 L 1.46875 -13.28125 C 1.46875 -13.75 1.585938 -14.066406 1.828125 -14.234375 C 2.078125 -14.410156 2.4375 -14.5 2.90625 -14.5 C 3.539062 -14.5 4.019531 -14.222656 4.34375 -13.671875 L 8.015625 -7.546875 L 11.640625 -13.65625 C 11.835938 -14 12.0625 -14.222656 12.3125 -14.328125 C 12.570312 -14.441406 12.910156 -14.5 13.328125 -14.5 C 14.140625 -14.5 14.546875 -14.09375 14.546875 -13.28125 L 14.546875 -1.21875 C 14.546875 -0.40625 14.140625 0 13.328125 0 C 12.503906 0 12.09375 -0.40625 12.09375 -1.21875 L 12.09375 -10.34375 L 9.171875 -5.28125 C 8.890625 -4.71875 8.5 -4.4375 8 -4.4375 C 7.5 -4.4375 7.101562 -4.71875 6.8125 -5.28125 L 3.875 -10.34375 L 3.875 -1.21875 C 3.875 -0.40625 3.472656 0 2.671875 0 Z M 2.671875 0 "/></g></g></g><g fill="#8b5cf6" fill-opacity="1"><g transform="translate(186.456052, 46.650523)"><g><path d="M 6.71875 0.296875 C 5.695312 0.296875 4.742188 0.117188 3.859375 -0.234375 C 2.972656 -0.585938 2.210938 -1.179688 1.578125 -2.015625 C 1.335938 -2.335938 1.253906 -2.640625 1.328125 -2.921875 C 1.410156 -3.210938 1.617188 -3.445312 1.953125 -3.625 C 2.265625 -3.820312 2.550781 -3.878906 2.8125 -3.796875 C 3.082031 -3.722656 3.347656 -3.539062 3.609375 -3.25 C 3.984375 -2.851562 4.425781 -2.539062 4.9375 -2.3125 C 5.457031 -2.09375 6.078125 -1.984375 6.796875 -1.984375 C 7.765625 -1.984375 8.441406 -2.160156 8.828125 -2.515625 C 9.222656 -2.867188 9.421875 -3.347656 9.421875 -3.953125 C 9.421875 -4.335938 9.332031 -4.664062 9.15625 -4.9375 C 8.988281 -5.21875 8.628906 -5.476562 8.078125 -5.71875 C 7.535156 -5.96875 6.703125 -6.21875 5.578125 -6.46875 C 4.234375 -6.78125 3.238281 -7.28125 2.59375 -7.96875 C 1.957031 -8.664062 1.640625 -9.546875 1.640625 -10.609375 C 1.640625 -11.453125 1.84375 -12.191406 2.25 -12.828125 C 2.65625 -13.472656 3.226562 -13.972656 3.96875 -14.328125 C 4.707031 -14.691406 5.570312 -14.875 6.5625 -14.875 C 7.601562 -14.875 8.507812 -14.691406 9.28125 -14.328125 C 10.050781 -13.972656 10.703125 -13.515625 11.234375 -12.953125 C 11.492188 -12.660156 11.59375 -12.367188 11.53125 -12.078125 C 11.476562 -11.785156 11.300781 -11.523438 11 -11.296875 C 10.695312 -11.046875 10.40625 -10.945312 10.125 -11 C 9.851562 -11.050781 9.578125 -11.210938 9.296875 -11.484375 C 8.953125 -11.796875 8.554688 -12.054688 8.109375 -12.265625 C 7.671875 -12.472656 7.148438 -12.578125 6.546875 -12.578125 C 5.828125 -12.578125 5.242188 -12.421875 4.796875 -12.109375 C 4.347656 -11.796875 4.125 -11.332031 4.125 -10.71875 C 4.125 -10.4375 4.1875 -10.164062 4.3125 -9.90625 C 4.445312 -9.65625 4.738281 -9.410156 5.1875 -9.171875 C 5.632812 -8.941406 6.328125 -8.71875 7.265625 -8.5 C 8.929688 -8.125 10.128906 -7.5625 10.859375 -6.8125 C 11.585938 -6.070312 11.953125 -5.132812 11.953125 -4 C 11.953125 -2.613281 11.472656 -1.550781 10.515625 -0.8125 C 9.554688 -0.0703125 8.289062 0.296875 6.71875 0.296875 Z M 6.71875 0.296875 "/></g></g></g></svg>
@@ -0,0 +1 @@
1
+ /// <reference types="astro/client" />
@@ -0,0 +1,12 @@
1
+ import { defineCollection } from 'astro:content';
2
+ import { docsSchema } from '@astrojs/starlight/schema';
3
+ import { cmsLoader } from './starlight/loader.ts';
4
+
5
+ import { loadSnapshot } from './starlight/source.ts';
6
+
7
+ export const collections = {
8
+ docs: defineCollection({
9
+ loader: cmsLoader({ loadSnapshot }),
10
+ schema: docsSchema(),
11
+ }),
12
+ };
@@ -0,0 +1,36 @@
1
+ /// <reference path="../sql.d.ts" />
2
+ import schema from '../../migrations/0001_schema.sql';
3
+ import delivery from '../../migrations/0002_publish_delivery.sql';
4
+
5
+ // Only these bounded, idempotent CREATE migrations run on requests. Future
6
+ // ALTER/backfill migrations need an explicit deployment migration plan.
7
+ const migrations = [
8
+ ['0001_schema.sql', schema],
9
+ ['0002_publish_delivery.sql', delivery],
10
+ ] as const;
11
+ const ready = new WeakSet<D1Database>();
12
+
13
+ export async function ensureSchema(db: D1Database): Promise<void> {
14
+ if (ready.has(db)) return;
15
+ await db.prepare(`CREATE TABLE IF NOT EXISTS d1_migrations (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ name TEXT UNIQUE,
18
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
19
+ )`).run();
20
+ const applied = await db.prepare('SELECT name FROM d1_migrations').all<{ name: string }>();
21
+ const names = new Set(applied.results.map((row) => row.name));
22
+ for (const [name, sql] of migrations) {
23
+ if (names.has(name)) continue;
24
+ const statements = sql.split(';').map((part) => part.trim()).filter(Boolean);
25
+ if (statements.some((statement) => !/^CREATE (?:TABLE|(?:UNIQUE )?INDEX) IF NOT EXISTS\s/i.test(statement))) {
26
+ throw new Error('Runtime bootstrap only supports idempotent CREATE statements');
27
+ }
28
+ // D1 batch rolls back schema and history together on failure. Concurrent
29
+ // bootstraps are safe because every statement is idempotent.
30
+ await db.batch([
31
+ ...statements.map((statement) => db.prepare(statement)),
32
+ db.prepare('INSERT OR IGNORE INTO d1_migrations (name) VALUES (?)').bind(name),
33
+ ]);
34
+ }
35
+ ready.add(db);
36
+ }
@@ -0,0 +1,7 @@
1
+ import { drizzle } from 'drizzle-orm/d1';
2
+ import type { RuntimeEnv } from '../env.ts';
3
+ import * as schema from './schema.ts';
4
+
5
+ export function database(env: RuntimeEnv) {
6
+ return drizzle(env.DB, { schema });
7
+ }
@@ -0,0 +1,45 @@
1
+ import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
2
+
3
+ export const folder = sqliteTable('folder', {
4
+ id: text('id').primaryKey(), parentId: text('parent_id'), slug: text('slug').notNull(),
5
+ order: integer('sort_order').notNull(), createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull(),
6
+ });
7
+ export const folderTranslation = sqliteTable('folder_translation', {
8
+ folderId: text('folder_id').notNull(), locale: text('locale').notNull(), name: text('name').notNull(),
9
+ createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull(),
10
+ }, (table) => [uniqueIndex('folder_translation_folder_locale').on(table.folderId, table.locale)]);
11
+
12
+ export const document = sqliteTable('document', {
13
+ id: text('id').primaryKey(), folderId: text('folder_id'), slug: text('slug').notNull(), order: integer('sort_order').notNull(),
14
+ createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull(),
15
+ });
16
+ export const documentTranslation = sqliteTable('document_translation', {
17
+ id: text('id').primaryKey(), documentId: text('document_id').notNull(), locale: text('locale').notNull(), title: text('title').notNull(),
18
+ sidebarLabel: text('sidebar_label'), description: text('description').notNull(), contentJson: text('content_json').notNull(),
19
+ publishedRevisionId: text('published_revision_id'), version: integer('version').notNull(), createdAt: integer('created_at').notNull(),
20
+ updatedAt: integer('updated_at').notNull(), publishedAt: integer('published_at'),
21
+ }, (table) => [uniqueIndex('document_translation_document_locale').on(table.documentId, table.locale)]);
22
+
23
+ export const documentRevision = sqliteTable('document_revision', {
24
+ id: text('id').primaryKey(), documentTranslationId: text('document_translation_id').notNull(), revision: integer('revision').notNull(), title: text('title').notNull(),
25
+ sidebarLabel: text('sidebar_label'), description: text('description').notNull(), contentJson: text('content_json').notNull(), createdAt: integer('created_at').notNull(),
26
+ }, (table) => [uniqueIndex('document_revision_translation_revision').on(table.documentTranslationId, table.revision)]);
27
+
28
+ export const media = sqliteTable('media', {
29
+ id: text('id').primaryKey(), objectKey: text('object_key').notNull().unique(), fileName: text('file_name').notNull(),
30
+ contentType: text('content_type').notNull(), size: integer('size').notNull(), createdAt: integer('created_at').notNull(),
31
+ });
32
+
33
+ export const publishDelivery = sqliteTable('publish_delivery', {
34
+ id: text('id').primaryKey(),
35
+ triggerKind: text('trigger_kind').notNull(),
36
+ documentTranslationId: text('document_translation_id'),
37
+ status: text('status').notNull(),
38
+ attempts: integer('attempts').notNull(),
39
+ buildId: text('build_id'),
40
+ alreadyExists: integer('already_exists').notNull(),
41
+ lastError: text('last_error'),
42
+ requestedAt: integer('requested_at').notNull(),
43
+ acceptedAt: integer('accepted_at'),
44
+ nextRetryAt: integer('next_retry_at'),
45
+ });
@@ -0,0 +1,178 @@
1
+ import { siteDeliveryStatement } from '../publish/record.ts';
2
+ import { defaultLocale, type SupportedLocale } from '../locales.ts';
3
+ import type { RuntimeEnv } from '../env.ts';
4
+ import { documentInput, documentUpdate, parseContent, serializeContent, type DocumentInput } from './validation.ts';
5
+
6
+ export class DocumentConflictError extends Error {}
7
+ export class DocumentNotFoundError extends Error {}
8
+
9
+ type TranslationRow = {
10
+ id: string; document_id: string; locale: string; title: string; sidebar_label: string | null;
11
+ description: string; content_json: string; published_revision_id: string | null; version: number;
12
+ created_at: number; updated_at: number; published_at: number | null; folder_id: string | null;
13
+ slug: string; sort_order: number;
14
+ };
15
+
16
+ export type DocumentView = {
17
+ id: string; translationId: string; locale: string; folderId: string | null; title: string;
18
+ sidebarLabel: string | null; slug: string; description: string; order: number; contentJson: unknown;
19
+ status: 'draft' | 'published'; version: number; createdAt: number; updatedAt: number;
20
+ publishedAt: number | null; publishedRevisionId: string | null;
21
+ };
22
+
23
+ /** A delivery is inserted in the same D1 batch as the published revision pointer. */
24
+ export type PendingPublishDelivery = { id: string; requestedAt: number };
25
+
26
+ const translationSelect = `
27
+ SELECT t.id,t.document_id,t.locale,t.title,t.sidebar_label,t.description,t.content_json,
28
+ t.published_revision_id,t.version,t.created_at,t.updated_at,t.published_at,
29
+ d.folder_id,d.slug,d.sort_order
30
+ FROM document_translation t JOIN document d ON d.id=t.document_id`;
31
+
32
+ function toView(row: TranslationRow): DocumentView {
33
+ return {
34
+ id: row.document_id, translationId: row.id, locale: row.locale, folderId: row.folder_id,
35
+ title: row.title, sidebarLabel: row.sidebar_label, slug: row.slug, description: row.description,
36
+ order: row.sort_order, contentJson: parseContent(row.content_json),
37
+ status: row.published_revision_id ? 'published' : 'draft', version: row.version,
38
+ createdAt: row.created_at, updatedAt: row.updated_at, publishedAt: row.published_at,
39
+ publishedRevisionId: row.published_revision_id,
40
+ };
41
+ }
42
+
43
+ async function assertSlugAvailable(env: RuntimeEnv, folderId: string | null, slug: string, exceptId = '') {
44
+ const [folderMatch, documentMatch] = await Promise.all([
45
+ env.DB.prepare('SELECT id FROM folder WHERE parent_id IS ? AND slug = ? LIMIT 1').bind(folderId, slug).first(),
46
+ env.DB.prepare('SELECT id FROM document WHERE folder_id IS ? AND slug = ? AND id <> ? LIMIT 1').bind(folderId, slug, exceptId).first(),
47
+ ]);
48
+ if (folderMatch || documentMatch) throw new DocumentConflictError('A folder or page already uses this URL segment');
49
+ }
50
+
51
+ async function getTranslation(env: RuntimeEnv, documentId: string, locale: SupportedLocale): Promise<TranslationRow> {
52
+ const row = await env.DB.prepare(`${translationSelect} WHERE t.document_id=? AND t.locale=?`)
53
+ .bind(documentId, locale).first<TranslationRow>();
54
+ if (!row) throw new DocumentNotFoundError();
55
+ return row;
56
+ }
57
+
58
+ function snapshotStatement(env: RuntimeEnv, id: string, translationId: string, input: Pick<DocumentInput, 'title' | 'description' | 'contentJson'>, now: number, afterUpdate = false) {
59
+ return env.DB.prepare(`
60
+ INSERT INTO document_revision (id,document_translation_id,revision,title,sidebar_label,description,content_json,created_at)
61
+ SELECT ?, id, COALESCE((SELECT MAX(revision) + 1 FROM document_revision WHERE document_translation_id = ?), 1), ?, sidebar_label, ?, ?, ?
62
+ FROM document_translation WHERE id = ? ${afterUpdate ? 'AND changes()=1' : ''}`)
63
+ .bind(id, translationId, input.title, input.description, serializeContent(input.contentJson), now, translationId);
64
+ }
65
+
66
+ async function snapshot(env: RuntimeEnv, translationId: string, input: Pick<DocumentInput, 'title' | 'description' | 'contentJson'>, now: number) {
67
+ const id = crypto.randomUUID();
68
+ await snapshotStatement(env, id, translationId, input, now).run();
69
+ return id;
70
+ }
71
+
72
+ export async function listDocuments(env: RuntimeEnv, locale: SupportedLocale = defaultLocale) {
73
+ const rows = await env.DB.prepare(`${translationSelect} WHERE t.locale=? ORDER BY d.sort_order,d.slug`)
74
+ .bind(locale).all<TranslationRow>();
75
+ return rows.results.map(toView);
76
+ }
77
+
78
+ export async function getDocument(env: RuntimeEnv, id: string, locale: SupportedLocale = defaultLocale) {
79
+ return toView(await getTranslation(env, id, locale));
80
+ }
81
+
82
+ export async function createDocument(env: RuntimeEnv, raw: unknown, locale: SupportedLocale = defaultLocale) {
83
+ if (locale !== defaultLocale) throw new DocumentConflictError(`Create pages in ${defaultLocale}, then add a translation`);
84
+ const input = documentInput.parse(raw);
85
+ await assertSlugAvailable(env, input.folderId, input.slug);
86
+ const id = crypto.randomUUID();
87
+ const translationId = crypto.randomUUID();
88
+ const now = Date.now();
89
+ await env.DB.batch([
90
+ env.DB.prepare('INSERT INTO document (id,folder_id,slug,sort_order,created_at,updated_at) VALUES (?,?,?,?,?,?)')
91
+ .bind(id, input.folderId, input.slug, input.order, now, now),
92
+ env.DB.prepare('INSERT INTO document_translation (id,document_id,locale,title,sidebar_label,description,content_json,published_revision_id,version,created_at,updated_at,published_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)')
93
+ .bind(translationId, id, locale, input.title, null, input.description, serializeContent(input.contentJson), null, 1, now, now, null),
94
+ ]);
95
+ await snapshot(env, translationId, input, now);
96
+ return getDocument(env, id, locale);
97
+ }
98
+
99
+ export async function updateDocument(env: RuntimeEnv, id: string, raw: unknown, locale: SupportedLocale = defaultLocale) {
100
+ const input = documentUpdate.parse(raw);
101
+ await assertSlugAvailable(env, input.folderId, input.slug, id);
102
+ const current = await getTranslation(env, id, locale);
103
+ if (current.version !== input.version) throw new DocumentConflictError();
104
+ const now = Date.now();
105
+ const results = await env.DB.batch([
106
+ siteDeliveryStatement(env, `EXISTS (SELECT 1 FROM document d JOIN document_translation t ON t.document_id=d.id
107
+ WHERE d.id=? AND t.published_revision_id IS NOT NULL AND (d.folder_id IS NOT ? OR d.slug<>? OR d.sort_order<>?)
108
+ AND EXISTS (SELECT 1 FROM document_translation WHERE id=? AND version=?))`, [id,input.folderId,input.slug,input.order,current.id,input.version]),
109
+ env.DB.prepare('UPDATE document SET folder_id=?,slug=?,sort_order=?,updated_at=? WHERE id=? AND EXISTS (SELECT 1 FROM document_translation WHERE id=? AND version=?)')
110
+ .bind(input.folderId, input.slug, input.order, now, id, current.id, input.version),
111
+ env.DB.prepare('UPDATE document_translation SET title=?,description=?,content_json=?,updated_at=?,version=version+1 WHERE id=? AND version=?')
112
+ .bind(input.title, input.description, serializeContent(input.contentJson), now, current.id, input.version),
113
+ snapshotStatement(env, crypto.randomUUID(), current.id, input, now, true),
114
+ ]);
115
+ if (results[2]?.meta.changes !== 1) throw new DocumentConflictError();
116
+ return getDocument(env, id, locale);
117
+ }
118
+
119
+ export async function publishDocument(env: RuntimeEnv, id: string, version: number, locale: SupportedLocale = defaultLocale, delivery?: PendingPublishDelivery) {
120
+ const current = await getTranslation(env, id, locale);
121
+ if (current.version !== version) throw new DocumentConflictError();
122
+ const now = Date.now();
123
+ const revisionId = crypto.randomUUID();
124
+ const results = await env.DB.batch([
125
+ env.DB.prepare(`INSERT INTO document_revision (id,document_translation_id,revision,title,sidebar_label,description,content_json,created_at)
126
+ SELECT ?,id,COALESCE((SELECT MAX(revision)+1 FROM document_revision WHERE document_translation_id=?),1),title,sidebar_label,description,content_json,?
127
+ FROM document_translation WHERE id=? AND version=?`).bind(revisionId,current.id,now,current.id,version),
128
+ env.DB.prepare('UPDATE document_translation SET published_revision_id=?,published_at=?,updated_at=?,version=version+1 WHERE id=? AND version=?')
129
+ .bind(revisionId, now, now, current.id, version),
130
+ ...(delivery ? [env.DB.prepare(`
131
+ INSERT INTO publish_delivery (id,trigger_kind,document_translation_id,status,attempts,already_exists,requested_at)
132
+ SELECT ?,'document',id,'pending',0,0,? FROM document_translation WHERE id=? AND published_revision_id=?`).bind(delivery.id, delivery.requestedAt, current.id, revisionId)] : []),
133
+ ]);
134
+ if (results[1]?.meta.changes !== 1) {
135
+ throw new DocumentConflictError();
136
+ }
137
+ return getDocument(env, id, locale);
138
+ }
139
+
140
+ export async function listRevisions(env: RuntimeEnv, id: string, locale: SupportedLocale = defaultLocale) {
141
+ const current = await getTranslation(env, id, locale);
142
+ const rows = await env.DB.prepare('SELECT id,revision,created_at FROM document_revision WHERE document_translation_id=? ORDER BY revision DESC')
143
+ .bind(current.id).all<{ id: string; revision: number; created_at: number }>();
144
+ return rows.results.map((row) => ({ id: row.id, revision: row.revision, createdAt: row.created_at }));
145
+ }
146
+
147
+ export async function restoreRevision(env: RuntimeEnv, id: string, revisionId: string, version: number, locale: SupportedLocale = defaultLocale) {
148
+ const current = await getTranslation(env, id, locale);
149
+ const revision = await env.DB.prepare('SELECT title,description,content_json FROM document_revision WHERE id=? AND document_translation_id=?')
150
+ .bind(revisionId, current.id).first<{ title: string; description: string; content_json: string }>();
151
+ if (!revision) throw new DocumentNotFoundError();
152
+ return updateDocument(env, id, {
153
+ title: revision.title, slug: current.slug, description: revision.description,
154
+ contentJson: parseContent(revision.content_json), folderId: current.folder_id, order: current.sort_order, version,
155
+ }, locale);
156
+ }
157
+
158
+ export async function deleteDocument(env: RuntimeEnv, id: string, version: number, locale: SupportedLocale = defaultLocale) {
159
+ const current = await getTranslation(env, id, locale);
160
+ const results = await env.DB.batch([
161
+ siteDeliveryStatement(env, 'EXISTS (SELECT 1 FROM document_translation WHERE id=? AND version=? AND published_revision_id IS NOT NULL)', [current.id, version]),
162
+ env.DB.prepare('DELETE FROM document_translation WHERE id=? AND version=? RETURNING id').bind(current.id, version),
163
+ env.DB.prepare('DELETE FROM document WHERE id=? AND NOT EXISTS (SELECT 1 FROM document_translation WHERE document_id=?)').bind(id, id),
164
+ ]);
165
+ if (results[1]?.results.length !== 1) throw new DocumentConflictError();
166
+ }
167
+
168
+ export async function createDocumentTranslation(env: RuntimeEnv, documentId: string, locale: SupportedLocale, sourceLocale: SupportedLocale = defaultLocale) {
169
+ if (locale === sourceLocale) throw new DocumentConflictError('Choose a different language');
170
+ const source = await getTranslation(env, documentId, sourceLocale);
171
+ const existing = await env.DB.prepare('SELECT id FROM document_translation WHERE document_id=? AND locale=?').bind(documentId, locale).first();
172
+ if (existing) throw new DocumentConflictError('This translation already exists');
173
+ const id = crypto.randomUUID(); const now = Date.now();
174
+ await env.DB.prepare('INSERT INTO document_translation (id,document_id,locale,title,sidebar_label,description,content_json,published_revision_id,version,created_at,updated_at,published_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)')
175
+ .bind(id, documentId, locale, source.title, source.sidebar_label, source.description, source.content_json, null, 1, now, now, null).run();
176
+ await snapshot(env, id, { title: source.title, description: source.description, contentJson: parseContent(source.content_json) }, now);
177
+ return getDocument(env, documentId, locale);
178
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ export const slug = z.string().min(1).max(120).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Slug must use lowercase URL segments').refine((value) => !['admin', 'api', 'auth', '_astro', 'pagefind', '404'].includes(value), 'Reserved route');
3
+ const jsonNode = z.object({ type: z.string().min(1) }).passthrough();
4
+ export const contentJson = z.object({ type: z.literal('doc'), content: z.array(jsonNode).optional() }).passthrough();
5
+ export const documentInput = z.object({ title: z.string().trim().min(1).max(200), slug, description: z.string().trim().max(300).default(''), contentJson, folderId: z.string().uuid().nullable().default(null), order: z.number().int().min(0).max(100000).default(0) });
6
+ export const documentUpdate = documentInput.extend({ version: z.number().int().positive() });
7
+ export type DocumentInput = z.infer<typeof documentInput>;
8
+ export function serializeContent(content: z.infer<typeof contentJson>) { return JSON.stringify(content); }
9
+ export function parseContent(input: string) { return contentJson.parse(JSON.parse(input)); }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Worker bindings come from `wrangler types`. Secrets cannot be generated from
3
+ * wrangler.jsonc, so only those runtime-only values are declared here.
4
+ */
5
+ export type RuntimeEnv = CloudflareBindings & {
6
+ MEDIA_PUBLIC_URL?: string;
7
+ WORKERS_DEPLOY_HOOK_URL?: string;
8
+ LOCAL_DEV_BUILD?: string;
9
+ };
@@ -0,0 +1,10 @@
1
+ import { adminApp } from './admin/app.ts';
2
+ import type { RuntimeEnv } from './env.ts';
3
+
4
+ export default {
5
+ async fetch(request: Request, env: RuntimeEnv, executionCtx: ExecutionContext): Promise<Response> {
6
+ const path = new URL(request.url).pathname;
7
+ if (path !== '/admin' && !path.startsWith('/admin/')) return env.ASSETS.fetch(request);
8
+ return adminApp.fetch(request, env, executionCtx);
9
+ },
10
+ } satisfies ExportedHandler<RuntimeEnv>;
@@ -0,0 +1,10 @@
1
+ import { siteConfig } from './site.config.ts';
2
+ export const defaultLocale = siteConfig.defaultLocale;
3
+ export const supportedLocales = siteConfig.locales.map(({ code }) => code);
4
+ export type SupportedLocale = (typeof supportedLocales)[number];
5
+ export function isSupportedLocale(value: string): value is SupportedLocale {
6
+ return supportedLocales.some((locale) => locale === value);
7
+ }
8
+ export function localeLabel(value: SupportedLocale): string {
9
+ return siteConfig.locales.find(({ code }) => code === value)?.label ?? value;
10
+ }
@@ -0,0 +1,120 @@
1
+ import { asc, eq } from 'drizzle-orm';
2
+ import { database } from '../db/client.ts';
3
+ import { documentRevision, documentTranslation, media } from '../db/schema.ts';
4
+ import type { RuntimeEnv } from '../env.ts';
5
+
6
+ const mediaTypes = new Map([
7
+ ['image/png', 'png'], ['image/jpeg', 'jpg'], ['image/webp', 'webp'], ['image/avif', 'avif'],
8
+ ['video/mp4', 'mp4'], ['video/webm', 'webm'],
9
+ ]);
10
+ /** Direct multipart parsing in a Worker is intentionally kept small. */
11
+ export const directUploadMaxBytes = 10 * 1024 * 1024;
12
+
13
+ export class InvalidMediaError extends Error {}
14
+ export class MediaNotFoundError extends Error {}
15
+ export class MediaInUseError extends Error {}
16
+
17
+ function mediaUrl(env: RuntimeEnv, key: string): string {
18
+ const base = env.MEDIA_PUBLIC_URL?.replace(/\/$/, '');
19
+ return base ? `${base}/${key}` : `/admin/api/media/object/${key}`;
20
+ }
21
+
22
+ export function signatureMatches(type: string, bytes: Uint8Array): boolean {
23
+ if (type === 'image/png') return bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;
24
+ if (type === 'image/jpeg') return bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;
25
+ if (type === 'image/webp') return new TextDecoder().decode(bytes.slice(0, 4)) === 'RIFF' && new TextDecoder().decode(bytes.slice(8, 12)) === 'WEBP';
26
+ if (type === 'image/avif' || type === 'video/mp4') return new TextDecoder().decode(bytes.slice(4, 8)) === 'ftyp';
27
+ if (type === 'video/webm') return bytes[0] === 0x1a && bytes[1] === 0x45 && bytes[2] === 0xdf && bytes[3] === 0xa3;
28
+ return false;
29
+ }
30
+
31
+ function contentReferencesObjectKey(content: unknown, objectKey: string): boolean {
32
+ if (!content || typeof content !== 'object') return false;
33
+ if (Array.isArray(content)) return content.some((item) => contentReferencesObjectKey(item, objectKey));
34
+
35
+ const node = content as { type?: unknown; attrs?: { src?: unknown }; content?: unknown };
36
+ if ((node.type === 'image' || node.type === 'video') && typeof node.attrs?.src === 'string') {
37
+ try {
38
+ const { pathname } = new URL(node.attrs.src, 'https://cms.invalid');
39
+ if (pathname === `/${objectKey}` || pathname.endsWith(`/${objectKey}`)) return true;
40
+ } catch {
41
+ // A malformed editor URL cannot identify a managed R2 object.
42
+ }
43
+ }
44
+ return contentReferencesObjectKey(node.content, objectKey);
45
+ }
46
+
47
+ export function contentReferencesMediaObject(contentJson: string, objectKey: string): boolean {
48
+ try {
49
+ return contentReferencesObjectKey(JSON.parse(contentJson), objectKey);
50
+ } catch {
51
+ // Preserve media for malformed historical JSON: it cannot prove an asset is unused.
52
+ return true;
53
+ }
54
+ }
55
+
56
+ async function referencedObjectKeys(env: RuntimeEnv, objectKeys: readonly string[]): Promise<Set<string>> {
57
+ if (objectKeys.length === 0) return new Set();
58
+ const db = database(env);
59
+ const [drafts, revisions] = await Promise.all([
60
+ db.select({ contentJson: documentTranslation.contentJson }).from(documentTranslation),
61
+ db.select({ contentJson: documentRevision.contentJson }).from(documentRevision),
62
+ ]);
63
+ const content = [...drafts, ...revisions].map((row) => row.contentJson);
64
+ return new Set(objectKeys.filter((objectKey) => content.some((json) => contentReferencesMediaObject(json, objectKey))));
65
+ }
66
+
67
+ export async function listMedia(env: RuntimeEnv) {
68
+ const rows = await database(env).select().from(media).orderBy(asc(media.fileName));
69
+ const usedObjectKeys = await referencedObjectKeys(env, rows.map((row) => row.objectKey));
70
+ return rows.map((row) => ({ ...row, url: mediaUrl(env, row.objectKey), isUsed: usedObjectKeys.has(row.objectKey) }));
71
+ }
72
+
73
+ export async function uploadMedia(env: RuntimeEnv, request: Request) {
74
+ const contentLength = Number(request.headers.get('content-length'));
75
+ if (Number.isFinite(contentLength) && contentLength > directUploadMaxBytes + 32 * 1024) {
76
+ throw new InvalidMediaError('Media file exceeds the 10 MiB direct upload limit');
77
+ }
78
+ const form = await request.formData();
79
+ const file = form.get('file');
80
+ if (!(file instanceof File)) throw new InvalidMediaError('A file is required');
81
+ const type = file.type.toLowerCase();
82
+ const extension = mediaTypes.get(type);
83
+ if (!extension) throw new InvalidMediaError('Unsupported media type');
84
+ if (file.size === 0 || file.size > directUploadMaxBytes) throw new InvalidMediaError('Media file exceeds the 10 MiB direct upload limit');
85
+ const header = new Uint8Array(await file.slice(0, 16).arrayBuffer());
86
+ if (!signatureMatches(type, header)) throw new InvalidMediaError('Media bytes do not match the declared type');
87
+
88
+ const id = crypto.randomUUID();
89
+ const objectKey = `media/${id}.${extension}`;
90
+ const now = Date.now();
91
+ await env.MEDIA.put(objectKey, file.stream(), { httpMetadata: { contentType: type } });
92
+ try {
93
+ await database(env).insert(media).values({
94
+ id, objectKey, fileName: file.name.slice(0, 255) || `${id}.${extension}`, contentType: type,
95
+ size: file.size, createdAt: now,
96
+ });
97
+ } catch (error) {
98
+ await env.MEDIA.delete(objectKey);
99
+ throw error;
100
+ }
101
+ return { id, objectKey, fileName: file.name, contentType: type, size: file.size, url: mediaUrl(env, objectKey) };
102
+ }
103
+
104
+ export async function deleteUnusedMedia(env: RuntimeEnv, id: string): Promise<void> {
105
+ const db = database(env);
106
+ const row = await db.select().from(media).where(eq(media.id, id)).get();
107
+ if (!row) throw new MediaNotFoundError();
108
+ if ((await referencedObjectKeys(env, [row.objectKey])).has(row.objectKey)) {
109
+ throw new MediaInUseError('Media is still used by a draft, published page, or revision');
110
+ }
111
+
112
+ await db.delete(media).where(eq(media.id, id));
113
+ try {
114
+ await env.MEDIA.delete(row.objectKey);
115
+ } catch (error) {
116
+ // Restore the row when R2 rejects deletion, leaving the object manageable.
117
+ await db.insert(media).values(row);
118
+ throw error;
119
+ }
120
+ }