@morphemecms/cms 0.0.0-stage → 0.1.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 (1106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -3
  3. package/astro.d.mts +2 -0
  4. package/astro.mjs +162 -0
  5. package/bin/morpheme.mjs +38 -0
  6. package/client.d.ts +10 -0
  7. package/defaults/blockTexts.ts +33 -0
  8. package/defaults/collections.ts +774 -0
  9. package/defaults/colors.ts +125 -0
  10. package/defaults/company.ts +20 -0
  11. package/defaults/editor.ts +178 -0
  12. package/defaults/internalLinks.ts +30 -0
  13. package/defaults/opens.ts +18 -0
  14. package/defaults/popup.tsx +93 -0
  15. package/defaults/privacy.ts +79 -0
  16. package/defaults/rules.ts +450 -0
  17. package/defaults/schema.ts +16 -0
  18. package/defaults/sitedata.ts +129 -0
  19. package/defaults/standard.ts +106 -0
  20. package/defaults/standardPages.ts +87 -0
  21. package/defaults/texts.ts +481 -0
  22. package/demo/README.md +26 -0
  23. package/demo/public/apple-touch-icon.png +0 -0
  24. package/demo/public/editor/apple-touch-icon.png +0 -0
  25. package/demo/public/editor/icon-192.png +0 -0
  26. package/demo/public/editor/icon-512.png +0 -0
  27. package/demo/public/editor/icon-maskable-512.png +0 -0
  28. package/demo/public/favicon.ico +0 -0
  29. package/demo/public/favicon.svg +1 -0
  30. package/demo/public/images/uploads/2026/10/andromeda.webp +0 -0
  31. package/demo/public/images/uploads/2026/10/earth-from-space.webp +0 -0
  32. package/demo/public/images/uploads/2026/10/editor-blog.webp +0 -0
  33. package/demo/public/images/uploads/2026/10/editor-colors.webp +0 -0
  34. package/demo/public/images/uploads/2026/10/editor-home.webp +0 -0
  35. package/demo/public/images/uploads/2026/10/editor-page.webp +0 -0
  36. package/demo/public/images/uploads/2026/10/editor-pages.webp +0 -0
  37. package/demo/public/images/uploads/2026/10/editor-seo.webp +0 -0
  38. package/demo/public/images/uploads/2026/10/editor-start.webp +0 -0
  39. package/demo/public/images/uploads/2026/10/editor-themes.webp +0 -0
  40. package/demo/public/images/uploads/2026/10/moon.webp +0 -0
  41. package/demo/public/images/uploads/2026/10/saturn.webp +0 -0
  42. package/demo/public/logo.svg +1 -0
  43. package/demo/src/content/blog/build-a-page-from-blocks.md +19 -0
  44. package/demo/src/content/blog/colors-everyone-can-read.md +19 -0
  45. package/demo/src/content/blog/edit-your-website-from-your-phone.md +21 -0
  46. package/demo/src/content/blog/every-save-is-a-version.md +21 -0
  47. package/demo/src/content/blog/schedule-a-post.md +17 -0
  48. package/demo/src/content/blog/secure-from-day-one.md +17 -0
  49. package/demo/src/content/blog/seo-without-the-guesswork.md +23 -0
  50. package/demo/src/content/blog/switch-themes-without-losing-anything.md +23 -0
  51. package/demo/src/content/blog/the-app-store.md +21 -0
  52. package/demo/src/content/pages/about.md +22 -0
  53. package/demo/src/content/pages/app-store.md +30 -0
  54. package/demo/src/content/pages/blocks.md +98 -0
  55. package/demo/src/content/pages/blog-page.md +11 -0
  56. package/demo/src/content/pages/contact.md +16 -0
  57. package/demo/src/content/pages/get-started.md +20 -0
  58. package/demo/src/content/pages/home.md +26 -0
  59. package/demo/src/content/pages/page-not-found.md +11 -0
  60. package/demo/src/content/pages/privacy-policy.md +24 -0
  61. package/demo/src/data/blocks.json +25 -0
  62. package/demo/src/data/booking.json +6 -0
  63. package/demo/src/data/categories/apps.json +6 -0
  64. package/demo/src/data/categories/design.json +6 -0
  65. package/demo/src/data/categories/editing.json +6 -0
  66. package/demo/src/data/categories/security.json +6 -0
  67. package/demo/src/data/categories/seo.json +6 -0
  68. package/demo/src/data/colors.json +75 -0
  69. package/demo/src/data/contact.json +8 -0
  70. package/demo/src/data/cookies.json +21 -0
  71. package/demo/src/data/footer.json +27 -0
  72. package/demo/src/data/menu.json +35 -0
  73. package/demo/src/data/mobile-menu.json +25 -0
  74. package/demo/src/data/popups/booking-bar.json +13 -0
  75. package/demo/src/data/popups/try-the-editor.json +16 -0
  76. package/demo/src/data/standard-pages.json +8 -0
  77. package/demo/src/data/testimonials/sample-bakery.json +10 -0
  78. package/demo/src/data/testimonials/sample-clinic.json +10 -0
  79. package/demo/src/data/testimonials/sample-studio.json +10 -0
  80. package/demo/src/site/colors.ts +11 -0
  81. package/demo/src/site/config.ts +19 -0
  82. package/icons/lucide/LICENSE +43 -0
  83. package/icons/lucide/LICENSE-material-design-icons +20 -0
  84. package/icons/lucide/address-book.svg +1 -0
  85. package/icons/lucide/app-window.svg +1 -0
  86. package/icons/lucide/arrow-counter-clockwise.svg +1 -0
  87. package/icons/lucide/arrow-down.svg +1 -0
  88. package/icons/lucide/arrow-left.svg +1 -0
  89. package/icons/lucide/arrow-right.svg +1 -0
  90. package/icons/lucide/arrow-square-out.svg +1 -0
  91. package/icons/lucide/arrow-u-up-left.svg +1 -0
  92. package/icons/lucide/arrow-u-up-right.svg +1 -0
  93. package/icons/lucide/arrow-up-right.svg +1 -0
  94. package/icons/lucide/arrow-up.svg +1 -0
  95. package/icons/lucide/arrows-down-up.svg +1 -0
  96. package/icons/lucide/arrows-in-line-horizontal.svg +1 -0
  97. package/icons/lucide/arrows-out-line-horizontal.svg +1 -0
  98. package/icons/lucide/article.svg +1 -0
  99. package/icons/lucide/bank.svg +1 -0
  100. package/icons/lucide/bell.svg +1 -0
  101. package/icons/lucide/bicycle.svg +1 -0
  102. package/icons/lucide/binoculars.svg +1 -0
  103. package/icons/lucide/book-open-text.svg +1 -0
  104. package/icons/lucide/book-open.svg +1 -0
  105. package/icons/lucide/browser.svg +1 -0
  106. package/icons/lucide/buildings.svg +1 -0
  107. package/icons/lucide/bus.svg +1 -0
  108. package/icons/lucide/calendar-blank.svg +1 -0
  109. package/icons/lucide/calendar-check.svg +1 -0
  110. package/icons/lucide/car.svg +1 -0
  111. package/icons/lucide/cards.svg +1 -0
  112. package/icons/lucide/caret-down.svg +1 -0
  113. package/icons/lucide/caret-left.svg +1 -0
  114. package/icons/lucide/caret-right.svg +1 -0
  115. package/icons/lucide/caret-up.svg +1 -0
  116. package/icons/lucide/certificate.svg +1 -0
  117. package/icons/lucide/chart-line-up.svg +1 -0
  118. package/icons/lucide/chats.svg +1 -0
  119. package/icons/lucide/check-circle.svg +1 -0
  120. package/icons/lucide/check.svg +1 -0
  121. package/icons/lucide/circle-half.svg +1 -0
  122. package/icons/lucide/circle.svg +1 -0
  123. package/icons/lucide/clock-counter-clockwise.svg +1 -0
  124. package/icons/lucide/clock.svg +1 -0
  125. package/icons/lucide/code.svg +1 -0
  126. package/icons/lucide/coffee.svg +1 -0
  127. package/icons/lucide/columns.svg +1 -0
  128. package/icons/lucide/cookie.svg +1 -0
  129. package/icons/lucide/copy.svg +1 -0
  130. package/icons/lucide/credit-card.svg +1 -0
  131. package/icons/lucide/currency-circle-dollar.svg +1 -0
  132. package/icons/lucide/cursor-click.svg +1 -0
  133. package/icons/lucide/database.svg +1 -0
  134. package/icons/lucide/desktop.svg +1 -0
  135. package/icons/lucide/device-mobile.svg +1 -0
  136. package/icons/lucide/door-open.svg +1 -0
  137. package/icons/lucide/download-simple.svg +1 -0
  138. package/icons/lucide/envelope-simple.svg +1 -0
  139. package/icons/lucide/eye-slash.svg +1 -0
  140. package/icons/lucide/eye.svg +1 -0
  141. package/icons/lucide/file-pdf.svg +1 -0
  142. package/icons/lucide/file-text.svg +1 -0
  143. package/icons/lucide/file-video.svg +1 -0
  144. package/icons/lucide/file-zip.svg +1 -0
  145. package/icons/lucide/flower-lotus.svg +1 -0
  146. package/icons/lucide/folder-simple.svg +1 -0
  147. package/icons/lucide/funnel.svg +1 -0
  148. package/icons/lucide/gauge.svg +1 -0
  149. package/icons/lucide/gear.svg +1 -0
  150. package/icons/lucide/github-logo.svg +1 -0
  151. package/icons/lucide/globe.svg +1 -0
  152. package/icons/lucide/google-logo.svg +1 -0
  153. package/icons/lucide/graduation-cap.svg +1 -0
  154. package/icons/lucide/hammer.svg +1 -0
  155. package/icons/lucide/hand-heart.svg +1 -0
  156. package/icons/lucide/handshake.svg +1 -0
  157. package/icons/lucide/hard-drives.svg +1 -0
  158. package/icons/lucide/heart.svg +1 -0
  159. package/icons/lucide/house.svg +1 -0
  160. package/icons/lucide/image-square.svg +1 -0
  161. package/icons/lucide/image.svg +1 -0
  162. package/icons/lucide/info.svg +1 -0
  163. package/icons/lucide/keyboard.svg +1 -0
  164. package/icons/lucide/leaf.svg +1 -0
  165. package/icons/lucide/lifebuoy.svg +1 -0
  166. package/icons/lucide/lightbulb.svg +1 -0
  167. package/icons/lucide/link-break.svg +1 -0
  168. package/icons/lucide/link-simple.svg +1 -0
  169. package/icons/lucide/list-bullets.svg +1 -0
  170. package/icons/lucide/list-checks.svg +1 -0
  171. package/icons/lucide/list-numbers.svg +1 -0
  172. package/icons/lucide/list.svg +1 -0
  173. package/icons/lucide/lock-simple-open.svg +1 -0
  174. package/icons/lucide/lock-simple.svg +1 -0
  175. package/icons/lucide/magnifying-glass.svg +1 -0
  176. package/icons/lucide/map-pin.svg +1 -0
  177. package/icons/lucide/map-trifold.svg +1 -0
  178. package/icons/lucide/medal.svg +1 -0
  179. package/icons/lucide/megaphone.svg +1 -0
  180. package/icons/lucide/minus.svg +1 -0
  181. package/icons/lucide/moon.svg +1 -0
  182. package/icons/lucide/newspaper.svg +1 -0
  183. package/icons/lucide/pack.json +23 -0
  184. package/icons/lucide/paint-brush-broad.svg +1 -0
  185. package/icons/lucide/palette.svg +1 -0
  186. package/icons/lucide/paper-plane-tilt.svg +1 -0
  187. package/icons/lucide/pencil-simple.svg +1 -0
  188. package/icons/lucide/phone.svg +1 -0
  189. package/icons/lucide/play-fill.svg +1 -0
  190. package/icons/lucide/plus.svg +1 -0
  191. package/icons/lucide/prohibit.svg +1 -0
  192. package/icons/lucide/puzzle-piece.svg +1 -0
  193. package/icons/lucide/question.svg +1 -0
  194. package/icons/lucide/quotes.svg +1 -0
  195. package/icons/lucide/rows.svg +1 -0
  196. package/icons/lucide/ruler.svg +1 -0
  197. package/icons/lucide/scan.svg +1 -0
  198. package/icons/lucide/shapes.svg +1 -0
  199. package/icons/lucide/share-network.svg +1 -0
  200. package/icons/lucide/shield-check.svg +1 -0
  201. package/icons/lucide/sidebar-simple.svg +1 -0
  202. package/icons/lucide/sign-out.svg +1 -0
  203. package/icons/lucide/signpost.svg +1 -0
  204. package/icons/lucide/sliders-horizontal.svg +1 -0
  205. package/icons/lucide/smiley.svg +1 -0
  206. package/icons/lucide/sparkle.svg +1 -0
  207. package/icons/lucide/square-half-bottom.svg +1 -0
  208. package/icons/lucide/square.svg +1 -0
  209. package/icons/lucide/squares-four.svg +1 -0
  210. package/icons/lucide/sticker.svg +1 -0
  211. package/icons/lucide/storefront.svg +1 -0
  212. package/icons/lucide/sun.svg +1 -0
  213. package/icons/lucide/swatches.svg +1 -0
  214. package/icons/lucide/tag.svg +1 -0
  215. package/icons/lucide/text-aa.svg +1 -0
  216. package/icons/lucide/text-align-left.svg +1 -0
  217. package/icons/lucide/text-b.svg +1 -0
  218. package/icons/lucide/text-h-one.svg +1 -0
  219. package/icons/lucide/text-h-three.svg +1 -0
  220. package/icons/lucide/text-h-two.svg +1 -0
  221. package/icons/lucide/text-italic.svg +1 -0
  222. package/icons/lucide/text-t.svg +1 -0
  223. package/icons/lucide/ticket.svg +1 -0
  224. package/icons/lucide/toggle-right.svg +1 -0
  225. package/icons/lucide/train.svg +1 -0
  226. package/icons/lucide/trash.svg +1 -0
  227. package/icons/lucide/upload-simple.svg +1 -0
  228. package/icons/lucide/user-circle.svg +1 -0
  229. package/icons/lucide/user.svg +1 -0
  230. package/icons/lucide/users-three.svg +1 -0
  231. package/icons/lucide/users.svg +1 -0
  232. package/icons/lucide/video-camera.svg +1 -0
  233. package/icons/lucide/warning-circle.svg +1 -0
  234. package/icons/lucide/warning.svg +1 -0
  235. package/icons/lucide/wave-sine.svg +1 -0
  236. package/icons/lucide/wheelchair.svg +1 -0
  237. package/icons/lucide/wrench.svg +1 -0
  238. package/icons/lucide/x.svg +1 -0
  239. package/icons/material-symbols/LICENSE +201 -0
  240. package/icons/material-symbols/LICENSE-material-design-icons +20 -0
  241. package/icons/material-symbols/address-book.svg +1 -0
  242. package/icons/material-symbols/app-window.svg +1 -0
  243. package/icons/material-symbols/arrow-counter-clockwise.svg +1 -0
  244. package/icons/material-symbols/arrow-down.svg +1 -0
  245. package/icons/material-symbols/arrow-left.svg +1 -0
  246. package/icons/material-symbols/arrow-right.svg +1 -0
  247. package/icons/material-symbols/arrow-square-out.svg +1 -0
  248. package/icons/material-symbols/arrow-u-up-left.svg +1 -0
  249. package/icons/material-symbols/arrow-u-up-right.svg +1 -0
  250. package/icons/material-symbols/arrow-up-right.svg +1 -0
  251. package/icons/material-symbols/arrow-up.svg +1 -0
  252. package/icons/material-symbols/arrows-down-up.svg +1 -0
  253. package/icons/material-symbols/arrows-in-line-horizontal.svg +1 -0
  254. package/icons/material-symbols/arrows-out-line-horizontal.svg +1 -0
  255. package/icons/material-symbols/article.svg +1 -0
  256. package/icons/material-symbols/bank.svg +1 -0
  257. package/icons/material-symbols/bell.svg +1 -0
  258. package/icons/material-symbols/bicycle.svg +1 -0
  259. package/icons/material-symbols/binoculars.svg +1 -0
  260. package/icons/material-symbols/book-open-text.svg +1 -0
  261. package/icons/material-symbols/book-open.svg +1 -0
  262. package/icons/material-symbols/browser.svg +1 -0
  263. package/icons/material-symbols/buildings.svg +1 -0
  264. package/icons/material-symbols/bus.svg +1 -0
  265. package/icons/material-symbols/calendar-blank.svg +1 -0
  266. package/icons/material-symbols/calendar-check.svg +1 -0
  267. package/icons/material-symbols/car.svg +1 -0
  268. package/icons/material-symbols/cards.svg +1 -0
  269. package/icons/material-symbols/caret-down.svg +1 -0
  270. package/icons/material-symbols/caret-left.svg +1 -0
  271. package/icons/material-symbols/caret-right.svg +1 -0
  272. package/icons/material-symbols/caret-up.svg +1 -0
  273. package/icons/material-symbols/certificate.svg +1 -0
  274. package/icons/material-symbols/chart-line-up.svg +1 -0
  275. package/icons/material-symbols/chats.svg +1 -0
  276. package/icons/material-symbols/check-circle.svg +1 -0
  277. package/icons/material-symbols/check.svg +1 -0
  278. package/icons/material-symbols/circle-half.svg +1 -0
  279. package/icons/material-symbols/circle.svg +1 -0
  280. package/icons/material-symbols/clock-counter-clockwise.svg +1 -0
  281. package/icons/material-symbols/clock.svg +1 -0
  282. package/icons/material-symbols/code.svg +1 -0
  283. package/icons/material-symbols/coffee.svg +1 -0
  284. package/icons/material-symbols/columns.svg +1 -0
  285. package/icons/material-symbols/cookie.svg +1 -0
  286. package/icons/material-symbols/copy.svg +1 -0
  287. package/icons/material-symbols/credit-card.svg +1 -0
  288. package/icons/material-symbols/currency-circle-dollar.svg +1 -0
  289. package/icons/material-symbols/cursor-click.svg +1 -0
  290. package/icons/material-symbols/database.svg +1 -0
  291. package/icons/material-symbols/desktop.svg +1 -0
  292. package/icons/material-symbols/device-mobile.svg +1 -0
  293. package/icons/material-symbols/door-open.svg +1 -0
  294. package/icons/material-symbols/download-simple.svg +1 -0
  295. package/icons/material-symbols/envelope-simple.svg +1 -0
  296. package/icons/material-symbols/eye-slash.svg +1 -0
  297. package/icons/material-symbols/eye.svg +1 -0
  298. package/icons/material-symbols/file-pdf.svg +1 -0
  299. package/icons/material-symbols/file-text.svg +1 -0
  300. package/icons/material-symbols/file-video.svg +1 -0
  301. package/icons/material-symbols/file-zip.svg +1 -0
  302. package/icons/material-symbols/flower-lotus.svg +1 -0
  303. package/icons/material-symbols/folder-simple.svg +1 -0
  304. package/icons/material-symbols/funnel.svg +1 -0
  305. package/icons/material-symbols/gauge.svg +1 -0
  306. package/icons/material-symbols/gear.svg +1 -0
  307. package/icons/material-symbols/github-logo.svg +1 -0
  308. package/icons/material-symbols/globe.svg +1 -0
  309. package/icons/material-symbols/google-logo.svg +1 -0
  310. package/icons/material-symbols/graduation-cap.svg +1 -0
  311. package/icons/material-symbols/hammer.svg +1 -0
  312. package/icons/material-symbols/hand-heart.svg +1 -0
  313. package/icons/material-symbols/handshake.svg +1 -0
  314. package/icons/material-symbols/hard-drives.svg +1 -0
  315. package/icons/material-symbols/heart.svg +1 -0
  316. package/icons/material-symbols/house.svg +1 -0
  317. package/icons/material-symbols/image-square.svg +1 -0
  318. package/icons/material-symbols/image.svg +1 -0
  319. package/icons/material-symbols/info.svg +1 -0
  320. package/icons/material-symbols/keyboard.svg +1 -0
  321. package/icons/material-symbols/leaf.svg +1 -0
  322. package/icons/material-symbols/lifebuoy.svg +1 -0
  323. package/icons/material-symbols/lightbulb.svg +1 -0
  324. package/icons/material-symbols/link-break.svg +1 -0
  325. package/icons/material-symbols/link-simple.svg +1 -0
  326. package/icons/material-symbols/list-bullets.svg +1 -0
  327. package/icons/material-symbols/list-checks.svg +1 -0
  328. package/icons/material-symbols/list-numbers.svg +1 -0
  329. package/icons/material-symbols/list.svg +1 -0
  330. package/icons/material-symbols/lock-simple-open.svg +1 -0
  331. package/icons/material-symbols/lock-simple.svg +1 -0
  332. package/icons/material-symbols/magnifying-glass.svg +1 -0
  333. package/icons/material-symbols/map-pin.svg +1 -0
  334. package/icons/material-symbols/map-trifold.svg +1 -0
  335. package/icons/material-symbols/medal.svg +1 -0
  336. package/icons/material-symbols/megaphone.svg +1 -0
  337. package/icons/material-symbols/minus.svg +1 -0
  338. package/icons/material-symbols/moon.svg +1 -0
  339. package/icons/material-symbols/newspaper.svg +1 -0
  340. package/icons/material-symbols/pack.json +29 -0
  341. package/icons/material-symbols/paint-brush-broad.svg +1 -0
  342. package/icons/material-symbols/palette.svg +1 -0
  343. package/icons/material-symbols/paper-plane-tilt.svg +1 -0
  344. package/icons/material-symbols/pencil-simple.svg +1 -0
  345. package/icons/material-symbols/phone.svg +1 -0
  346. package/icons/material-symbols/play-fill.svg +1 -0
  347. package/icons/material-symbols/plus.svg +1 -0
  348. package/icons/material-symbols/prohibit.svg +1 -0
  349. package/icons/material-symbols/puzzle-piece.svg +1 -0
  350. package/icons/material-symbols/question.svg +1 -0
  351. package/icons/material-symbols/quotes.svg +1 -0
  352. package/icons/material-symbols/rows.svg +1 -0
  353. package/icons/material-symbols/ruler.svg +1 -0
  354. package/icons/material-symbols/scan.svg +1 -0
  355. package/icons/material-symbols/shapes.svg +1 -0
  356. package/icons/material-symbols/share-network.svg +1 -0
  357. package/icons/material-symbols/shield-check.svg +1 -0
  358. package/icons/material-symbols/sidebar-simple.svg +1 -0
  359. package/icons/material-symbols/sign-out.svg +1 -0
  360. package/icons/material-symbols/signpost.svg +1 -0
  361. package/icons/material-symbols/sliders-horizontal.svg +1 -0
  362. package/icons/material-symbols/smiley.svg +1 -0
  363. package/icons/material-symbols/sparkle.svg +1 -0
  364. package/icons/material-symbols/square-half-bottom.svg +1 -0
  365. package/icons/material-symbols/square.svg +1 -0
  366. package/icons/material-symbols/squares-four.svg +1 -0
  367. package/icons/material-symbols/sticker.svg +1 -0
  368. package/icons/material-symbols/storefront.svg +1 -0
  369. package/icons/material-symbols/sun.svg +1 -0
  370. package/icons/material-symbols/swatches.svg +1 -0
  371. package/icons/material-symbols/tag.svg +1 -0
  372. package/icons/material-symbols/text-aa.svg +1 -0
  373. package/icons/material-symbols/text-align-left.svg +1 -0
  374. package/icons/material-symbols/text-b.svg +1 -0
  375. package/icons/material-symbols/text-h-one.svg +1 -0
  376. package/icons/material-symbols/text-h-three.svg +1 -0
  377. package/icons/material-symbols/text-h-two.svg +1 -0
  378. package/icons/material-symbols/text-italic.svg +1 -0
  379. package/icons/material-symbols/text-t.svg +1 -0
  380. package/icons/material-symbols/ticket.svg +1 -0
  381. package/icons/material-symbols/toggle-right.svg +1 -0
  382. package/icons/material-symbols/train.svg +1 -0
  383. package/icons/material-symbols/trash.svg +1 -0
  384. package/icons/material-symbols/upload-simple.svg +1 -0
  385. package/icons/material-symbols/user-circle.svg +1 -0
  386. package/icons/material-symbols/user.svg +1 -0
  387. package/icons/material-symbols/users-three.svg +1 -0
  388. package/icons/material-symbols/users.svg +1 -0
  389. package/icons/material-symbols/video-camera.svg +1 -0
  390. package/icons/material-symbols/warning-circle.svg +1 -0
  391. package/icons/material-symbols/warning.svg +1 -0
  392. package/icons/material-symbols/wave-sine.svg +1 -0
  393. package/icons/material-symbols/wheelchair.svg +1 -0
  394. package/icons/material-symbols/wrench.svg +1 -0
  395. package/icons/material-symbols/x.svg +1 -0
  396. package/icons/phosphor/LICENSE +21 -0
  397. package/icons/phosphor/address-book.svg +1 -0
  398. package/icons/phosphor/app-window.svg +1 -0
  399. package/icons/phosphor/arrow-counter-clockwise.svg +1 -0
  400. package/icons/phosphor/arrow-down.svg +1 -0
  401. package/icons/phosphor/arrow-left.svg +1 -0
  402. package/icons/phosphor/arrow-right.svg +1 -0
  403. package/icons/phosphor/arrow-square-out.svg +1 -0
  404. package/icons/phosphor/arrow-u-up-left.svg +1 -0
  405. package/icons/phosphor/arrow-u-up-right.svg +1 -0
  406. package/icons/phosphor/arrow-up-right.svg +1 -0
  407. package/icons/phosphor/arrow-up.svg +1 -0
  408. package/icons/phosphor/arrows-down-up.svg +1 -0
  409. package/icons/phosphor/arrows-in-line-horizontal.svg +1 -0
  410. package/icons/phosphor/arrows-out-line-horizontal.svg +1 -0
  411. package/icons/phosphor/article.svg +1 -0
  412. package/icons/phosphor/bank.svg +1 -0
  413. package/icons/phosphor/bell.svg +1 -0
  414. package/icons/phosphor/bicycle.svg +1 -0
  415. package/icons/phosphor/binoculars.svg +1 -0
  416. package/icons/phosphor/book-open-text.svg +1 -0
  417. package/icons/phosphor/book-open.svg +1 -0
  418. package/icons/phosphor/browser.svg +1 -0
  419. package/icons/phosphor/buildings.svg +1 -0
  420. package/icons/phosphor/bus.svg +1 -0
  421. package/icons/phosphor/calendar-blank.svg +1 -0
  422. package/icons/phosphor/calendar-check.svg +1 -0
  423. package/icons/phosphor/car.svg +1 -0
  424. package/icons/phosphor/cards.svg +1 -0
  425. package/icons/phosphor/caret-down.svg +1 -0
  426. package/icons/phosphor/caret-left.svg +1 -0
  427. package/icons/phosphor/caret-right.svg +1 -0
  428. package/icons/phosphor/caret-up.svg +1 -0
  429. package/icons/phosphor/certificate.svg +1 -0
  430. package/icons/phosphor/chart-line-up.svg +1 -0
  431. package/icons/phosphor/chats.svg +1 -0
  432. package/icons/phosphor/check-circle.svg +1 -0
  433. package/icons/phosphor/check.svg +1 -0
  434. package/icons/phosphor/circle-half.svg +1 -0
  435. package/icons/phosphor/circle.svg +1 -0
  436. package/icons/phosphor/clock-counter-clockwise.svg +1 -0
  437. package/icons/phosphor/clock.svg +1 -0
  438. package/icons/phosphor/code.svg +1 -0
  439. package/icons/phosphor/coffee.svg +1 -0
  440. package/icons/phosphor/columns.svg +1 -0
  441. package/icons/phosphor/cookie.svg +1 -0
  442. package/icons/phosphor/copy.svg +1 -0
  443. package/icons/phosphor/credit-card.svg +1 -0
  444. package/icons/phosphor/currency-circle-dollar.svg +1 -0
  445. package/icons/phosphor/cursor-click.svg +1 -0
  446. package/icons/phosphor/database.svg +1 -0
  447. package/icons/phosphor/desktop.svg +1 -0
  448. package/icons/phosphor/device-mobile.svg +1 -0
  449. package/icons/phosphor/door-open.svg +1 -0
  450. package/icons/phosphor/download-simple.svg +1 -0
  451. package/icons/phosphor/envelope-simple.svg +1 -0
  452. package/icons/phosphor/eye-slash.svg +1 -0
  453. package/icons/phosphor/eye.svg +1 -0
  454. package/icons/phosphor/file-pdf.svg +1 -0
  455. package/icons/phosphor/file-text.svg +1 -0
  456. package/icons/phosphor/file-video.svg +1 -0
  457. package/icons/phosphor/file-zip.svg +1 -0
  458. package/icons/phosphor/flower-lotus.svg +1 -0
  459. package/icons/phosphor/folder-simple.svg +1 -0
  460. package/icons/phosphor/funnel.svg +1 -0
  461. package/icons/phosphor/gauge.svg +1 -0
  462. package/icons/phosphor/gear.svg +1 -0
  463. package/icons/phosphor/github-logo.svg +1 -0
  464. package/icons/phosphor/globe.svg +1 -0
  465. package/icons/phosphor/google-logo.svg +1 -0
  466. package/icons/phosphor/graduation-cap.svg +1 -0
  467. package/icons/phosphor/hammer.svg +1 -0
  468. package/icons/phosphor/hand-heart.svg +1 -0
  469. package/icons/phosphor/handshake.svg +1 -0
  470. package/icons/phosphor/hard-drives.svg +1 -0
  471. package/icons/phosphor/heart.svg +1 -0
  472. package/icons/phosphor/house.svg +1 -0
  473. package/icons/phosphor/image-square.svg +1 -0
  474. package/icons/phosphor/image.svg +1 -0
  475. package/icons/phosphor/info.svg +1 -0
  476. package/icons/phosphor/keyboard.svg +1 -0
  477. package/icons/phosphor/leaf.svg +1 -0
  478. package/icons/phosphor/lifebuoy.svg +1 -0
  479. package/icons/phosphor/lightbulb.svg +1 -0
  480. package/icons/phosphor/link-break.svg +1 -0
  481. package/icons/phosphor/link-simple.svg +1 -0
  482. package/icons/phosphor/list-bullets.svg +1 -0
  483. package/icons/phosphor/list-checks.svg +1 -0
  484. package/icons/phosphor/list-numbers.svg +1 -0
  485. package/icons/phosphor/list.svg +1 -0
  486. package/icons/phosphor/lock-simple-open.svg +1 -0
  487. package/icons/phosphor/lock-simple.svg +1 -0
  488. package/icons/phosphor/magnifying-glass.svg +1 -0
  489. package/icons/phosphor/map-pin.svg +1 -0
  490. package/icons/phosphor/map-trifold.svg +1 -0
  491. package/icons/phosphor/medal.svg +1 -0
  492. package/icons/phosphor/megaphone.svg +1 -0
  493. package/icons/phosphor/minus.svg +1 -0
  494. package/icons/phosphor/moon.svg +1 -0
  495. package/icons/phosphor/newspaper.svg +1 -0
  496. package/icons/phosphor/pack.json +8 -0
  497. package/icons/phosphor/paint-brush-broad.svg +1 -0
  498. package/icons/phosphor/palette.svg +1 -0
  499. package/icons/phosphor/paper-plane-tilt.svg +1 -0
  500. package/icons/phosphor/pencil-simple.svg +1 -0
  501. package/icons/phosphor/phone.svg +1 -0
  502. package/icons/phosphor/play-fill.svg +1 -0
  503. package/icons/phosphor/plus.svg +1 -0
  504. package/icons/phosphor/prohibit.svg +1 -0
  505. package/icons/phosphor/puzzle-piece.svg +1 -0
  506. package/icons/phosphor/question.svg +1 -0
  507. package/icons/phosphor/quotes.svg +1 -0
  508. package/icons/phosphor/rows.svg +1 -0
  509. package/icons/phosphor/ruler.svg +1 -0
  510. package/icons/phosphor/scan.svg +1 -0
  511. package/icons/phosphor/shapes.svg +1 -0
  512. package/icons/phosphor/share-network.svg +1 -0
  513. package/icons/phosphor/shield-check.svg +1 -0
  514. package/icons/phosphor/sidebar-simple.svg +1 -0
  515. package/icons/phosphor/sign-out.svg +1 -0
  516. package/icons/phosphor/signpost.svg +1 -0
  517. package/icons/phosphor/sliders-horizontal.svg +1 -0
  518. package/icons/phosphor/smiley.svg +1 -0
  519. package/icons/phosphor/sparkle.svg +1 -0
  520. package/icons/phosphor/square-half-bottom.svg +1 -0
  521. package/icons/phosphor/square.svg +1 -0
  522. package/icons/phosphor/squares-four.svg +1 -0
  523. package/icons/phosphor/sticker.svg +1 -0
  524. package/icons/phosphor/storefront.svg +1 -0
  525. package/icons/phosphor/sun.svg +1 -0
  526. package/icons/phosphor/swatches.svg +1 -0
  527. package/icons/phosphor/tag.svg +1 -0
  528. package/icons/phosphor/text-aa.svg +1 -0
  529. package/icons/phosphor/text-align-left.svg +1 -0
  530. package/icons/phosphor/text-b.svg +1 -0
  531. package/icons/phosphor/text-h-one.svg +1 -0
  532. package/icons/phosphor/text-h-three.svg +1 -0
  533. package/icons/phosphor/text-h-two.svg +1 -0
  534. package/icons/phosphor/text-italic.svg +1 -0
  535. package/icons/phosphor/text-t.svg +1 -0
  536. package/icons/phosphor/ticket.svg +1 -0
  537. package/icons/phosphor/toggle-right.svg +1 -0
  538. package/icons/phosphor/train.svg +1 -0
  539. package/icons/phosphor/trash.svg +1 -0
  540. package/icons/phosphor/upload-simple.svg +1 -0
  541. package/icons/phosphor/user-circle.svg +1 -0
  542. package/icons/phosphor/user.svg +1 -0
  543. package/icons/phosphor/users-three.svg +1 -0
  544. package/icons/phosphor/users.svg +1 -0
  545. package/icons/phosphor/video-camera.svg +1 -0
  546. package/icons/phosphor/warning-circle.svg +1 -0
  547. package/icons/phosphor/warning.svg +1 -0
  548. package/icons/phosphor/wave-sine.svg +1 -0
  549. package/icons/phosphor/wheelchair.svg +1 -0
  550. package/icons/phosphor/wrench.svg +1 -0
  551. package/icons/phosphor/x.svg +1 -0
  552. package/lib/apps.d.mts +28 -0
  553. package/lib/apps.mjs +139 -0
  554. package/lib/blocks.d.mts +23 -0
  555. package/lib/blocks.mjs +90 -0
  556. package/lib/colors.ts +86 -0
  557. package/lib/contactBar.ts +18 -0
  558. package/lib/cookieCategories.d.mts +17 -0
  559. package/lib/cookieCategories.mjs +28 -0
  560. package/lib/crop.d.mts +34 -0
  561. package/lib/crop.mjs +83 -0
  562. package/lib/csp.d.mts +22 -0
  563. package/lib/csp.mjs +130 -0
  564. package/lib/cspSources.d.mts +24 -0
  565. package/lib/cspSources.mjs +60 -0
  566. package/lib/dynamic-colors.ts +426 -0
  567. package/lib/git.d.mts +24 -0
  568. package/lib/git.mjs +64 -0
  569. package/lib/hooks.ts +51 -0
  570. package/lib/inline.ts +20 -0
  571. package/lib/license-texts/Apache-2.0.txt +191 -0
  572. package/lib/license-texts/BSD-2-Clause.txt +24 -0
  573. package/lib/license-texts/BSD-3-Clause.txt +28 -0
  574. package/lib/license-texts/ISC.txt +15 -0
  575. package/lib/license-texts/MIT.txt +21 -0
  576. package/lib/licenses.d.mts +190 -0
  577. package/lib/licenses.mjs +683 -0
  578. package/lib/markdown.ts +42 -0
  579. package/lib/motion-schemes.d.mts +2 -0
  580. package/lib/motion-schemes.mjs +5 -0
  581. package/lib/motion.ts +131 -0
  582. package/lib/notice.d.mts +3 -0
  583. package/lib/notice.mjs +34 -0
  584. package/lib/scheduled.d.mts +15 -0
  585. package/lib/scheduled.mjs +37 -0
  586. package/lib/snackbar.ts +149 -0
  587. package/lib/status.d.mts +7 -0
  588. package/lib/status.mjs +18 -0
  589. package/lib/svg.d.mts +22 -0
  590. package/lib/svg.mjs +231 -0
  591. package/lib/text-colors.d.mts +11 -0
  592. package/lib/text-colors.mjs +43 -0
  593. package/lib/video-id.d.mts +14 -0
  594. package/lib/video-id.mjs +72 -0
  595. package/lib/video.d.mts +20 -0
  596. package/lib/video.mjs +91 -0
  597. package/package.json +131 -6
  598. package/pages/404.astro +10 -0
  599. package/pages/[...slug].astro +51 -0
  600. package/pages/_headers.txt +31 -0
  601. package/pages/blog/index.astro +10 -0
  602. package/pages/blog/kategori/[category].astro +18 -0
  603. package/pages/editor/code-images.json.js +25 -0
  604. package/pages/editor/index.astro +75 -0
  605. package/pages/editor/manifest.webmanifest.js +23 -0
  606. package/pages/editor/sw.js +33 -0
  607. package/pages/editor/sw.js.js +4 -0
  608. package/pages/editor/thumbnails.json.js +21 -0
  609. package/pages/editor/version.json.js +22 -0
  610. package/pages/headers.js +4 -0
  611. package/pages/index.astro +10 -0
  612. package/pages/ny.astro +6 -0
  613. package/pages/robots.txt.js +3 -0
  614. package/pages/rss.xml.js +20 -0
  615. package/scripts/app-screenshots.mjs +164 -0
  616. package/scripts/app-workers.mjs +6 -0
  617. package/scripts/bin.mjs +12 -0
  618. package/scripts/built-site.mjs +99 -0
  619. package/scripts/check-consent.mjs +113 -0
  620. package/scripts/check-content.mjs +149 -0
  621. package/scripts/check-demo.mjs +24 -0
  622. package/scripts/check-themes.mjs +45 -0
  623. package/scripts/check-worker.mjs +569 -0
  624. package/scripts/compare-html.mjs +81 -0
  625. package/scripts/demo-site.mjs +42 -0
  626. package/scripts/editor-icons.mjs +56 -0
  627. package/scripts/editor-roundtrip.mjs +134 -0
  628. package/scripts/editor-screens.mjs +101 -0
  629. package/scripts/git-env.mjs +9 -0
  630. package/scripts/icon-packs.mjs +151 -0
  631. package/scripts/link-counts.mjs +22 -0
  632. package/scripts/page-colors.mjs +42 -0
  633. package/scripts/releases.d.mts +10 -0
  634. package/scripts/releases.mjs +80 -0
  635. package/scripts/responsive-images.mjs +94 -0
  636. package/scripts/scheduled-list.mjs +15 -0
  637. package/scripts/theme-screenshots.mjs +88 -0
  638. package/scripts/types.mjs +21 -0
  639. package/scripts/video-thumbnails.mjs +89 -0
  640. package/scripts/worker-modules.mjs +34 -0
  641. package/site/appCookies.d.mts +9 -0
  642. package/site/appCookies.mjs +74 -0
  643. package/site/apps.ts +21 -0
  644. package/site/autolinks.d.mts +11 -0
  645. package/site/autolinks.mjs +200 -0
  646. package/site/blockScripts.ts +31 -0
  647. package/site/categories.ts +16 -0
  648. package/site/consent.ts +26 -0
  649. package/site/content.config.ts +84 -0
  650. package/site/content.ts +104 -0
  651. package/site/cookieList.ts +17 -0
  652. package/site/defineSite.d.mts +142 -0
  653. package/site/defineSite.mjs +136 -0
  654. package/site/directives/interact.d.mts +2 -0
  655. package/site/directives/interact.mjs +31 -0
  656. package/site/editor-version.d.mts +1 -0
  657. package/site/editor-version.mjs +26 -0
  658. package/site/fill.ts +13 -0
  659. package/site/format.ts +11 -0
  660. package/site/home.ts +66 -0
  661. package/site/iconPacks.d.mts +20 -0
  662. package/site/iconPacks.mjs +43 -0
  663. package/site/icons.d.mts +693 -0
  664. package/site/icons.mjs +605 -0
  665. package/site/images.d.mts +43 -0
  666. package/site/images.mjs +157 -0
  667. package/site/motion.ts +13 -0
  668. package/site/noindex.d.mts +6 -0
  669. package/site/noindex.mjs +21 -0
  670. package/site/pageColors.ts +110 -0
  671. package/site/popups.ts +98 -0
  672. package/site/schema.ts +189 -0
  673. package/site/sections.ts +3 -0
  674. package/site/seo.ts +28 -0
  675. package/site/site.ts +76 -0
  676. package/site/siteConfig.d.mts +1 -0
  677. package/site/siteConfig.mjs +8 -0
  678. package/site/siteCsp.d.mts +10 -0
  679. package/site/siteCsp.mjs +75 -0
  680. package/site/siteModules.d.mts +7 -0
  681. package/site/siteModules.mjs +27 -0
  682. package/site/standardPages.ts +59 -0
  683. package/site/testimonials.ts +48 -0
  684. package/site/text.ts +43 -0
  685. package/site/theme.d.mts +83 -0
  686. package/site/theme.mjs +472 -0
  687. package/site/tracking.client.ts +225 -0
  688. package/site/tracking.ts +9 -0
  689. package/src/ActionParts.tsx +205 -0
  690. package/src/Advanced.tsx +152 -0
  691. package/src/App.tsx +904 -0
  692. package/src/AppList.tsx +1345 -0
  693. package/src/BlockDialog.tsx +360 -0
  694. package/src/BlockParts.tsx +652 -0
  695. package/src/BlockSettings.tsx +533 -0
  696. package/src/BrandForm.tsx +164 -0
  697. package/src/Colors.tsx +476 -0
  698. package/src/CommunicationParts.tsx +199 -0
  699. package/src/CompanyForm.tsx +209 -0
  700. package/src/Consents.tsx +175 -0
  701. package/src/ContainmentParts.tsx +185 -0
  702. package/src/Crop.tsx +152 -0
  703. package/src/DataForm.tsx +434 -0
  704. package/src/Deleted.tsx +177 -0
  705. package/src/EntryForm.tsx +740 -0
  706. package/src/FileView.tsx +30 -0
  707. package/src/FormFrame.tsx +237 -0
  708. package/src/Keywords.tsx +225 -0
  709. package/src/Library.tsx +1563 -0
  710. package/src/Licenses.tsx +336 -0
  711. package/src/LinkSheet.tsx +138 -0
  712. package/src/MarkdownEditor.tsx +1075 -0
  713. package/src/Media.tsx +677 -0
  714. package/src/NavigationParts.tsx +350 -0
  715. package/src/NavigationRestParts.tsx +247 -0
  716. package/src/Notifications.tsx +59 -0
  717. package/src/PagePicker.tsx +136 -0
  718. package/src/Parts.tsx +991 -0
  719. package/src/PartsShown.tsx +168 -0
  720. package/src/PickerParts.tsx +183 -0
  721. package/src/Redirects.tsx +450 -0
  722. package/src/Screen.tsx +184 -0
  723. package/src/SectionsField.tsx +65 -0
  724. package/src/SelectionParts.tsx +134 -0
  725. package/src/SeoScores.tsx +184 -0
  726. package/src/Sheet.tsx +35 -0
  727. package/src/ThemeStore.tsx +376 -0
  728. package/src/Tokens.tsx +346 -0
  729. package/src/Versions.tsx +214 -0
  730. package/src/addresses.ts +75 -0
  731. package/src/allIcons.ts +7 -0
  732. package/src/appFiles.ts +29 -0
  733. package/src/apps.ts +281 -0
  734. package/src/ask.tsx +123 -0
  735. package/src/blocks.ts +251 -0
  736. package/src/brand.ts +153 -0
  737. package/src/cache.ts +42 -0
  738. package/src/check.ts +285 -0
  739. package/src/checkSite.ts +256 -0
  740. package/src/consents.ts +26 -0
  741. package/src/cookieList.ts +70 -0
  742. package/src/countries.ts +59 -0
  743. package/src/deploy.ts +155 -0
  744. package/src/editor.css +602 -0
  745. package/src/examples.ts +85 -0
  746. package/src/fields.tsx +942 -0
  747. package/src/frontmatter.d.mts +6 -0
  748. package/src/frontmatter.mjs +42 -0
  749. package/src/github.ts +376 -0
  750. package/src/i18n/da-DK/advanced.ts +28 -0
  751. package/src/i18n/da-DK/app.ts +71 -0
  752. package/src/i18n/da-DK/apps.ts +90 -0
  753. package/src/i18n/da-DK/blockSettings.ts +38 -0
  754. package/src/i18n/da-DK/blocks.ts +408 -0
  755. package/src/i18n/da-DK/check.ts +25 -0
  756. package/src/i18n/da-DK/colors.ts +64 -0
  757. package/src/i18n/da-DK/common.ts +118 -0
  758. package/src/i18n/da-DK/company.ts +24 -0
  759. package/src/i18n/da-DK/consents.ts +23 -0
  760. package/src/i18n/da-DK/forms.ts +116 -0
  761. package/src/i18n/da-DK/history.ts +35 -0
  762. package/src/i18n/da-DK/index.ts +49 -0
  763. package/src/i18n/da-DK/keywords.ts +19 -0
  764. package/src/i18n/da-DK/library.ts +135 -0
  765. package/src/i18n/da-DK/licenses.ts +25 -0
  766. package/src/i18n/da-DK/media.ts +79 -0
  767. package/src/i18n/da-DK/parts.ts +696 -0
  768. package/src/i18n/da-DK/preview.ts +13 -0
  769. package/src/i18n/da-DK/redirects.ts +51 -0
  770. package/src/i18n/da-DK/seo.ts +20 -0
  771. package/src/i18n/da-DK/text.ts +35 -0
  772. package/src/i18n/da-DK/theme.ts +41 -0
  773. package/src/i18n/en-US/advanced.ts +32 -0
  774. package/src/i18n/en-US/app.ts +84 -0
  775. package/src/i18n/en-US/apps.ts +134 -0
  776. package/src/i18n/en-US/blockSettings.ts +48 -0
  777. package/src/i18n/en-US/blocks.ts +441 -0
  778. package/src/i18n/en-US/check.ts +31 -0
  779. package/src/i18n/en-US/colors.ts +82 -0
  780. package/src/i18n/en-US/common.ts +144 -0
  781. package/src/i18n/en-US/company.ts +25 -0
  782. package/src/i18n/en-US/consents.ts +25 -0
  783. package/src/i18n/en-US/forms.ts +145 -0
  784. package/src/i18n/en-US/history.ts +43 -0
  785. package/src/i18n/en-US/index.ts +51 -0
  786. package/src/i18n/en-US/keywords.ts +24 -0
  787. package/src/i18n/en-US/library.ts +176 -0
  788. package/src/i18n/en-US/licenses.ts +36 -0
  789. package/src/i18n/en-US/media.ts +80 -0
  790. package/src/i18n/en-US/parts.ts +718 -0
  791. package/src/i18n/en-US/preview.ts +15 -0
  792. package/src/i18n/en-US/redirects.ts +66 -0
  793. package/src/i18n/en-US/seo.ts +26 -0
  794. package/src/i18n/en-US/text.ts +41 -0
  795. package/src/i18n/en-US/theme.ts +41 -0
  796. package/src/i18n/index.ts +124 -0
  797. package/src/icon.tsx +160 -0
  798. package/src/iconPacks.ts +89 -0
  799. package/src/images.ts +163 -0
  800. package/src/licenses.ts +79 -0
  801. package/src/markdown.d.mts +12 -0
  802. package/src/markdown.mjs +204 -0
  803. package/src/nav.ts +68 -0
  804. package/src/notFound.ts +28 -0
  805. package/src/notifications.ts +223 -0
  806. package/src/pagePreview.tsx +204 -0
  807. package/src/r2Caching.ts +55 -0
  808. package/src/redirects.ts +149 -0
  809. package/src/registry.tsx +188 -0
  810. package/src/schema.ts +417 -0
  811. package/src/sections.d.mts +45 -0
  812. package/src/sections.mjs +303 -0
  813. package/src/seo.ts +333 -0
  814. package/src/shared.ts +44 -0
  815. package/src/sidebar.ts +31 -0
  816. package/src/site.ts +317 -0
  817. package/src/standard/Block.astro +19 -0
  818. package/src/standard/BlockSite.astro +18 -0
  819. package/src/standard/accordion/index.tsx +47 -0
  820. package/src/standard/accordion/load.ts +23 -0
  821. package/src/standard/accordion/shared.ts +15 -0
  822. package/src/standard/accordion/view.ts +32 -0
  823. package/src/standard/buttons/fields.ts +98 -0
  824. package/src/standard/buttons/index.tsx +23 -0
  825. package/src/standard/buttons/load.ts +34 -0
  826. package/src/standard/buttons/shared.ts +12 -0
  827. package/src/standard/buttons/view.ts +30 -0
  828. package/src/standard/cards/index.tsx +71 -0
  829. package/src/standard/cards/load.ts +17 -0
  830. package/src/standard/cards/shared.ts +27 -0
  831. package/src/standard/cards/view.ts +29 -0
  832. package/src/standard/checklist/index.tsx +27 -0
  833. package/src/standard/checklist/load.ts +5 -0
  834. package/src/standard/checklist/view.ts +13 -0
  835. package/src/standard/cookies/index.tsx +17 -0
  836. package/src/standard/cookies/list.ts +56 -0
  837. package/src/standard/cookies/load.ts +11 -0
  838. package/src/standard/cookies/shared.ts +20 -0
  839. package/src/standard/cookies/view.ts +28 -0
  840. package/src/standard/faq/index.tsx +29 -0
  841. package/src/standard/faq/load.ts +14 -0
  842. package/src/standard/faq/view.ts +21 -0
  843. package/src/standard/head.ts +23 -0
  844. package/src/standard/hero/fields.ts +48 -0
  845. package/src/standard/hero/index.tsx +57 -0
  846. package/src/standard/hero/load.ts +23 -0
  847. package/src/standard/hero/view.ts +38 -0
  848. package/src/standard/links/index.tsx +41 -0
  849. package/src/standard/links/load.ts +17 -0
  850. package/src/standard/links/view.ts +25 -0
  851. package/src/standard/list/index.tsx +50 -0
  852. package/src/standard/list/load.ts +5 -0
  853. package/src/standard/list/view.ts +18 -0
  854. package/src/standard/load.ts +7 -0
  855. package/src/standard/notice/index.tsx +30 -0
  856. package/src/standard/notice/load.ts +16 -0
  857. package/src/standard/notice/view.ts +10 -0
  858. package/src/standard/points/index.tsx +42 -0
  859. package/src/standard/points/load.ts +5 -0
  860. package/src/standard/points/view.ts +12 -0
  861. package/src/standard/posts/index.tsx +132 -0
  862. package/src/standard/posts/load.ts +36 -0
  863. package/src/standard/posts/shared.ts +21 -0
  864. package/src/standard/posts/view.ts +63 -0
  865. package/src/standard/pricecards/index.tsx +57 -0
  866. package/src/standard/pricecards/load.ts +11 -0
  867. package/src/standard/pricecards/view.ts +50 -0
  868. package/src/standard/sharedTexts.ts +6 -0
  869. package/src/standard/site.ts +124 -0
  870. package/src/standard/siteLooks.ts +19 -0
  871. package/src/standard/sites.ts +13 -0
  872. package/src/standard/steps/index.tsx +28 -0
  873. package/src/standard/steps/load.ts +5 -0
  874. package/src/standard/steps/view.ts +11 -0
  875. package/src/standard/testimonials/index.tsx +87 -0
  876. package/src/standard/testimonials/list.ts +43 -0
  877. package/src/standard/testimonials/load.ts +58 -0
  878. package/src/standard/testimonials/shared.ts +13 -0
  879. package/src/standard/testimonials/view.ts +76 -0
  880. package/src/standard/themeBlocks.ts +13 -0
  881. package/src/standard/video/index.tsx +23 -0
  882. package/src/standard/video/load.ts +10 -0
  883. package/src/standard/video/view.ts +42 -0
  884. package/src/standard/view.tsx +105 -0
  885. package/src/standard/views.ts +37 -0
  886. package/src/store.ts +499 -0
  887. package/src/theme.ts +90 -0
  888. package/src/themeUi.ts +97 -0
  889. package/src/themes.ts +91 -0
  890. package/src/ui.tsx +3402 -0
  891. package/src/virtual.d.ts +5 -0
  892. package/src/when.ts +22 -0
  893. package/starter/.githooks/pre-push +4 -0
  894. package/starter/.nvmrc +1 -0
  895. package/starter/.prettierignore +11 -0
  896. package/starter/.prettierrc.json +7 -0
  897. package/starter/README.md +26 -0
  898. package/starter/astro.config.mjs +5 -0
  899. package/starter/package.json +67 -0
  900. package/starter/public/_redirects +1 -0
  901. package/starter/public/apple-touch-icon.png +0 -0
  902. package/starter/public/editor/apple-touch-icon.png +0 -0
  903. package/starter/public/editor/icon-192.png +0 -0
  904. package/starter/public/editor/icon-512.png +0 -0
  905. package/starter/public/editor/icon-maskable-512.png +0 -0
  906. package/starter/public/favicon.ico +0 -0
  907. package/starter/public/favicon.svg +1 -0
  908. package/starter/public/logo.svg +1 -0
  909. package/starter/src/content/pages/blog.md +8 -0
  910. package/starter/src/content/pages/forside.md +9 -0
  911. package/starter/src/content/pages/kontakt.md +7 -0
  912. package/starter/src/content/pages/privatlivspolitik.md +9 -0
  913. package/starter/src/content/pages/siden-findes-ikke.md +7 -0
  914. package/starter/src/content.config.ts +2 -0
  915. package/starter/src/data/apps.json +1 -0
  916. package/starter/src/data/block-settings.json +3 -0
  917. package/starter/src/data/blocks.json +1 -0
  918. package/starter/src/data/booking.json +6 -0
  919. package/starter/src/data/colors.json +70 -0
  920. package/starter/src/data/contact.json +8 -0
  921. package/starter/src/data/cookies.json +21 -0
  922. package/starter/src/data/csp.json +1 -0
  923. package/starter/src/data/footer.json +15 -0
  924. package/starter/src/data/internal-links.json +4 -0
  925. package/starter/src/data/menu.json +4 -0
  926. package/starter/src/data/mobile-menu.json +4 -0
  927. package/starter/src/data/old-addresses.json +1 -0
  928. package/starter/src/data/redirects.json +1 -0
  929. package/starter/src/data/standard-pages.json +8 -0
  930. package/starter/src/data/theme.json +1 -0
  931. package/starter/src/env.d.ts +2 -0
  932. package/starter/src/site/config.ts +13 -0
  933. package/starter/src/site/worker.js +14 -0
  934. package/starter/tsconfig.json +17 -0
  935. package/starter/worker/apps.js +6 -0
  936. package/starter/wrangler.jsonc +39 -0
  937. package/theme/LICENSE +21 -0
  938. package/theme/blocks/ArrowLink.tsx +14 -0
  939. package/theme/blocks/BlockHead.tsx +26 -0
  940. package/theme/blocks/Panel.tsx +32 -0
  941. package/theme/blocks/Person.tsx +26 -0
  942. package/theme/blocks/accordion/PageRow.tsx +25 -0
  943. package/theme/blocks/accordion/View.tsx +17 -0
  944. package/theme/blocks/buttons/Buttons.tsx +28 -0
  945. package/theme/blocks/buttons/View.tsx +12 -0
  946. package/theme/blocks/cards/LinkCards.tsx +72 -0
  947. package/theme/blocks/cards/View.tsx +20 -0
  948. package/theme/blocks/checklist/Checks.tsx +15 -0
  949. package/theme/blocks/checklist/View.tsx +14 -0
  950. package/theme/blocks/cookies/View.tsx +36 -0
  951. package/theme/blocks/faq/Accordion.tsx +34 -0
  952. package/theme/blocks/faq/View.tsx +16 -0
  953. package/theme/blocks/faq/script.ts +56 -0
  954. package/theme/blocks/hero/View.tsx +44 -0
  955. package/theme/blocks/links/LinkGroups.tsx +24 -0
  956. package/theme/blocks/links/View.tsx +17 -0
  957. package/theme/blocks/list/Rows.tsx +24 -0
  958. package/theme/blocks/list/View.tsx +14 -0
  959. package/theme/blocks/notice/View.tsx +13 -0
  960. package/theme/blocks/points/PointList.tsx +34 -0
  961. package/theme/blocks/points/View.tsx +14 -0
  962. package/theme/blocks/posts/View.tsx +26 -0
  963. package/theme/blocks/pricecards/View.tsx +40 -0
  964. package/theme/blocks/steps/StepList.tsx +23 -0
  965. package/theme/blocks/steps/View.tsx +14 -0
  966. package/theme/blocks/testimonials/List.tsx +46 -0
  967. package/theme/blocks/testimonials/Slider.tsx +44 -0
  968. package/theme/blocks/testimonials/Testimonial.tsx +124 -0
  969. package/theme/blocks/testimonials/View.tsx +21 -0
  970. package/theme/blocks/testimonials/script.ts +52 -0
  971. package/theme/blocks/video/View.tsx +41 -0
  972. package/theme/components/ActionBar.astro +63 -0
  973. package/theme/components/AppSlot.astro +21 -0
  974. package/theme/components/ArrowLink.tsx +21 -0
  975. package/theme/components/BlogIndex.astro +48 -0
  976. package/theme/components/BlogPosts.astro +18 -0
  977. package/theme/components/BlogPosts.tsx +45 -0
  978. package/theme/components/BookButton.tsx +22 -0
  979. package/theme/components/Container.astro +11 -0
  980. package/theme/components/CookieConsent.astro +121 -0
  981. package/theme/components/EndBlock.astro +21 -0
  982. package/theme/components/FontPreload.astro +6 -0
  983. package/theme/components/Footer.astro +85 -0
  984. package/theme/components/Header.astro +186 -0
  985. package/theme/components/Icon.astro +16 -0
  986. package/theme/components/Img.astro +29 -0
  987. package/theme/components/Img.tsx +33 -0
  988. package/theme/components/Inline.astro +11 -0
  989. package/theme/components/Logo.astro +23 -0
  990. package/theme/components/LogoMark.astro +10 -0
  991. package/theme/components/Markdown.astro +17 -0
  992. package/theme/components/MobileMenu.tsx +224 -0
  993. package/theme/components/MobileNav.astro +33 -0
  994. package/theme/components/PageHeader.astro +22 -0
  995. package/theme/components/PageSection.astro +24 -0
  996. package/theme/components/PhoneButton.tsx +11 -0
  997. package/theme/components/Popup.astro +145 -0
  998. package/theme/components/PopupButton.tsx +24 -0
  999. package/theme/components/PopupView.tsx +48 -0
  1000. package/theme/components/PostList.tsx +58 -0
  1001. package/theme/components/PostMeta.tsx +17 -0
  1002. package/theme/components/RichText.astro +28 -0
  1003. package/theme/components/Section.astro +29 -0
  1004. package/theme/components/SectionHeading.tsx +8 -0
  1005. package/theme/components/TextSection.astro +16 -0
  1006. package/theme/components/home/PageBlock.astro +19 -0
  1007. package/theme/components/home/Text.astro +13 -0
  1008. package/theme/components/loadPopup.ts +22 -0
  1009. package/theme/components/loadPosts.ts +16 -0
  1010. package/theme/components/posts.ts +15 -0
  1011. package/theme/editor/looks.mjs +7 -0
  1012. package/theme/editor/previews.tsx +56 -0
  1013. package/theme/layouts/Base.astro +273 -0
  1014. package/theme/layouts/PageLayout.astro +126 -0
  1015. package/theme/layouts/PostLayout.astro +137 -0
  1016. package/theme/screenshots/1-front.jpg +0 -0
  1017. package/theme/screenshots/2-front-dark.jpg +0 -0
  1018. package/theme/screenshots/3-blog.jpg +0 -0
  1019. package/theme/screenshots/4-contact.jpg +0 -0
  1020. package/theme/scripts/motion.ts +29 -0
  1021. package/theme/styles/base.css +320 -0
  1022. package/theme/styles/blocks.css +1808 -0
  1023. package/theme/styles/components.css +206 -0
  1024. package/theme/styles/global.css +1302 -0
  1025. package/theme/styles/ui.css +6544 -0
  1026. package/theme/templates/Blog.astro +38 -0
  1027. package/theme/templates/Category.astro +19 -0
  1028. package/theme/templates/Home.astro +36 -0
  1029. package/theme/templates/New.astro +68 -0
  1030. package/theme/templates/NotFound.astro +117 -0
  1031. package/theme/theme.json +28 -0
  1032. package/theme/ui/Badge.tsx +45 -0
  1033. package/theme/ui/BottomSheet.tsx +148 -0
  1034. package/theme/ui/Button.tsx +79 -0
  1035. package/theme/ui/ButtonGroup.tsx +31 -0
  1036. package/theme/ui/Card.tsx +45 -0
  1037. package/theme/ui/Carousel.tsx +124 -0
  1038. package/theme/ui/Checkbox.tsx +59 -0
  1039. package/theme/ui/Chip.tsx +101 -0
  1040. package/theme/ui/ChipSet.tsx +27 -0
  1041. package/theme/ui/DatePicker.tsx +149 -0
  1042. package/theme/ui/DatePickerContainer.tsx +316 -0
  1043. package/theme/ui/Dialog.tsx +100 -0
  1044. package/theme/ui/Divider.tsx +27 -0
  1045. package/theme/ui/ExposedDropdownMenu.tsx +77 -0
  1046. package/theme/ui/Fab.tsx +88 -0
  1047. package/theme/ui/FabMenu.tsx +67 -0
  1048. package/theme/ui/IconButton.tsx +78 -0
  1049. package/theme/ui/List.tsx +287 -0
  1050. package/theme/ui/LoadingIndicator.tsx +29 -0
  1051. package/theme/ui/Menu.tsx +278 -0
  1052. package/theme/ui/NavigationBar.tsx +62 -0
  1053. package/theme/ui/NavigationDrawer.tsx +80 -0
  1054. package/theme/ui/NavigationRail.tsx +154 -0
  1055. package/theme/ui/ProgressIndicator.tsx +162 -0
  1056. package/theme/ui/RadioButton.tsx +50 -0
  1057. package/theme/ui/Search.tsx +129 -0
  1058. package/theme/ui/SegmentedButton.tsx +49 -0
  1059. package/theme/ui/SideSheet.tsx +54 -0
  1060. package/theme/ui/Slider.tsx +155 -0
  1061. package/theme/ui/Snackbar.tsx +67 -0
  1062. package/theme/ui/SplitButton.tsx +67 -0
  1063. package/theme/ui/Switch.tsx +60 -0
  1064. package/theme/ui/Tabs.tsx +114 -0
  1065. package/theme/ui/TextField.tsx +278 -0
  1066. package/theme/ui/TimePicker.tsx +128 -0
  1067. package/theme/ui/TimePickerContainer.tsx +234 -0
  1068. package/theme/ui/Toolbar.tsx +67 -0
  1069. package/theme/ui/Tooltip.tsx +58 -0
  1070. package/theme/ui/TopAppBar.tsx +77 -0
  1071. package/theme/ui/bottomSheet.script.ts +8 -0
  1072. package/theme/ui/bottomSheet.ts +211 -0
  1073. package/theme/ui/carousel.script.ts +28 -0
  1074. package/theme/ui/carouselLayout.ts +229 -0
  1075. package/theme/ui/checkbox.script.ts +8 -0
  1076. package/theme/ui/datePicker.script.ts +302 -0
  1077. package/theme/ui/dates.ts +185 -0
  1078. package/theme/ui/dialog.ts +48 -0
  1079. package/theme/ui/dropdownMenu.script.ts +166 -0
  1080. package/theme/ui/fabMenu.script.ts +57 -0
  1081. package/theme/ui/navigationRail.script.ts +47 -0
  1082. package/theme/ui/ripple.script.ts +159 -0
  1083. package/theme/ui/search.script.ts +92 -0
  1084. package/theme/ui/sideSheet.ts +48 -0
  1085. package/theme/ui/slider.script.ts +12 -0
  1086. package/theme/ui/sliderUpdate.ts +52 -0
  1087. package/theme/ui/snackbar.script.ts +16 -0
  1088. package/theme/ui/splitButton.script.ts +63 -0
  1089. package/theme/ui/tabs.script.ts +48 -0
  1090. package/theme/ui/textField.script.ts +17 -0
  1091. package/theme/ui/textFieldUpdate.ts +44 -0
  1092. package/theme/ui/timePicker.script.ts +248 -0
  1093. package/theme/ui/toolbar.script.ts +43 -0
  1094. package/theme/ui/tooltip.script.ts +68 -0
  1095. package/worker/consent.js +141 -0
  1096. package/worker/editor-github.js +167 -0
  1097. package/worker/editor-host.js +74 -0
  1098. package/worker/files.js +401 -0
  1099. package/worker/github.js +36 -0
  1100. package/worker/index.js +184 -0
  1101. package/worker/not-found.js +97 -0
  1102. package/worker/oauth.js +95 -0
  1103. package/worker/publish.js +52 -0
  1104. package/worker/push.js +270 -0
  1105. package/worker/r2-status.js +50 -0
  1106. package/worker/session.js +58 -0
package/src/check.ts ADDED
@@ -0,0 +1,285 @@
1
+ // The rules a value must keep to before it is saved, from its fields (collections.ts): what must be
2
+ // filled in, the form of a text (a pattern, an address, a number in pairs), the kind of each value
3
+ // and that a choice is one of the options. The editor checks with them before it saves (fields.tsx
4
+ // and the forms), and the site before it is built (packages/editor/scripts/check-content.mjs), so what the editor
5
+ // would not save does not reach the site either. Also how a value is saved (clean) and a new one
6
+ // (newValue). No React here, so the build can use it.
7
+ import type { Field, Template } from './schema';
8
+ import { addressOf } from './addresses';
9
+ import { codeAt, regionOf } from './countries';
10
+ import { blocksIn } from '../lib/blocks.mjs';
11
+ import { t } from './i18n';
12
+
13
+ type Item = Record<string, unknown>;
14
+
15
+ /** The fields and value an object's fields are in: an item of a list is in the object with the list
16
+ * (a block's cards in the block), which may be in another. */
17
+ export type Around = { fields: Field[]; value: Record<string, unknown> | undefined; around?: Around };
18
+
19
+ /** Whether a field is in use: always, or (Field.only) while the field it depends on has one of the
20
+ * values (its default when it has none yet) and is in use itself (a pop-up's delay, by when it opens,
21
+ * which a kind without it does not have). That field is next to it or, for a field of an item in a
22
+ * list, next to the list (`around`): a card's icon by the block's design. */
23
+ export function inUse(field: Field, fields: Field[], value: Record<string, unknown> | undefined, around?: Around): boolean {
24
+ if (!field.only) return true;
25
+ const name = field.only.field;
26
+ let at: Around = { fields, value, around };
27
+ while (at.around && !at.fields.some((f) => f.name === name)) at = at.around;
28
+ const by = at.fields.find((f) => f.name === name);
29
+ return (
30
+ field.only.is.includes(String(at.value?.[name] ?? by?.default ?? '')) &&
31
+ (!by || by === field || inUse(by, at.fields, at.value, at.around))
32
+ );
33
+ }
34
+
35
+ // The block an item of a list of blocks is (undefined for a type the editor does not know).
36
+ const blockOf = (field: Field, item: unknown) => field.blocks!.find((b) => b.type === (item as Item)?.type);
37
+
38
+ // The kind of value a field holds, and what is said when it holds another (a file edited by hand).
39
+ const kindOf = (f: Field) =>
40
+ f.type === 'list' || f.type === 'blocks' || f.multiple
41
+ ? 'list'
42
+ : f.type === 'object'
43
+ ? 'object'
44
+ : f.type === 'number'
45
+ ? 'number'
46
+ : f.type === 'boolean'
47
+ ? 'boolean'
48
+ : 'text';
49
+ function ofItsKind(f: Field, v: unknown): boolean {
50
+ // Only shown, never edited here: whatever the file has.
51
+ if (f.type === 'readonly') return true;
52
+ switch (kindOf(f)) {
53
+ case 'list':
54
+ return Array.isArray(v);
55
+ case 'object':
56
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
57
+ case 'number':
58
+ return typeof v === 'number' && Number.isFinite(v);
59
+ case 'boolean':
60
+ return typeof v === 'boolean';
61
+ default:
62
+ return typeof v === 'string';
63
+ }
64
+ }
65
+
66
+ // A new, empty item of a list.
67
+ export const newItem = (field: Field) => (field.field ? '' : newValue(field.fields!));
68
+
69
+ /** A saved value with the defaults of the fields it has no value for (a field added after it was
70
+ * saved, the pop-up's "Rullet ned"), after its own values. */
71
+ export const withDefaults = (fields: Field[], value: Record<string, unknown>): Record<string, unknown> => ({
72
+ ...value,
73
+ ...Object.fromEntries(fields.filter((f) => f.default !== undefined && value?.[f.name] === undefined).map((f) => [f.name, f.default])),
74
+ });
75
+
76
+ /** A saved entry in a form: its own values, and for a field it has none for, what that means (Field.missing), else
77
+ * its value in a new entry (newValue). */
78
+ export const savedValue = (fields: Field[], value: Record<string, unknown>): Record<string, unknown> => ({
79
+ ...newValue(fields),
80
+ ...Object.fromEntries(fields.filter((f) => f.missing !== undefined).map((f) => [f.name, f.missing])),
81
+ ...value,
82
+ });
83
+
84
+ // An empty entry for a list of objects or a new file: the fields' defaults, lists empty.
85
+ export function newValue(fields: Field[]): Record<string, unknown> {
86
+ return Object.fromEntries(
87
+ fields.map((f) => [
88
+ f.name,
89
+ f.default ??
90
+ (f.type === 'list' || f.type === 'blocks' || f.multiple
91
+ ? []
92
+ : f.type === 'object'
93
+ ? newValue(f.fields!)
94
+ : f.type === 'boolean'
95
+ ? false
96
+ : ''),
97
+ ]),
98
+ );
99
+ }
100
+
101
+ // A number typed with or without spaces (or dots, dashes, the site's own country code before a phone
102
+ // number) as its digits in pairs, "30 64 16 12"; a text that is not only digits as it was typed.
103
+ // abroad: a phone number with another country code, which is not counted or taken off.
104
+ function digitsOf(field: Field, text: string, abroad = false) {
105
+ let digits = text.replace(/[\s.\-()]/g, '').replace(/^\+/, '');
106
+ const own = !abroad && field.phone?.slice(1);
107
+ if (own && typeof field.digitsInPairs === 'number' && digits.length === field.digitsInPairs + own.length && digits.startsWith(own))
108
+ digits = digits.slice(own.length);
109
+ return digits;
110
+ }
111
+ function pairsOf(field: Field, text: string) {
112
+ const digits = digitsOf(field, text);
113
+ return /^\d+$/.test(digits) ? digits.match(/\d{1,2}/g)!.join(' ') : text;
114
+ }
115
+
116
+ /** A phone number's country code and the number after it (Field.phone): one without a code, or with
117
+ * the site's own, is in the site's own country; one typed with another (+46 or 0046, with a space
118
+ * after it or not) has it taken off. */
119
+ export function phoneParts(field: Field, text: string): [code: string, number: string] {
120
+ const typed = text.trim();
121
+ const m = typed.match(/^(?:\+|00)(\d*)(.*)$/s);
122
+ if (!m) return [field.phone!, typed];
123
+ // Written "+46 70 12 34 56", the code is the digits before the space; together, the longest that is one.
124
+ const whole = regionOf(`+${m[1]}`) ? `+${m[1]}` : undefined;
125
+ const code = (m[2] && whole) || codeAt(m[1]) || whole;
126
+ if (!code) return [field.phone!, typed];
127
+ return [code, `${m[1].slice(code.length - 1)}${m[2]}`.trim()];
128
+ }
129
+
130
+ /** A phone number as it is saved: with the site's own country code, the number alone; with another,
131
+ * the code before it. A number typed with a code keeps that one. */
132
+ export function phoneText(field: Field, code: string, number: string) {
133
+ const typed = number.trim();
134
+ return !typed || /^(\+|00)/.test(typed) || code === field.phone ? number : `${code} ${number}`;
135
+ }
136
+
137
+ // A phone number in another country is written as it was typed (its groups differ from country to
138
+ // country), only with one space between them.
139
+ export function inPairs(field: Field, text: string) {
140
+ if (!field.phone) return pairsOf(field, text);
141
+ const [code, number] = phoneParts(field, text);
142
+ return phoneText(field, code, code === field.phone ? pairsOf(field, number) : number.replace(/\s+/g, ' '));
143
+ }
144
+
145
+ // What is wrong with a text that must have a form (a pattern, a number in pairs), or null.
146
+ export function problemOf(field: Field, typed: string): string | null {
147
+ const text = typed.trim();
148
+ if (!text) return null;
149
+ if (field.pattern && !new RegExp(field.pattern[0]).test(text)) return field.pattern[1];
150
+ if (field.address && !addressOf(text, field.address)) return t.check.address;
151
+ if (field.digitsInPairs) {
152
+ // A phone number in another country: only digits, however many it has there.
153
+ const [code, number] = field.phone ? phoneParts(field, text) : [field.phone, text];
154
+ const abroad = code !== field.phone;
155
+ const digits = digitsOf(field, number, abroad);
156
+ if (!/^\d+$/.test(digits)) return t.check.onlyDigits;
157
+ if (typeof field.digitsInPairs === 'number' && !abroad && digits.length !== field.digitsInPairs)
158
+ return t.check.digits(field.digitsInPairs);
159
+ }
160
+ return null;
161
+ }
162
+
163
+ /** The first problem with a value: the message for the person editing, and the field it is about, as
164
+ * the names and item numbers to it joined with dots ("items.0.title"), so it can be shown there. */
165
+ export type Problem = { text: string; path: string };
166
+
167
+ // The first problem with a value, as a message for the person editing, or null.
168
+ export function check(fields: Field[], value: Record<string, unknown>, where = '', around?: Around): string | null {
169
+ return problemAt(fields, value, where, around)?.text ?? null;
170
+ }
171
+
172
+ /** As check, with the field the problem is about (Problem). */
173
+ export function problemAt(fields: Field[], value: Record<string, unknown>, where = '', around?: Around, at = ''): Problem | null {
174
+ const here: Around = { fields, value, around };
175
+ for (const f of fields) {
176
+ if (!inUse(f, fields, value, around)) continue;
177
+ const v = value?.[f.name];
178
+ const label = where ? `${where} › ${f.label}` : f.label;
179
+ const path = at ? `${at}.${f.name}` : f.name;
180
+ const empty = v == null || v === '' || (Array.isArray(v) && v.length === 0);
181
+ const wrong = (text: string) => ({ text, path });
182
+ if (f.required && empty) return wrong(t.check.fillIn(label));
183
+ if (!empty && !ofItsKind(f, v)) return wrong(t.check.wrong(label, t.check.kinds[kindOf(f)]));
184
+ if (f.type === 'select' && f.options && !empty && !f.options.some((o) => o.value === v))
185
+ return wrong(t.check.notAnOption(label, String(v)));
186
+ const form = typeof v === 'string' && problemOf(f, v);
187
+ if (form) return wrong(t.check.wrong(label, form));
188
+ if (f.type === 'markdown' && f.template && typeof v === 'string') {
189
+ const [rule] = textProblems(f.template, v);
190
+ if (rule) return wrong(t.check.inField(label, rule));
191
+ }
192
+ if (f.type === 'object') {
193
+ const problem = problemAt(f.fields!, v as Record<string, unknown>, label, here, path);
194
+ if (problem) return problem;
195
+ }
196
+ if (f.type === 'blocks' && Array.isArray(v) && f.template) {
197
+ const name = (type: string) => f.blocks!.find((b) => b.type === type)?.label ?? type;
198
+ const [rule] = templateProblems(f.template, v as Item[], name);
199
+ if (rule) return wrong(t.check.inField(label, rule));
200
+ }
201
+ if (f.type === 'blocks' && Array.isArray(v)) {
202
+ // Hidden blocks are not on the site, so they may be unfinished.
203
+ for (const [i, item] of (v as Item[]).entries()) {
204
+ const block = blockOf(f, item);
205
+ if (item.hidden || !block) continue;
206
+ const problem = problemAt(block.fields, item, `${label} › ${block.label}`, undefined, `${path}.${i}`);
207
+ if (problem) return problem;
208
+ }
209
+ }
210
+ if (f.type === 'list' && Array.isArray(v)) {
211
+ if (f.maxItems !== undefined && v.length > f.maxItems) return wrong(t.check.atMostItems(label, f.maxItems));
212
+ for (const [i, item] of v.entries()) {
213
+ const named = `${label} ${i + 1}`;
214
+ const problem = f.field
215
+ ? problemAt([f.field], { [f.field.name]: item }, named, here, `${path}.${i}`)
216
+ : problemAt(f.fields!, item as Record<string, unknown>, named, here, `${path}.${i}`);
217
+ if (problem) return problem;
218
+ }
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+
224
+ // The value as it is saved: optional fields left empty (text, number, list) are left out, as in the
225
+ // existing files (their readers treat a missing field as empty). Keys keep their order.
226
+ export function clean(fields: Field[], value: Record<string, unknown>, around?: Around): Record<string, unknown> {
227
+ const out: Record<string, unknown> = {};
228
+ const here: Around = { fields, value, around };
229
+ for (const [key, v] of Object.entries(value)) {
230
+ const f = fields.find((x) => x.name === key);
231
+ const empty = v == null || v === '' || (Array.isArray(v) && v.length === 0);
232
+ if (f && ((!f.required && empty) || !inUse(f, fields, value, around))) continue;
233
+ if (f?.digitsInPairs && typeof v === 'string') out[key] = inPairs(f, v);
234
+ else if (f?.pattern && typeof v === 'string') out[key] = v.trim();
235
+ else if (f?.type === 'object') out[key] = clean(f.fields!, v as Record<string, unknown>, here);
236
+ else if (f?.type === 'list' && f.fields) out[key] = (v as Record<string, unknown>[]).map((item) => clean(f.fields!, item, here));
237
+ // A block keeps its type first and `hidden` last, and only when it is hidden.
238
+ else if (f?.type === 'blocks')
239
+ out[key] = (v as Item[]).map(({ type, hidden, ...item }) => ({
240
+ type,
241
+ ...clean(blockOf(f, { type })?.fields ?? [], item),
242
+ ...(hidden ? { hidden: true } : {}),
243
+ }));
244
+ else out[key] = v;
245
+ }
246
+ return out;
247
+ }
248
+
249
+ // A picture in Markdown (MarkdownEditor.tsx puts them in).
250
+ const PICTURES = /!\[(?:\\.|[^\]])*\]\([^)\s]+/g;
251
+
252
+ // A block's name by its type: a standard block's (the editor's texts), a picture's, else its type.
253
+ const nameOf = (type: string) =>
254
+ type === 'image' ? t.blocks.picture.label : ((t.blocks as unknown as Record<string, { label?: string }>)[type]?.label ?? type);
255
+
256
+ /** What is wrong with the blocks and pictures in the text of a markdown field by its template (Field.template): one
257
+ * that cannot be there, and too many of a type ('image': the pictures). */
258
+ export function textProblems(template: Template, markdown: string): string[] {
259
+ const types = [...blocksIn(markdown).map((b) => b.type), ...[...markdown.matchAll(PICTURES)].map(() => 'image')];
260
+ const problems: string[] = [];
261
+ for (const type of new Set(types)) {
262
+ if (template.blocks && !template.blocks.includes(type)) problems.push(t.check.cannotBeInText(nameOf(type)));
263
+ const max = template.max?.[type];
264
+ if (max !== undefined && types.filter((x) => x === type).length > max) problems.push(t.check.atMostInText(nameOf(type), max));
265
+ }
266
+ return problems;
267
+ }
268
+
269
+ /** What is wrong with the blocks of a text by its template (collections.ts): one that cannot be there,
270
+ * too many of a type, and one that must be on the page (required, locked) but is not, or is hidden.
271
+ * blocks: all of them in order, hidden ones too ({ type, hidden }); name: a type's name. */
272
+ export function templateProblems(template: Template, blocks: Item[], name: (type: string) => string): string[] {
273
+ const problems: string[] = [];
274
+ const count = (type: string, shown = false) => blocks.filter((b) => b.type === type && !(shown && b.hidden)).length;
275
+ for (const type of new Set(blocks.map((b) => String(b.type)))) {
276
+ if (template.blocks && !template.blocks.includes(type)) problems.push(t.check.cannotBeHere(name(type)));
277
+ const max = template.max?.[type];
278
+ if (max !== undefined && count(type) > max) problems.push(t.check.atMost(name(type), max));
279
+ }
280
+ for (const type of [...(template.required ?? []), ...(template.locked ?? [])]) {
281
+ if (!count(type)) problems.push(t.check.mustBeOnPage(name(type)));
282
+ else if (!count(type, true)) problems.push(t.check.mustBeShown(name(type)));
283
+ }
284
+ return [...new Set(problems)];
285
+ }
@@ -0,0 +1,256 @@
1
+ // Everything the editor writes, checked by the editor's own rules (check.ts) before the site is built
2
+ // (packages/editor/scripts/check-content.mjs): every block in the text of the pages and posts (src/apps/<app>/blocks/, an unknown
3
+ // one too), and the data files with the fields the editor edits them with: the fixed pages with their
4
+ // sections, the menu and the other single files, the lists (prices, testimonials, tags), the
5
+ // screens under "Virksomheden" and "Blokke", the apps installed and their settings (src/apps/), and the
6
+ // colours ("Farver": colours, readable together). A hidden block is not on the site, so it may be unfinished.
7
+ // The rules of a page's template (collections.ts) too: what may be there, how many, and what must.
8
+ // The redirects (redirects.ts): none sends on through another, and none in a loop. And the site's own rules
9
+ // (siteProblems in its rules.ts: a button that opens a promotion not on its page).
10
+ // And the site's texts the editor has no translation of (untranslated).
11
+ import { check } from './check';
12
+ import {
13
+ appearance,
14
+ blockParts,
15
+ collections,
16
+ colorFields,
17
+ colorPairs,
18
+ colorRoles,
19
+ companyParts,
20
+ config,
21
+ frontLeads,
22
+ groups,
23
+ partScreens,
24
+ seoParts,
25
+ siteProblems,
26
+ } from '@site/editor';
27
+ import { colorProblems, lowContrast, MODES } from '../lib/colors';
28
+ import { completeColors, dynamicProblems } from '../lib/dynamic-colors';
29
+ import { APP_HOOKS, hooksOf } from '../lib/hooks';
30
+ import { texts } from '@site/texts';
31
+ import { texts as standardTexts } from '../defaults/texts';
32
+ import type { Part } from './schema';
33
+ import { apps, asStandardTemplate, blockProblems, siteBlocks, standardPageOf, withPageBlocks } from './registry';
34
+ import { installedApps, settingsFile } from './apps';
35
+ import { appFolder, SCREENSHOTS, shotFile, shotOf } from './store';
36
+ import { appFolders } from './appFiles';
37
+ import { colorPairs as shownPairs, sharedParts, translator, writtenApps, writtenBlockParts, writtenCollections } from './site';
38
+ import { splitBlocks } from '../lib/blocks.mjs';
39
+ import { appTexts, DEFAULTS_LOCALE, locales, siteLocale, t, type Texts } from './i18n';
40
+ import { flatten, readAll, REDIRECT_FILES, type AllRedirects } from './redirects';
41
+
42
+ const ratio = (n: number) => n.toLocaleString(config.locale, { maximumFractionDigits: 1 });
43
+
44
+ export type SiteFile = { path: string; text: string };
45
+
46
+ /** What is wrong, a line for each file or block ('src/content/pages/x.md: Udfyld "Pris › Pris".'), and
47
+ * how many files and blocks were checked. */
48
+ export function problemsOf(files: SiteFile[]): { problems: string[]; files: number; blocks: number } {
49
+ const problems: string[] = [];
50
+ const byPath = new Map(files.map((f) => [f.path, f]));
51
+ const checked = new Set<string>();
52
+ let blocks = 0;
53
+
54
+ // A data file by the fields it is edited with (at the top or under `at`, over the values it starts
55
+ // with; a file that is only a list under `root`).
56
+ const data = (
57
+ path: string,
58
+ fields: Parameters<typeof check>[0],
59
+ at?: string,
60
+ root?: string,
61
+ defaults?: Record<string, unknown>,
62
+ values?: Record<string, unknown>,
63
+ // An entry of a list as the list has it now (FolderCollection.upgrade: a contact bar is an action bar).
64
+ upgrade?: (value: Record<string, unknown>) => Record<string, unknown>,
65
+ ) => {
66
+ const file = byPath.get(path);
67
+ if (!file) return;
68
+ checked.add(path);
69
+ let value: unknown;
70
+ try {
71
+ value = JSON.parse(file.text);
72
+ } catch {
73
+ return problems.push(`${path}: ${t.app.invalidJson}`);
74
+ }
75
+ if (root) value = { [root]: value };
76
+ else if (at) value = { ...defaults, ...((value as Record<string, unknown>)[at] ?? {}) };
77
+ else if (values) value = { ...values, ...(value as Record<string, unknown>) };
78
+ if (upgrade) value = upgrade(value as Record<string, unknown>);
79
+ const problem = check(fields, value as Record<string, unknown>);
80
+ if (problem) problems.push(`${path}: ${problem}`);
81
+ };
82
+
83
+ // With the kinds the apps add to them (a Mailchimp pop-up).
84
+ for (const c of writtenCollections) {
85
+ if (c.kind === 'files')
86
+ for (const f of c.files)
87
+ data(f.file, f.root ? [...f.fields, f.root] : withPageBlocks(f.fields), undefined, f.fields.length ? undefined : f.root?.name);
88
+ if (c.kind === 'folder')
89
+ for (const f of files)
90
+ if (f.path.startsWith(`${c.folder}/`) && f.path.endsWith('.json'))
91
+ data(f.path, c.fields, undefined, undefined, undefined, undefined, c.upgrade && ((v) => c.upgrade!(v, files)));
92
+ }
93
+ for (const part of [
94
+ ...companyParts.flatMap((p) => [p, ...(p.parts ?? [])]),
95
+ ...partScreens.flatMap((s) => s.parts),
96
+ ...appearance,
97
+ ...seoParts,
98
+ ...writtenBlockParts,
99
+ ] as Part[])
100
+ if (part.file && part.fields) data(part.file, part.fields, part.at, undefined, part.defaults);
101
+ // The blocks turned off under "Blokindstillinger" (BlockSettings.tsx): a list of their types.
102
+ data(config.blockSettings, [
103
+ { name: 'off', label: t.app.blocksOff, type: 'list', field: { name: 'type', label: t.app.block, type: 'string' } },
104
+ ]);
105
+ // The apps installed under "Apps" (AppList.tsx): a list of their ids, and each app's settings.
106
+ data(config.apps.installed, [
107
+ { name: 'installed', label: t.app.appsInstalled, type: 'list', field: { name: 'id', label: t.app.app, type: 'string' } },
108
+ ]);
109
+ for (const app of apps)
110
+ if (app.settings) data(settingsFile(app.id), app.settings.fields, undefined, undefined, undefined, app.settings.values);
111
+ // Each app installed is in the site: one from the app store is copied into it when it is installed (store.ts).
112
+ for (const id of installedApps(files))
113
+ if (!apps.some((a) => a.id === id)) problems.push(`${config.apps.installed}: ${t.apps.missing(id, appFolder(id))}`);
114
+ // Each file in an app's hooks/ is a place an app may change the site (@morphemecms/cms/lib/hooks.ts).
115
+ for (const app of writtenApps)
116
+ for (const file of hooksOf(appFolders[app.id]?.files ?? []).unknown)
117
+ problems.push(`${appFolder(app.id)}/${file}: ${t.apps.unknownHook(APP_HOOKS.map((h) => `hooks/${h}.ts`).join(', '))}`);
118
+ // Each picture of an app shows something it has (shotOf) and is in its screenshots/, and each file there is one of them.
119
+ for (const app of writtenApps) {
120
+ const blocks = siteBlocks.filter((b) => b.app === app.id).map((b) => b.type);
121
+ const files = (appFolders[app.id]?.files ?? []).filter((f) => f.startsWith(`${SCREENSHOTS}/`));
122
+ const listed = (app.screenshots ?? []).map((x, i) => `${SCREENSHOTS}/${shotFile(i, x.shows)}`);
123
+ for (const x of app.screenshots ?? [])
124
+ if (!shotOf(x.shows, app, blocks)) problems.push(`${appFolder(app.id)}/index.ts: ${t.apps.unknownScreenshot(x.shows)}`);
125
+ for (const file of listed.filter((f) => !files.includes(f)))
126
+ problems.push(`${appFolder(app.id)}/index.ts: ${t.apps.noScreenshot(file)}`);
127
+ for (const file of files.filter((f) => !listed.includes(f)))
128
+ problems.push(`${appFolder(app.id)}/${file}: ${t.apps.unlistedScreenshot}`);
129
+ }
130
+
131
+ // The colours under "Farver" (Colors.tsx): every colour in both modes (those a file written before them does not have
132
+ // made, completeColors), readable together, the choice of dynamic colour they were made from, if they were.
133
+ const colorsFile = byPath.get(config.colors);
134
+ if (colorsFile) {
135
+ checked.add(colorsFile.path);
136
+ try {
137
+ const colors = completeColors(JSON.parse(colorsFile.text), colorRoles);
138
+ const bad = colorProblems(
139
+ colors,
140
+ colorFields.map((f) => f.name),
141
+ );
142
+ for (const where of bad) problems.push(`${colorsFile.path}: ${t.colors.notAColorAt(where)}`);
143
+ if (colors.dynamic !== undefined)
144
+ for (const where of dynamicProblems(colors.dynamic)) problems.push(`${colorsFile.path}: ${t.colors.dynamic.wrongAt(where)}`);
145
+ if (!bad.length)
146
+ for (const mode of MODES)
147
+ for (const pair of lowContrast(colors[mode], shownPairs))
148
+ problems.push(`${colorsFile.path}: ${t.colors[mode]}: ${t.colors.tooLow(pair.what, ratio(pair.ratio), ratio(pair.min))}`);
149
+ } catch {
150
+ problems.push(`${colorsFile.path}: ${t.app.invalidJson}`);
151
+ }
152
+ }
153
+
154
+ // The redirects in their three files: each straight to where it ends (flatten changes nothing).
155
+ const redirectFiles = files.filter((f) => REDIRECT_FILES.includes(f.path));
156
+ if (redirectFiles.length) {
157
+ redirectFiles.forEach((f) => checked.add(f.path));
158
+ try {
159
+ const all = readAll(redirectFiles.map((f) => ({ ...f, oid: '' })));
160
+ const flat = flatten(all);
161
+ if (typeof flat === 'string') problems.push(flat);
162
+ else for (const line of chainsOf(all, flat)) problems.push(line);
163
+ } catch {
164
+ problems.push(`${config.siteRedirects.map}: ${t.app.invalidJson}`);
165
+ }
166
+ }
167
+
168
+ // The blocks in the text of the pages and posts, by the template of their collection.
169
+ for (const f of files) {
170
+ if (!f.path.endsWith('.md')) continue;
171
+ checked.add(f.path);
172
+ blocks += splitBlocks(f.text, null).filter((part) => 'type' in part).length;
173
+ const collection = collections.find((c) => c.kind === 'markdown' && f.path.startsWith(`${c.folder}/`));
174
+ const template = collection?.kind === 'markdown' ? collection.template : undefined;
175
+ // A standard page by its own rules too (the blog page has "Blogindlæg"), and the front page's leads read as the site reads
176
+ // them (packages/editor/site/home.ts).
177
+ const standard = standardPageOf(f, files);
178
+ const front = standard?.key === 'front';
179
+ for (const problem of blockProblems(f.text, asStandardTemplate(template, standard), front ? frontLeads : undefined))
180
+ problems.push(`${f.path}: ${problem}`);
181
+ }
182
+ // The site's own rules (a button that opens a promotion that is not on its page).
183
+ problems.push(...siteProblems(files));
184
+ return { problems, files: checked.size, blocks };
185
+ }
186
+
187
+ // A line for each redirect that sends on through another: its file, where it sends and where it ends.
188
+ function chainsOf(all: AllRedirects, flat: AllRedirects): string[] {
189
+ const lines: string[] = [];
190
+ const compare = (file: string, was: Record<string, string>, now: Record<string, string>) => {
191
+ for (const [from, to] of Object.entries(was)) if (now[from] !== to) lines.push(`${file}: ${t.redirects.chain(from, to, now[from])}`);
192
+ };
193
+ compare(config.redirects, all.own, flat.own);
194
+ compare(config.siteRedirects.map, all.map.paths, flat.map.paths);
195
+ compare(config.siteRedirects.map, all.map.ids, flat.map.ids);
196
+ all.rules.forEach((line, i) => {
197
+ if (line !== flat.rules[i]) lines.push(`${config.siteRedirects.rules}: ${t.redirects.chainRule(line.trim(), flat.rules[i].trim())}`);
198
+ });
199
+ return lines;
200
+ }
201
+
202
+ /** The site's own texts with no translation, by the editor's other languages, by the file they belong
203
+ * in: the site's in @site/texts, a block's in its texts.ts (or the site's, for what it takes from
204
+ * the site). The editor shows them as they are written. Not the ones said in code (the messages in
205
+ * rules.ts, the lines on the blocks' cards). */
206
+ export function untranslated(): Record<string, Record<string, string[]>> {
207
+ // The editor shows a site's definitions translated already on a site in another language than Danish, so a text the walk
208
+ // meets is traced back to the one it was translated from (the translations' tables are by it). A text is written in the
209
+ // package's language when the package's standard parts or an app have it in Danish (defaults/texts.ts, an app's
210
+ // texts.ts), else in the site's; it needs a translation in each other language.
211
+ const tables = [standardTexts, ...Object.values(appTexts)].map((table) => Object.entries(table['en-US'] ?? {}));
212
+ const danish = new Set(tables.flatMap((entries) => entries.map(([text]) => text)));
213
+ const original = new Map(tables.flatMap((entries) => entries.map(([text, translated]) => [translated, text] as const)));
214
+ const writtenIn = (text: string) => (danish.has(text) ? DEFAULTS_LOCALE : siteLocale);
215
+ const missing = (l: string, own: Record<string, string>, walk: (w: ReturnType<typeof translator>) => void) => {
216
+ const found = new Set<string>();
217
+ walk(
218
+ translator((shown) => {
219
+ const text = original.get(shown) ?? shown;
220
+ if (writtenIn(text) !== l && !(text in own)) found.add(text);
221
+ return shown;
222
+ }),
223
+ );
224
+ return [...found];
225
+ };
226
+ return Object.fromEntries(
227
+ locales
228
+ .filter((l) => l !== siteLocale || l !== DEFAULTS_LOCALE)
229
+ .map((l) => {
230
+ const site = (texts as Texts)[l] ?? {};
231
+ const files: Record<string, string[]> = {
232
+ 'src/site/texts.ts': missing(l, site, (w) => {
233
+ collections.forEach(w.collection);
234
+ [...companyParts, ...partScreens.flatMap((s) => s.parts), ...appearance, ...seoParts, ...blockParts].forEach(w.part);
235
+ partScreens.forEach((s) => w.group({ title: s.label, ids: [] }));
236
+ groups.forEach(w.group);
237
+ colorFields.forEach(w.color);
238
+ colorPairs.forEach(w.pair);
239
+ }),
240
+ };
241
+ // An app's block's own texts, and the fields of its shared texts (shared.ts), in its texts.ts.
242
+ for (const block of siteBlocks.filter((b) => b.app))
243
+ files[`src/apps/${block.app}/blocks/${block.type}/texts.ts`] = missing(
244
+ l,
245
+ { ...site, ...appTexts[`blocks/${block.type}`]?.[l] },
246
+ (w) => {
247
+ w.block(block);
248
+ sharedParts.filter((p) => p.id === block.type).forEach(w.part);
249
+ },
250
+ );
251
+ // An app's name, what it does and its settings, in its texts.ts.
252
+ for (const app of apps) files[`src/apps/${app.id}/texts.ts`] = missing(l, { ...site, ...appTexts[app.id]?.[l] }, (w) => w.app(app));
253
+ return [l, Object.fromEntries(Object.entries(files).filter(([, list]) => list.length))];
254
+ }),
255
+ );
256
+ }
@@ -0,0 +1,26 @@
1
+ // The visitors' answers in the cookie banner, as the site's Worker keeps them (packages/editor/worker/consent.js, /api/consent on the
2
+ // editor's own address), for "Samtykker" under "Privatliv" (Consents.tsx). Asked with the GitHub sign-in (the Worker's
3
+ // cookie, sent with the request): the Worker lets only someone who can save the site see or delete them.
4
+ import type { ConsentCategory } from '../lib/cookieCategories.mjs';
5
+
6
+ /** An answer: the visitor's consent id, when (ISO), the yes or no to each category the banner asked about, and the
7
+ * commit the site was built from (the banner they answered). */
8
+ export type ConsentAnswer = { id: string; at: string; choices: Partial<Record<ConsentCategory, boolean>>; version: string };
9
+
10
+ export const CONSENT_API = '/api/consent';
11
+
12
+ /** The newest answers (at most 500), those of a consent id starting with `query`, and how many there are in all; null
13
+ * when the site does not keep them (no Worker, no storage). */
14
+ export async function loadConsents(query = ''): Promise<{ entries: ConsentAnswer[]; total: number } | null> {
15
+ const res = await fetch(`${CONSENT_API}${query ? `?q=${encodeURIComponent(query)}` : ''}`, { cache: 'no-store' });
16
+ if (res.status === 503 || !res.headers.get('content-type')?.includes('application/json')) return null;
17
+ if (!res.ok) throw new Error(String(res.status));
18
+ return (await res.json()) as { entries: ConsentAnswer[]; total: number };
19
+ }
20
+
21
+ /** Deletes every answer with a consent id (a visitor asked for it); how many. */
22
+ export async function deleteConsents(id: string): Promise<number> {
23
+ const res = await fetch(CONSENT_API, { method: 'DELETE', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id }) });
24
+ if (!res.ok) throw new Error(String(res.status));
25
+ return ((await res.json()) as { deleted: number }).deleted;
26
+ }
@@ -0,0 +1,70 @@
1
+ // The site's cookie list, "Cookies" under "Lister" (the list the standard block "Cookieliste" brings,
2
+ // standard/cookies/list.ts): a file per cookie in config.cookieList, each with its name (`*` for the rest of it), who
3
+ // sets it, what for, how long it lasts and its category (necessary, or one the visitor says yes to in the cookie banner,
4
+ // lib/cookieCategories.mjs). An app's cookies (its
5
+ // cookies.json, those with a name) are added to it in the commit that installs the app, and those it added (`app`, its
6
+ // id, kept in the file but not shown) are taken out in the one that uninstalls it; an update adds its new ones and takes
7
+ // out those it no longer sets. One already in the list by its name and provider is left as it is, so what was written
8
+ // over an app's text stays. The app "Cookiescanner" adds those its scan finds besides (its worker.js), and more can be
9
+ // added by hand.
10
+ import { config } from '@site/config';
11
+ import type { Change, RepoFile } from './github';
12
+ import { slugify } from './schema';
13
+ import { categoryOf, type CookieCategory } from '../lib/cookieCategories.mjs';
14
+ import type { AppCookie } from './store';
15
+
16
+ /** A cookie in the list, as its file has it; `app`: the app that added it; `new`: added by the app "Cookiescanner" and not
17
+ * yet looked through (first in the list, marked, until it is saved: FolderCollection.fresh); `scanned`: added by it, kept
18
+ * when saved (its tab "Problemer" says when the site no longer sets it). */
19
+ export type CookieEntry = {
20
+ name: string;
21
+ provider: string;
22
+ purpose: string;
23
+ lasts?: string;
24
+ category: CookieCategory;
25
+ app?: string;
26
+ new?: boolean;
27
+ scanned?: boolean;
28
+ };
29
+
30
+ /** Whether a file is one of the list's. */
31
+ export const inCookieList = (path: string) => path.startsWith(`${config.cookieList}/`) && path.endsWith('.json');
32
+
33
+ function entryOf(file: RepoFile): Partial<CookieEntry> {
34
+ try {
35
+ return JSON.parse(file.text) ?? {};
36
+ } catch {
37
+ return {};
38
+ }
39
+ }
40
+
41
+ const same = (a: { name?: string; provider?: string }, b: { name?: string; provider?: string }) =>
42
+ a.name === b.name && (a.provider ?? '') === (b.provider ?? '');
43
+
44
+ /** The changes to the list when an app is installed or updated with `cookies` (its cookies.json, in the site's
45
+ * language), or uninstalled (null), from the site's files now (see above). */
46
+ export function cookieChanges(app: string, cookies: AppCookie[] | null, files: RepoFile[]): Change[] {
47
+ const list = files.filter((f) => inCookieList(f.path)).map((file) => ({ file, entry: entryOf(file) }));
48
+ const wanted = (cookies ?? []).filter((c) => c.name?.trim());
49
+ const changes: Change[] = list
50
+ .filter(({ entry }) => entry.app === app && !wanted.some((c) => same(c, entry)))
51
+ .map(({ file }) => ({ path: file.path, content: null, oid: file.oid }));
52
+ const taken = new Set(list.map(({ file }) => file.path));
53
+ for (const c of wanted) {
54
+ if (list.some(({ entry }) => same(c, entry))) continue;
55
+ const slug = slugify(`${c.name!.replace(/\*/g, ' x ')} ${c.provider ?? ''}`) || 'cookie';
56
+ let path = `${config.cookieList}/${slug}.json`;
57
+ for (let n = 2; taken.has(path); n++) path = `${config.cookieList}/${slug}-${n}.json`;
58
+ taken.add(path);
59
+ const entry: CookieEntry = {
60
+ name: c.name!,
61
+ provider: c.provider ?? '',
62
+ purpose: c.purpose,
63
+ ...(c.lasts ? { lasts: c.lasts } : {}),
64
+ category: categoryOf(c),
65
+ app,
66
+ };
67
+ changes.push({ path, content: `${JSON.stringify(entry, null, 2)}\n`, oid: null });
68
+ }
69
+ return changes;
70
+ }