@morphemecms/cms 0.1.0 → 0.2.0-next-20261104150211

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 (908) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -21
  3. package/README.md +51 -51
  4. package/astro.mjs +162 -162
  5. package/bin/morpheme.mjs +38 -38
  6. package/client.d.ts +10 -10
  7. package/defaults/blockTexts.ts +33 -33
  8. package/defaults/collections.ts +774 -774
  9. package/defaults/colors.ts +125 -125
  10. package/defaults/company.ts +20 -20
  11. package/defaults/editor.ts +178 -178
  12. package/defaults/internalLinks.ts +30 -30
  13. package/defaults/opens.ts +18 -18
  14. package/defaults/popup.tsx +93 -93
  15. package/defaults/privacy.ts +79 -79
  16. package/defaults/rules.ts +450 -450
  17. package/defaults/schema.ts +16 -16
  18. package/defaults/sitedata.ts +129 -129
  19. package/defaults/standard.ts +106 -106
  20. package/defaults/standardPages.ts +87 -87
  21. package/defaults/texts.ts +481 -481
  22. package/demo/README.md +26 -26
  23. package/demo/src/content/blog/build-a-page-from-blocks.md +19 -19
  24. package/demo/src/content/blog/colors-everyone-can-read.md +19 -19
  25. package/demo/src/content/blog/edit-your-website-from-your-phone.md +21 -21
  26. package/demo/src/content/blog/every-save-is-a-version.md +21 -21
  27. package/demo/src/content/blog/schedule-a-post.md +17 -17
  28. package/demo/src/content/blog/secure-from-day-one.md +17 -17
  29. package/demo/src/content/blog/seo-without-the-guesswork.md +23 -23
  30. package/demo/src/content/blog/switch-themes-without-losing-anything.md +23 -23
  31. package/demo/src/content/blog/the-app-store.md +21 -21
  32. package/demo/src/content/pages/about.md +22 -22
  33. package/demo/src/content/pages/app-store.md +30 -30
  34. package/demo/src/content/pages/blocks.md +98 -98
  35. package/demo/src/content/pages/blog-page.md +11 -11
  36. package/demo/src/content/pages/contact.md +16 -16
  37. package/demo/src/content/pages/get-started.md +20 -20
  38. package/demo/src/content/pages/home.md +26 -26
  39. package/demo/src/content/pages/page-not-found.md +11 -11
  40. package/demo/src/content/pages/privacy-policy.md +24 -24
  41. package/demo/src/data/blocks.json +25 -25
  42. package/demo/src/data/booking.json +6 -6
  43. package/demo/src/data/categories/apps.json +6 -6
  44. package/demo/src/data/categories/design.json +6 -6
  45. package/demo/src/data/categories/editing.json +6 -6
  46. package/demo/src/data/categories/security.json +6 -6
  47. package/demo/src/data/categories/seo.json +6 -6
  48. package/demo/src/data/colors.json +75 -75
  49. package/demo/src/data/contact.json +8 -8
  50. package/demo/src/data/cookies.json +21 -21
  51. package/demo/src/data/footer.json +27 -27
  52. package/demo/src/data/menu.json +35 -35
  53. package/demo/src/data/mobile-menu.json +25 -25
  54. package/demo/src/data/popups/booking-bar.json +13 -13
  55. package/demo/src/data/popups/try-the-editor.json +16 -16
  56. package/demo/src/data/standard-pages.json +8 -8
  57. package/demo/src/data/testimonials/sample-bakery.json +10 -10
  58. package/demo/src/data/testimonials/sample-clinic.json +10 -10
  59. package/demo/src/data/testimonials/sample-studio.json +10 -10
  60. package/demo/src/site/colors.ts +11 -11
  61. package/demo/src/site/config.ts +19 -19
  62. package/icons/lucide/LICENSE +43 -43
  63. package/icons/lucide/address-book.svg +1 -1
  64. package/icons/lucide/app-window.svg +1 -1
  65. package/icons/lucide/arrow-counter-clockwise.svg +1 -1
  66. package/icons/lucide/arrow-down.svg +1 -1
  67. package/icons/lucide/arrow-left.svg +1 -1
  68. package/icons/lucide/arrow-right.svg +1 -1
  69. package/icons/lucide/arrow-square-out.svg +1 -1
  70. package/icons/lucide/arrow-u-up-left.svg +1 -1
  71. package/icons/lucide/arrow-u-up-right.svg +1 -1
  72. package/icons/lucide/arrow-up-right.svg +1 -1
  73. package/icons/lucide/arrow-up.svg +1 -1
  74. package/icons/lucide/arrows-down-up.svg +1 -1
  75. package/icons/lucide/arrows-in-line-horizontal.svg +1 -1
  76. package/icons/lucide/arrows-out-line-horizontal.svg +1 -1
  77. package/icons/lucide/article.svg +1 -1
  78. package/icons/lucide/bank.svg +1 -1
  79. package/icons/lucide/bell.svg +1 -1
  80. package/icons/lucide/bicycle.svg +1 -1
  81. package/icons/lucide/binoculars.svg +1 -1
  82. package/icons/lucide/book-open-text.svg +1 -1
  83. package/icons/lucide/book-open.svg +1 -1
  84. package/icons/lucide/browser.svg +1 -1
  85. package/icons/lucide/buildings.svg +1 -1
  86. package/icons/lucide/bus.svg +1 -1
  87. package/icons/lucide/calendar-blank.svg +1 -1
  88. package/icons/lucide/calendar-check.svg +1 -1
  89. package/icons/lucide/car.svg +1 -1
  90. package/icons/lucide/cards.svg +1 -1
  91. package/icons/lucide/caret-down.svg +1 -1
  92. package/icons/lucide/caret-left.svg +1 -1
  93. package/icons/lucide/caret-right.svg +1 -1
  94. package/icons/lucide/caret-up.svg +1 -1
  95. package/icons/lucide/certificate.svg +1 -1
  96. package/icons/lucide/chart-line-up.svg +1 -1
  97. package/icons/lucide/chats.svg +1 -1
  98. package/icons/lucide/check-circle.svg +1 -1
  99. package/icons/lucide/check.svg +1 -1
  100. package/icons/lucide/circle-half.svg +1 -1
  101. package/icons/lucide/circle.svg +1 -1
  102. package/icons/lucide/clock-counter-clockwise.svg +1 -1
  103. package/icons/lucide/clock.svg +1 -1
  104. package/icons/lucide/code.svg +1 -1
  105. package/icons/lucide/coffee.svg +1 -1
  106. package/icons/lucide/columns.svg +1 -1
  107. package/icons/lucide/cookie.svg +1 -1
  108. package/icons/lucide/copy.svg +1 -1
  109. package/icons/lucide/credit-card.svg +1 -1
  110. package/icons/lucide/currency-circle-dollar.svg +1 -1
  111. package/icons/lucide/cursor-click.svg +1 -1
  112. package/icons/lucide/database.svg +1 -1
  113. package/icons/lucide/desktop.svg +1 -1
  114. package/icons/lucide/device-mobile.svg +1 -1
  115. package/icons/lucide/door-open.svg +1 -1
  116. package/icons/lucide/download-simple.svg +1 -1
  117. package/icons/lucide/envelope-simple.svg +1 -1
  118. package/icons/lucide/eye-slash.svg +1 -1
  119. package/icons/lucide/eye.svg +1 -1
  120. package/icons/lucide/file-pdf.svg +1 -1
  121. package/icons/lucide/file-text.svg +1 -1
  122. package/icons/lucide/file-video.svg +1 -1
  123. package/icons/lucide/file-zip.svg +1 -1
  124. package/icons/lucide/flower-lotus.svg +1 -1
  125. package/icons/lucide/folder-simple.svg +1 -1
  126. package/icons/lucide/funnel.svg +1 -1
  127. package/icons/lucide/gauge.svg +1 -1
  128. package/icons/lucide/gear.svg +1 -1
  129. package/icons/lucide/github-logo.svg +1 -1
  130. package/icons/lucide/globe.svg +1 -1
  131. package/icons/lucide/google-logo.svg +1 -1
  132. package/icons/lucide/graduation-cap.svg +1 -1
  133. package/icons/lucide/hammer.svg +1 -1
  134. package/icons/lucide/hand-heart.svg +1 -1
  135. package/icons/lucide/handshake.svg +1 -1
  136. package/icons/lucide/hard-drives.svg +1 -1
  137. package/icons/lucide/heart.svg +1 -1
  138. package/icons/lucide/house.svg +1 -1
  139. package/icons/lucide/image-square.svg +1 -1
  140. package/icons/lucide/image.svg +1 -1
  141. package/icons/lucide/info.svg +1 -1
  142. package/icons/lucide/keyboard.svg +1 -1
  143. package/icons/lucide/leaf.svg +1 -1
  144. package/icons/lucide/lifebuoy.svg +1 -1
  145. package/icons/lucide/lightbulb.svg +1 -1
  146. package/icons/lucide/link-break.svg +1 -1
  147. package/icons/lucide/link-simple.svg +1 -1
  148. package/icons/lucide/list-bullets.svg +1 -1
  149. package/icons/lucide/list-checks.svg +1 -1
  150. package/icons/lucide/list-numbers.svg +1 -1
  151. package/icons/lucide/list.svg +1 -1
  152. package/icons/lucide/lock-simple-open.svg +1 -1
  153. package/icons/lucide/lock-simple.svg +1 -1
  154. package/icons/lucide/magnifying-glass.svg +1 -1
  155. package/icons/lucide/map-pin.svg +1 -1
  156. package/icons/lucide/map-trifold.svg +1 -1
  157. package/icons/lucide/medal.svg +1 -1
  158. package/icons/lucide/megaphone.svg +1 -1
  159. package/icons/lucide/minus.svg +1 -1
  160. package/icons/lucide/moon.svg +1 -1
  161. package/icons/lucide/newspaper.svg +1 -1
  162. package/icons/lucide/pack.json +23 -23
  163. package/icons/lucide/paint-brush-broad.svg +1 -1
  164. package/icons/lucide/palette.svg +1 -1
  165. package/icons/lucide/paper-plane-tilt.svg +1 -1
  166. package/icons/lucide/pencil-simple.svg +1 -1
  167. package/icons/lucide/phone.svg +1 -1
  168. package/icons/lucide/play-fill.svg +1 -1
  169. package/icons/lucide/plus.svg +1 -1
  170. package/icons/lucide/prohibit.svg +1 -1
  171. package/icons/lucide/puzzle-piece.svg +1 -1
  172. package/icons/lucide/question.svg +1 -1
  173. package/icons/lucide/quotes.svg +1 -1
  174. package/icons/lucide/rows.svg +1 -1
  175. package/icons/lucide/ruler.svg +1 -1
  176. package/icons/lucide/scan.svg +1 -1
  177. package/icons/lucide/shapes.svg +1 -1
  178. package/icons/lucide/share-network.svg +1 -1
  179. package/icons/lucide/shield-check.svg +1 -1
  180. package/icons/lucide/sidebar-simple.svg +1 -1
  181. package/icons/lucide/sign-out.svg +1 -1
  182. package/icons/lucide/signpost.svg +1 -1
  183. package/icons/lucide/sliders-horizontal.svg +1 -1
  184. package/icons/lucide/smiley.svg +1 -1
  185. package/icons/lucide/sparkle.svg +1 -1
  186. package/icons/lucide/square-half-bottom.svg +1 -1
  187. package/icons/lucide/square.svg +1 -1
  188. package/icons/lucide/squares-four.svg +1 -1
  189. package/icons/lucide/sticker.svg +1 -1
  190. package/icons/lucide/storefront.svg +1 -1
  191. package/icons/lucide/sun.svg +1 -1
  192. package/icons/lucide/swatches.svg +1 -1
  193. package/icons/lucide/tag.svg +1 -1
  194. package/icons/lucide/text-aa.svg +1 -1
  195. package/icons/lucide/text-align-left.svg +1 -1
  196. package/icons/lucide/text-b.svg +1 -1
  197. package/icons/lucide/text-h-one.svg +1 -1
  198. package/icons/lucide/text-h-three.svg +1 -1
  199. package/icons/lucide/text-h-two.svg +1 -1
  200. package/icons/lucide/text-italic.svg +1 -1
  201. package/icons/lucide/text-t.svg +1 -1
  202. package/icons/lucide/ticket.svg +1 -1
  203. package/icons/lucide/toggle-right.svg +1 -1
  204. package/icons/lucide/train.svg +1 -1
  205. package/icons/lucide/trash.svg +1 -1
  206. package/icons/lucide/upload-simple.svg +1 -1
  207. package/icons/lucide/user-circle.svg +1 -1
  208. package/icons/lucide/user.svg +1 -1
  209. package/icons/lucide/users-three.svg +1 -1
  210. package/icons/lucide/users.svg +1 -1
  211. package/icons/lucide/video-camera.svg +1 -1
  212. package/icons/lucide/warning-circle.svg +1 -1
  213. package/icons/lucide/warning.svg +1 -1
  214. package/icons/lucide/wave-sine.svg +1 -1
  215. package/icons/lucide/wheelchair.svg +1 -1
  216. package/icons/lucide/wrench.svg +1 -1
  217. package/icons/lucide/x.svg +1 -1
  218. package/icons/material-symbols/LICENSE +201 -201
  219. package/icons/material-symbols/address-book.svg +1 -1
  220. package/icons/material-symbols/app-window.svg +1 -1
  221. package/icons/material-symbols/arrow-counter-clockwise.svg +1 -1
  222. package/icons/material-symbols/arrow-down.svg +1 -1
  223. package/icons/material-symbols/arrow-left.svg +1 -1
  224. package/icons/material-symbols/arrow-right.svg +1 -1
  225. package/icons/material-symbols/arrow-square-out.svg +1 -1
  226. package/icons/material-symbols/arrow-u-up-left.svg +1 -1
  227. package/icons/material-symbols/arrow-u-up-right.svg +1 -1
  228. package/icons/material-symbols/arrow-up-right.svg +1 -1
  229. package/icons/material-symbols/arrow-up.svg +1 -1
  230. package/icons/material-symbols/arrows-down-up.svg +1 -1
  231. package/icons/material-symbols/arrows-in-line-horizontal.svg +1 -1
  232. package/icons/material-symbols/arrows-out-line-horizontal.svg +1 -1
  233. package/icons/material-symbols/article.svg +1 -1
  234. package/icons/material-symbols/bank.svg +1 -1
  235. package/icons/material-symbols/bell.svg +1 -1
  236. package/icons/material-symbols/bicycle.svg +1 -1
  237. package/icons/material-symbols/binoculars.svg +1 -1
  238. package/icons/material-symbols/book-open-text.svg +1 -1
  239. package/icons/material-symbols/book-open.svg +1 -1
  240. package/icons/material-symbols/browser.svg +1 -1
  241. package/icons/material-symbols/buildings.svg +1 -1
  242. package/icons/material-symbols/bus.svg +1 -1
  243. package/icons/material-symbols/calendar-blank.svg +1 -1
  244. package/icons/material-symbols/calendar-check.svg +1 -1
  245. package/icons/material-symbols/car.svg +1 -1
  246. package/icons/material-symbols/cards.svg +1 -1
  247. package/icons/material-symbols/caret-down.svg +1 -1
  248. package/icons/material-symbols/caret-left.svg +1 -1
  249. package/icons/material-symbols/caret-right.svg +1 -1
  250. package/icons/material-symbols/caret-up.svg +1 -1
  251. package/icons/material-symbols/certificate.svg +1 -1
  252. package/icons/material-symbols/chart-line-up.svg +1 -1
  253. package/icons/material-symbols/chats.svg +1 -1
  254. package/icons/material-symbols/check-circle.svg +1 -1
  255. package/icons/material-symbols/check.svg +1 -1
  256. package/icons/material-symbols/circle-half.svg +1 -1
  257. package/icons/material-symbols/circle.svg +1 -1
  258. package/icons/material-symbols/clock-counter-clockwise.svg +1 -1
  259. package/icons/material-symbols/clock.svg +1 -1
  260. package/icons/material-symbols/code.svg +1 -1
  261. package/icons/material-symbols/coffee.svg +1 -1
  262. package/icons/material-symbols/columns.svg +1 -1
  263. package/icons/material-symbols/cookie.svg +1 -1
  264. package/icons/material-symbols/copy.svg +1 -1
  265. package/icons/material-symbols/credit-card.svg +1 -1
  266. package/icons/material-symbols/currency-circle-dollar.svg +1 -1
  267. package/icons/material-symbols/cursor-click.svg +1 -1
  268. package/icons/material-symbols/database.svg +1 -1
  269. package/icons/material-symbols/desktop.svg +1 -1
  270. package/icons/material-symbols/device-mobile.svg +1 -1
  271. package/icons/material-symbols/door-open.svg +1 -1
  272. package/icons/material-symbols/download-simple.svg +1 -1
  273. package/icons/material-symbols/envelope-simple.svg +1 -1
  274. package/icons/material-symbols/eye-slash.svg +1 -1
  275. package/icons/material-symbols/eye.svg +1 -1
  276. package/icons/material-symbols/file-pdf.svg +1 -1
  277. package/icons/material-symbols/file-text.svg +1 -1
  278. package/icons/material-symbols/file-video.svg +1 -1
  279. package/icons/material-symbols/file-zip.svg +1 -1
  280. package/icons/material-symbols/flower-lotus.svg +1 -1
  281. package/icons/material-symbols/folder-simple.svg +1 -1
  282. package/icons/material-symbols/funnel.svg +1 -1
  283. package/icons/material-symbols/gauge.svg +1 -1
  284. package/icons/material-symbols/gear.svg +1 -1
  285. package/icons/material-symbols/github-logo.svg +1 -1
  286. package/icons/material-symbols/globe.svg +1 -1
  287. package/icons/material-symbols/google-logo.svg +1 -1
  288. package/icons/material-symbols/graduation-cap.svg +1 -1
  289. package/icons/material-symbols/hammer.svg +1 -1
  290. package/icons/material-symbols/hand-heart.svg +1 -1
  291. package/icons/material-symbols/handshake.svg +1 -1
  292. package/icons/material-symbols/hard-drives.svg +1 -1
  293. package/icons/material-symbols/heart.svg +1 -1
  294. package/icons/material-symbols/house.svg +1 -1
  295. package/icons/material-symbols/image-square.svg +1 -1
  296. package/icons/material-symbols/image.svg +1 -1
  297. package/icons/material-symbols/info.svg +1 -1
  298. package/icons/material-symbols/keyboard.svg +1 -1
  299. package/icons/material-symbols/leaf.svg +1 -1
  300. package/icons/material-symbols/lifebuoy.svg +1 -1
  301. package/icons/material-symbols/lightbulb.svg +1 -1
  302. package/icons/material-symbols/link-break.svg +1 -1
  303. package/icons/material-symbols/link-simple.svg +1 -1
  304. package/icons/material-symbols/list-bullets.svg +1 -1
  305. package/icons/material-symbols/list-checks.svg +1 -1
  306. package/icons/material-symbols/list-numbers.svg +1 -1
  307. package/icons/material-symbols/list.svg +1 -1
  308. package/icons/material-symbols/lock-simple-open.svg +1 -1
  309. package/icons/material-symbols/lock-simple.svg +1 -1
  310. package/icons/material-symbols/magnifying-glass.svg +1 -1
  311. package/icons/material-symbols/map-pin.svg +1 -1
  312. package/icons/material-symbols/map-trifold.svg +1 -1
  313. package/icons/material-symbols/medal.svg +1 -1
  314. package/icons/material-symbols/megaphone.svg +1 -1
  315. package/icons/material-symbols/minus.svg +1 -1
  316. package/icons/material-symbols/moon.svg +1 -1
  317. package/icons/material-symbols/newspaper.svg +1 -1
  318. package/icons/material-symbols/pack.json +29 -29
  319. package/icons/material-symbols/paint-brush-broad.svg +1 -1
  320. package/icons/material-symbols/palette.svg +1 -1
  321. package/icons/material-symbols/paper-plane-tilt.svg +1 -1
  322. package/icons/material-symbols/pencil-simple.svg +1 -1
  323. package/icons/material-symbols/phone.svg +1 -1
  324. package/icons/material-symbols/play-fill.svg +1 -1
  325. package/icons/material-symbols/plus.svg +1 -1
  326. package/icons/material-symbols/prohibit.svg +1 -1
  327. package/icons/material-symbols/puzzle-piece.svg +1 -1
  328. package/icons/material-symbols/question.svg +1 -1
  329. package/icons/material-symbols/quotes.svg +1 -1
  330. package/icons/material-symbols/rows.svg +1 -1
  331. package/icons/material-symbols/ruler.svg +1 -1
  332. package/icons/material-symbols/scan.svg +1 -1
  333. package/icons/material-symbols/shapes.svg +1 -1
  334. package/icons/material-symbols/share-network.svg +1 -1
  335. package/icons/material-symbols/shield-check.svg +1 -1
  336. package/icons/material-symbols/sidebar-simple.svg +1 -1
  337. package/icons/material-symbols/sign-out.svg +1 -1
  338. package/icons/material-symbols/signpost.svg +1 -1
  339. package/icons/material-symbols/sliders-horizontal.svg +1 -1
  340. package/icons/material-symbols/smiley.svg +1 -1
  341. package/icons/material-symbols/sparkle.svg +1 -1
  342. package/icons/material-symbols/square-half-bottom.svg +1 -1
  343. package/icons/material-symbols/square.svg +1 -1
  344. package/icons/material-symbols/squares-four.svg +1 -1
  345. package/icons/material-symbols/sticker.svg +1 -1
  346. package/icons/material-symbols/storefront.svg +1 -1
  347. package/icons/material-symbols/sun.svg +1 -1
  348. package/icons/material-symbols/swatches.svg +1 -1
  349. package/icons/material-symbols/tag.svg +1 -1
  350. package/icons/material-symbols/text-aa.svg +1 -1
  351. package/icons/material-symbols/text-align-left.svg +1 -1
  352. package/icons/material-symbols/text-b.svg +1 -1
  353. package/icons/material-symbols/text-h-one.svg +1 -1
  354. package/icons/material-symbols/text-h-three.svg +1 -1
  355. package/icons/material-symbols/text-h-two.svg +1 -1
  356. package/icons/material-symbols/text-italic.svg +1 -1
  357. package/icons/material-symbols/text-t.svg +1 -1
  358. package/icons/material-symbols/ticket.svg +1 -1
  359. package/icons/material-symbols/toggle-right.svg +1 -1
  360. package/icons/material-symbols/train.svg +1 -1
  361. package/icons/material-symbols/trash.svg +1 -1
  362. package/icons/material-symbols/upload-simple.svg +1 -1
  363. package/icons/material-symbols/user-circle.svg +1 -1
  364. package/icons/material-symbols/user.svg +1 -1
  365. package/icons/material-symbols/users-three.svg +1 -1
  366. package/icons/material-symbols/users.svg +1 -1
  367. package/icons/material-symbols/video-camera.svg +1 -1
  368. package/icons/material-symbols/warning-circle.svg +1 -1
  369. package/icons/material-symbols/warning.svg +1 -1
  370. package/icons/material-symbols/wave-sine.svg +1 -1
  371. package/icons/material-symbols/wheelchair.svg +1 -1
  372. package/icons/material-symbols/wrench.svg +1 -1
  373. package/icons/material-symbols/x.svg +1 -1
  374. package/icons/phosphor/pack.json +8 -8
  375. package/lib/apps.mjs +142 -139
  376. package/lib/apps.test.d.mts +1 -0
  377. package/lib/apps.test.mjs +50 -0
  378. package/lib/blocks.mjs +90 -90
  379. package/lib/caseCollisions.d.mts +4 -0
  380. package/lib/caseCollisions.mjs +62 -0
  381. package/lib/caseCollisions.test.d.mts +1 -0
  382. package/lib/caseCollisions.test.mjs +74 -0
  383. package/lib/colors.ts +86 -86
  384. package/lib/contactBar.ts +18 -18
  385. package/lib/contracts.d.mts +15 -0
  386. package/lib/contracts.mjs +17 -0
  387. package/lib/cookieCategories.mjs +28 -28
  388. package/lib/crop.mjs +83 -83
  389. package/lib/csp.mjs +130 -130
  390. package/lib/cspSources.mjs +60 -60
  391. package/lib/dynamic-colors.ts +426 -426
  392. package/lib/git.mjs +64 -64
  393. package/lib/hooks.ts +51 -51
  394. package/lib/inline.ts +20 -20
  395. package/lib/license-texts/Apache-2.0.txt +190 -190
  396. package/lib/license-texts/BSD-2-Clause.txt +24 -24
  397. package/lib/license-texts/BSD-3-Clause.txt +28 -28
  398. package/lib/license-texts/ISC.txt +15 -15
  399. package/lib/license-texts/MIT.txt +21 -21
  400. package/lib/licenses.d.mts +1 -1
  401. package/lib/licenses.mjs +683 -683
  402. package/lib/markdown.ts +42 -42
  403. package/lib/motion-schemes.mjs +5 -5
  404. package/lib/motion.ts +131 -131
  405. package/lib/notice.mjs +34 -34
  406. package/lib/scheduled.mjs +37 -37
  407. package/lib/snackbar.ts +149 -149
  408. package/lib/status.mjs +18 -18
  409. package/lib/storeEntries.d.mts +18 -0
  410. package/lib/storeEntries.mjs +64 -0
  411. package/lib/storeEntries.test.d.mts +1 -0
  412. package/lib/storeEntries.test.mjs +71 -0
  413. package/lib/svg.mjs +231 -231
  414. package/lib/text-colors.mjs +43 -43
  415. package/lib/video-id.mjs +72 -72
  416. package/lib/video.mjs +91 -91
  417. package/package.json +136 -131
  418. package/pages/404.astro +10 -10
  419. package/pages/[...slug].astro +51 -51
  420. package/pages/_headers.txt +31 -31
  421. package/pages/blog/index.astro +10 -10
  422. package/pages/blog/kategori/[category].astro +18 -18
  423. package/pages/editor/code-images.json.js +25 -25
  424. package/pages/editor/index.astro +75 -75
  425. package/pages/editor/manifest.webmanifest.js +23 -23
  426. package/pages/editor/sw.js +33 -33
  427. package/pages/editor/sw.js.js +4 -4
  428. package/pages/editor/thumbnails.json.js +21 -21
  429. package/pages/editor/version.json.js +22 -22
  430. package/pages/headers.js +4 -4
  431. package/pages/index.astro +10 -10
  432. package/pages/ny.astro +6 -6
  433. package/pages/robots.txt.js +3 -3
  434. package/pages/rss.xml.js +20 -20
  435. package/scripts/app-screenshots.mjs +164 -164
  436. package/scripts/app-workers.mjs +6 -6
  437. package/scripts/bin.mjs +12 -12
  438. package/scripts/built-site.mjs +134 -99
  439. package/scripts/check-consent.mjs +113 -113
  440. package/scripts/check-content.mjs +151 -149
  441. package/scripts/check-demo.mjs +24 -24
  442. package/scripts/check-store.mjs +160 -0
  443. package/scripts/check-themes.mjs +45 -45
  444. package/scripts/check-worker.mjs +569 -569
  445. package/scripts/compare-html.mjs +81 -81
  446. package/scripts/declarations.mjs +10 -0
  447. package/scripts/declarations.test.mjs +32 -0
  448. package/scripts/demo-site.mjs +42 -42
  449. package/scripts/editor-icons.mjs +56 -56
  450. package/scripts/editor-roundtrip.mjs +134 -134
  451. package/scripts/editor-screens.mjs +101 -101
  452. package/scripts/git-env.mjs +9 -9
  453. package/scripts/icon-packs.mjs +151 -151
  454. package/scripts/link-counts.mjs +22 -22
  455. package/scripts/page-colors.mjs +42 -42
  456. package/scripts/releases.mjs +80 -80
  457. package/scripts/responsive-images.mjs +94 -94
  458. package/scripts/scheduled-list.mjs +15 -15
  459. package/scripts/theme-screenshots.mjs +88 -88
  460. package/scripts/types.mjs +17 -21
  461. package/scripts/video-thumbnails.mjs +89 -89
  462. package/scripts/worker-modules.mjs +34 -34
  463. package/site/appCookies.mjs +74 -74
  464. package/site/apps.ts +21 -21
  465. package/site/autolinks.mjs +200 -200
  466. package/site/blockScripts.ts +31 -31
  467. package/site/categories.ts +16 -16
  468. package/site/consent.ts +26 -26
  469. package/site/content.config.ts +84 -84
  470. package/site/content.ts +104 -104
  471. package/site/cookieList.ts +17 -17
  472. package/site/defineSite.d.mts +2 -14
  473. package/site/defineSite.mjs +131 -136
  474. package/site/directives/interact.mjs +31 -31
  475. package/site/editor-version.mjs +26 -26
  476. package/site/fill.ts +13 -13
  477. package/site/format.ts +11 -11
  478. package/site/home.ts +66 -66
  479. package/site/iconPacks.mjs +43 -43
  480. package/site/icons.mjs +605 -605
  481. package/site/images.mjs +157 -157
  482. package/site/motion.ts +13 -13
  483. package/site/noindex.mjs +21 -21
  484. package/site/pageColors.ts +110 -110
  485. package/site/popups.ts +98 -98
  486. package/site/schema.ts +189 -189
  487. package/site/sections.ts +3 -3
  488. package/site/seo.ts +28 -28
  489. package/site/site.ts +76 -76
  490. package/site/siteConfig.mjs +8 -8
  491. package/site/siteCsp.mjs +75 -75
  492. package/site/siteModules.mjs +27 -27
  493. package/site/standardPages.ts +59 -59
  494. package/site/storeTheme.d.mts +6 -0
  495. package/site/storeTheme.mjs +29 -0
  496. package/site/storeTheme.test.d.mts +1 -0
  497. package/site/storeTheme.test.mjs +53 -0
  498. package/site/testimonials.ts +48 -48
  499. package/site/text.ts +43 -43
  500. package/site/theme.mjs +483 -472
  501. package/site/tracking.client.ts +225 -225
  502. package/site/tracking.ts +9 -9
  503. package/src/ActionParts.tsx +205 -205
  504. package/src/Advanced.tsx +152 -152
  505. package/src/App.tsx +901 -904
  506. package/src/AppList.tsx +1362 -1345
  507. package/src/BlockDialog.tsx +360 -360
  508. package/src/BlockParts.tsx +652 -652
  509. package/src/BlockSettings.tsx +533 -533
  510. package/src/BrandForm.tsx +164 -164
  511. package/src/Colors.tsx +476 -476
  512. package/src/CommunicationParts.tsx +199 -199
  513. package/src/CompanyForm.tsx +209 -209
  514. package/src/Consents.tsx +175 -175
  515. package/src/ContainmentParts.tsx +185 -185
  516. package/src/Crop.tsx +152 -152
  517. package/src/DataForm.tsx +434 -434
  518. package/src/Deleted.tsx +177 -177
  519. package/src/EntryForm.tsx +740 -740
  520. package/src/FileView.tsx +30 -30
  521. package/src/FormFrame.tsx +237 -237
  522. package/src/Keywords.tsx +225 -225
  523. package/src/Library.tsx +1563 -1563
  524. package/src/Licenses.tsx +336 -336
  525. package/src/LinkSheet.tsx +138 -138
  526. package/src/MarkdownEditor.tsx +1075 -1075
  527. package/src/Media.tsx +677 -677
  528. package/src/NavigationParts.tsx +350 -350
  529. package/src/NavigationRestParts.tsx +247 -247
  530. package/src/Notifications.tsx +59 -59
  531. package/src/PagePicker.tsx +136 -136
  532. package/src/Parts.tsx +991 -991
  533. package/src/PartsShown.tsx +168 -168
  534. package/src/PickerParts.tsx +183 -183
  535. package/src/Redirects.tsx +450 -450
  536. package/src/Screen.tsx +184 -184
  537. package/src/SectionsField.tsx +65 -65
  538. package/src/SelectionParts.tsx +134 -134
  539. package/src/SeoScores.tsx +184 -184
  540. package/src/Sheet.tsx +35 -35
  541. package/src/ThemeStore.tsx +376 -376
  542. package/src/Tokens.tsx +346 -346
  543. package/src/Versions.tsx +214 -214
  544. package/src/addresses.ts +75 -75
  545. package/src/allIcons.ts +7 -7
  546. package/src/appFiles.ts +29 -29
  547. package/src/apps.ts +281 -281
  548. package/src/ask.tsx +123 -123
  549. package/src/blocks.ts +251 -251
  550. package/src/brand.ts +153 -153
  551. package/src/cache.ts +42 -42
  552. package/src/check.ts +285 -285
  553. package/src/checkSite.ts +256 -256
  554. package/src/{consents.ts → consentData.ts} +26 -26
  555. package/src/cookieList.ts +70 -70
  556. package/src/countries.ts +59 -59
  557. package/src/deploy.ts +155 -155
  558. package/src/editor.css +602 -602
  559. package/src/examples.ts +85 -85
  560. package/src/fields.tsx +942 -942
  561. package/src/frontmatter.mjs +42 -42
  562. package/src/github.ts +376 -376
  563. package/src/i18n/da-DK/advanced.ts +28 -28
  564. package/src/i18n/da-DK/app.ts +71 -71
  565. package/src/i18n/da-DK/apps.ts +90 -90
  566. package/src/i18n/da-DK/blockSettings.ts +38 -38
  567. package/src/i18n/da-DK/blocks.ts +408 -408
  568. package/src/i18n/da-DK/check.ts +25 -25
  569. package/src/i18n/da-DK/colors.ts +64 -64
  570. package/src/i18n/da-DK/common.ts +118 -118
  571. package/src/i18n/da-DK/company.ts +24 -24
  572. package/src/i18n/da-DK/consents.ts +23 -23
  573. package/src/i18n/da-DK/forms.ts +116 -116
  574. package/src/i18n/da-DK/history.ts +35 -35
  575. package/src/i18n/da-DK/index.ts +49 -49
  576. package/src/i18n/da-DK/keywords.ts +19 -19
  577. package/src/i18n/da-DK/library.ts +135 -135
  578. package/src/i18n/da-DK/licenses.ts +25 -25
  579. package/src/i18n/da-DK/media.ts +79 -79
  580. package/src/i18n/da-DK/parts.ts +696 -696
  581. package/src/i18n/da-DK/preview.ts +13 -13
  582. package/src/i18n/da-DK/redirects.ts +51 -51
  583. package/src/i18n/da-DK/seo.ts +20 -20
  584. package/src/i18n/da-DK/text.ts +35 -35
  585. package/src/i18n/da-DK/theme.ts +41 -41
  586. package/src/i18n/en-US/advanced.ts +32 -32
  587. package/src/i18n/en-US/app.ts +84 -84
  588. package/src/i18n/en-US/apps.ts +134 -134
  589. package/src/i18n/en-US/blockSettings.ts +48 -48
  590. package/src/i18n/en-US/blocks.ts +441 -441
  591. package/src/i18n/en-US/check.ts +31 -31
  592. package/src/i18n/en-US/colors.ts +82 -82
  593. package/src/i18n/en-US/common.ts +144 -144
  594. package/src/i18n/en-US/company.ts +25 -25
  595. package/src/i18n/en-US/consents.ts +25 -25
  596. package/src/i18n/en-US/forms.ts +145 -145
  597. package/src/i18n/en-US/history.ts +43 -43
  598. package/src/i18n/en-US/index.ts +51 -51
  599. package/src/i18n/en-US/keywords.ts +24 -24
  600. package/src/i18n/en-US/library.ts +176 -176
  601. package/src/i18n/en-US/licenses.ts +36 -36
  602. package/src/i18n/en-US/media.ts +80 -80
  603. package/src/i18n/en-US/parts.ts +718 -718
  604. package/src/i18n/en-US/preview.ts +15 -15
  605. package/src/i18n/en-US/redirects.ts +66 -66
  606. package/src/i18n/en-US/seo.ts +26 -26
  607. package/src/i18n/en-US/text.ts +41 -41
  608. package/src/i18n/en-US/theme.ts +41 -41
  609. package/src/i18n/index.ts +124 -124
  610. package/src/icon.tsx +160 -160
  611. package/src/iconPacks.ts +89 -89
  612. package/src/images.ts +163 -163
  613. package/src/{licenses.ts → licenseData.ts} +79 -79
  614. package/src/markdown.mjs +204 -204
  615. package/src/nav.ts +68 -68
  616. package/src/notFound.ts +28 -28
  617. package/src/{notifications.ts → notificationData.ts} +223 -223
  618. package/src/pagePreview.tsx +204 -204
  619. package/src/r2Caching.ts +55 -55
  620. package/src/{redirects.ts → redirectData.ts} +149 -149
  621. package/src/registry.tsx +188 -188
  622. package/src/schema.ts +417 -417
  623. package/src/sections.mjs +303 -303
  624. package/src/seo.ts +333 -333
  625. package/src/shared.ts +44 -44
  626. package/src/sidebar.ts +31 -31
  627. package/src/site.ts +317 -317
  628. package/src/standard/Block.astro +19 -19
  629. package/src/standard/BlockSite.astro +18 -18
  630. package/src/standard/accordion/index.tsx +47 -47
  631. package/src/standard/accordion/load.ts +23 -23
  632. package/src/standard/accordion/shared.ts +15 -15
  633. package/src/standard/accordion/view.ts +32 -32
  634. package/src/standard/buttons/fields.ts +98 -98
  635. package/src/standard/buttons/index.tsx +23 -23
  636. package/src/standard/buttons/load.ts +34 -34
  637. package/src/standard/buttons/shared.ts +12 -12
  638. package/src/standard/buttons/view.ts +30 -30
  639. package/src/standard/cards/index.tsx +71 -71
  640. package/src/standard/cards/load.ts +17 -17
  641. package/src/standard/cards/shared.ts +27 -27
  642. package/src/standard/cards/view.ts +29 -29
  643. package/src/standard/checklist/index.tsx +27 -27
  644. package/src/standard/checklist/load.ts +5 -5
  645. package/src/standard/checklist/view.ts +13 -13
  646. package/src/standard/cookies/index.tsx +17 -17
  647. package/src/standard/cookies/list.ts +56 -56
  648. package/src/standard/cookies/load.ts +11 -11
  649. package/src/standard/cookies/shared.ts +20 -20
  650. package/src/standard/cookies/view.ts +28 -28
  651. package/src/standard/faq/index.tsx +29 -29
  652. package/src/standard/faq/load.ts +14 -14
  653. package/src/standard/faq/view.ts +21 -21
  654. package/src/standard/head.ts +23 -23
  655. package/src/standard/hero/fields.ts +48 -48
  656. package/src/standard/hero/index.tsx +57 -57
  657. package/src/standard/hero/load.ts +23 -23
  658. package/src/standard/hero/view.ts +38 -38
  659. package/src/standard/links/index.tsx +41 -41
  660. package/src/standard/links/load.ts +17 -17
  661. package/src/standard/links/view.ts +25 -25
  662. package/src/standard/list/index.tsx +50 -50
  663. package/src/standard/list/load.ts +5 -5
  664. package/src/standard/list/view.ts +18 -18
  665. package/src/standard/load.ts +7 -7
  666. package/src/standard/notice/index.tsx +30 -30
  667. package/src/standard/notice/load.ts +16 -16
  668. package/src/standard/notice/view.ts +10 -10
  669. package/src/standard/points/index.tsx +42 -42
  670. package/src/standard/points/load.ts +5 -5
  671. package/src/standard/points/view.ts +12 -12
  672. package/src/standard/posts/index.tsx +132 -132
  673. package/src/standard/posts/load.ts +36 -36
  674. package/src/standard/posts/shared.ts +21 -21
  675. package/src/standard/posts/view.ts +63 -63
  676. package/src/standard/pricecards/index.tsx +57 -57
  677. package/src/standard/pricecards/load.ts +11 -11
  678. package/src/standard/pricecards/view.ts +50 -50
  679. package/src/standard/sharedTexts.ts +6 -6
  680. package/src/standard/site.ts +124 -124
  681. package/src/standard/siteLooks.ts +19 -19
  682. package/src/standard/sites.ts +13 -13
  683. package/src/standard/steps/index.tsx +28 -28
  684. package/src/standard/steps/load.ts +5 -5
  685. package/src/standard/steps/view.ts +11 -11
  686. package/src/standard/testimonials/index.tsx +87 -87
  687. package/src/standard/testimonials/list.ts +43 -43
  688. package/src/standard/testimonials/load.ts +58 -58
  689. package/src/standard/testimonials/shared.ts +13 -13
  690. package/src/standard/testimonials/view.ts +76 -76
  691. package/src/standard/themeBlocks.ts +13 -13
  692. package/src/standard/video/index.tsx +23 -23
  693. package/src/standard/video/load.ts +10 -10
  694. package/src/standard/video/view.ts +42 -42
  695. package/src/standard/view.tsx +105 -105
  696. package/src/standard/views.ts +37 -37
  697. package/src/store.ts +479 -499
  698. package/src/theme.ts +90 -90
  699. package/src/themeUi.ts +97 -97
  700. package/src/themes.ts +91 -91
  701. package/src/ui.tsx +3402 -3402
  702. package/src/virtual.d.ts +5 -5
  703. package/src/when.ts +22 -22
  704. package/starter/.githooks/pre-push +4 -4
  705. package/starter/.nvmrc +1 -1
  706. package/starter/.prettierignore +11 -11
  707. package/starter/.prettierrc.json +7 -7
  708. package/starter/README.md +27 -26
  709. package/starter/astro.config.mjs +5 -5
  710. package/starter/package.json +67 -67
  711. package/starter/public/_redirects +1 -1
  712. package/starter/public/favicon.svg +1 -1
  713. package/starter/public/logo.svg +1 -1
  714. package/starter/src/content/pages/blog.md +8 -8
  715. package/starter/src/content/pages/forside.md +9 -9
  716. package/starter/src/content/pages/kontakt.md +7 -7
  717. package/starter/src/content/pages/privatlivspolitik.md +9 -9
  718. package/starter/src/content/pages/siden-findes-ikke.md +7 -7
  719. package/starter/src/content.config.ts +2 -2
  720. package/starter/src/data/apps.json +1 -1
  721. package/starter/src/data/block-settings.json +3 -3
  722. package/starter/src/data/blocks.json +1 -1
  723. package/starter/src/data/booking.json +6 -6
  724. package/starter/src/data/colors.json +70 -70
  725. package/starter/src/data/contact.json +8 -8
  726. package/starter/src/data/cookies.json +21 -21
  727. package/starter/src/data/csp.json +1 -1
  728. package/starter/src/data/footer.json +15 -15
  729. package/starter/src/data/internal-links.json +4 -4
  730. package/starter/src/data/menu.json +4 -4
  731. package/starter/src/data/mobile-menu.json +4 -4
  732. package/starter/src/data/old-addresses.json +1 -1
  733. package/starter/src/data/redirects.json +1 -1
  734. package/starter/src/data/standard-pages.json +8 -8
  735. package/starter/src/data/theme.json +1 -1
  736. package/starter/src/env.d.ts +2 -2
  737. package/starter/src/site/config.ts +13 -13
  738. package/starter/src/site/worker.js +14 -14
  739. package/starter/tsconfig.json +17 -17
  740. package/starter/worker/apps.js +6 -6
  741. package/starter/wrangler.jsonc +39 -39
  742. package/theme/LICENSE +21 -21
  743. package/theme/blocks/ArrowLink.tsx +14 -14
  744. package/theme/blocks/BlockHead.tsx +26 -26
  745. package/theme/blocks/Panel.tsx +32 -32
  746. package/theme/blocks/Person.tsx +26 -26
  747. package/theme/blocks/accordion/PageRow.tsx +25 -25
  748. package/theme/blocks/accordion/View.tsx +17 -17
  749. package/theme/blocks/buttons/Buttons.tsx +28 -28
  750. package/theme/blocks/buttons/View.tsx +12 -12
  751. package/theme/blocks/cards/LinkCards.tsx +72 -72
  752. package/theme/blocks/cards/View.tsx +20 -20
  753. package/theme/blocks/checklist/Checks.tsx +15 -15
  754. package/theme/blocks/checklist/View.tsx +14 -14
  755. package/theme/blocks/cookies/View.tsx +36 -36
  756. package/theme/blocks/faq/Accordion.tsx +34 -34
  757. package/theme/blocks/faq/View.tsx +16 -16
  758. package/theme/blocks/faq/script.ts +56 -56
  759. package/theme/blocks/hero/View.tsx +44 -44
  760. package/theme/blocks/links/LinkGroups.tsx +24 -24
  761. package/theme/blocks/links/View.tsx +17 -17
  762. package/theme/blocks/list/Rows.tsx +24 -24
  763. package/theme/blocks/list/View.tsx +14 -14
  764. package/theme/blocks/notice/View.tsx +13 -13
  765. package/theme/blocks/points/PointList.tsx +34 -34
  766. package/theme/blocks/points/View.tsx +14 -14
  767. package/theme/blocks/posts/View.tsx +26 -26
  768. package/theme/blocks/pricecards/View.tsx +40 -40
  769. package/theme/blocks/steps/StepList.tsx +23 -23
  770. package/theme/blocks/steps/View.tsx +14 -14
  771. package/theme/blocks/testimonials/List.tsx +46 -46
  772. package/theme/blocks/testimonials/Slider.tsx +44 -44
  773. package/theme/blocks/testimonials/Testimonial.tsx +124 -124
  774. package/theme/blocks/testimonials/View.tsx +21 -21
  775. package/theme/blocks/testimonials/script.ts +52 -52
  776. package/theme/blocks/video/View.tsx +41 -41
  777. package/theme/components/ActionBar.astro +63 -63
  778. package/theme/components/AppSlot.astro +21 -21
  779. package/theme/components/ArrowLink.tsx +21 -21
  780. package/theme/components/BlogIndex.astro +48 -48
  781. package/theme/components/BlogPosts.astro +18 -18
  782. package/theme/components/BlogPosts.tsx +45 -45
  783. package/theme/components/BookButton.tsx +22 -22
  784. package/theme/components/Container.astro +11 -11
  785. package/theme/components/CookieConsent.astro +121 -121
  786. package/theme/components/EndBlock.astro +21 -21
  787. package/theme/components/FontPreload.astro +6 -6
  788. package/theme/components/Footer.astro +85 -85
  789. package/theme/components/Header.astro +186 -186
  790. package/theme/components/Icon.astro +16 -16
  791. package/theme/components/Img.astro +29 -29
  792. package/theme/components/Img.tsx +33 -33
  793. package/theme/components/Inline.astro +11 -11
  794. package/theme/components/Logo.astro +23 -23
  795. package/theme/components/LogoMark.astro +10 -10
  796. package/theme/components/Markdown.astro +17 -17
  797. package/theme/components/MobileMenu.tsx +224 -224
  798. package/theme/components/MobileNav.astro +33 -33
  799. package/theme/components/PageHeader.astro +22 -22
  800. package/theme/components/PageSection.astro +24 -24
  801. package/theme/components/PhoneButton.tsx +11 -11
  802. package/theme/components/Popup.astro +145 -145
  803. package/theme/components/PopupButton.tsx +24 -24
  804. package/theme/components/PopupView.tsx +48 -48
  805. package/theme/components/PostList.tsx +58 -58
  806. package/theme/components/PostMeta.tsx +17 -17
  807. package/theme/components/RichText.astro +28 -28
  808. package/theme/components/Section.astro +29 -29
  809. package/theme/components/SectionHeading.tsx +8 -8
  810. package/theme/components/TextSection.astro +16 -16
  811. package/theme/components/home/PageBlock.astro +19 -19
  812. package/theme/components/home/Text.astro +13 -13
  813. package/theme/components/loadPopup.ts +22 -22
  814. package/theme/components/loadPosts.ts +16 -16
  815. package/theme/components/posts.ts +15 -15
  816. package/theme/editor/looks.mjs +7 -7
  817. package/theme/editor/previews.tsx +56 -56
  818. package/theme/layouts/Base.astro +273 -273
  819. package/theme/layouts/PageLayout.astro +126 -126
  820. package/theme/layouts/PostLayout.astro +137 -137
  821. package/theme/scripts/motion.ts +29 -29
  822. package/theme/styles/base.css +320 -320
  823. package/theme/styles/blocks.css +1808 -1808
  824. package/theme/styles/components.css +206 -206
  825. package/theme/styles/global.css +1302 -1302
  826. package/theme/styles/ui.css +6544 -6544
  827. package/theme/templates/Blog.astro +38 -38
  828. package/theme/templates/Category.astro +19 -19
  829. package/theme/templates/Home.astro +36 -36
  830. package/theme/templates/New.astro +68 -68
  831. package/theme/templates/NotFound.astro +117 -117
  832. package/theme/theme.json +28 -28
  833. package/theme/ui/Badge.tsx +45 -45
  834. package/theme/ui/BottomSheet.tsx +148 -148
  835. package/theme/ui/Button.tsx +79 -79
  836. package/theme/ui/ButtonGroup.tsx +31 -31
  837. package/theme/ui/Card.tsx +45 -45
  838. package/theme/ui/Carousel.tsx +124 -124
  839. package/theme/ui/Checkbox.tsx +59 -59
  840. package/theme/ui/Chip.tsx +101 -101
  841. package/theme/ui/ChipSet.tsx +27 -27
  842. package/theme/ui/DatePicker.tsx +149 -149
  843. package/theme/ui/DatePickerContainer.tsx +316 -316
  844. package/theme/ui/Dialog.tsx +100 -100
  845. package/theme/ui/Divider.tsx +27 -27
  846. package/theme/ui/ExposedDropdownMenu.tsx +77 -77
  847. package/theme/ui/Fab.tsx +88 -88
  848. package/theme/ui/FabMenu.tsx +67 -67
  849. package/theme/ui/IconButton.tsx +78 -78
  850. package/theme/ui/List.tsx +287 -287
  851. package/theme/ui/LoadingIndicator.tsx +29 -29
  852. package/theme/ui/Menu.tsx +278 -278
  853. package/theme/ui/NavigationBar.tsx +62 -62
  854. package/theme/ui/NavigationDrawer.tsx +80 -80
  855. package/theme/ui/NavigationRail.tsx +154 -154
  856. package/theme/ui/ProgressIndicator.tsx +162 -162
  857. package/theme/ui/RadioButton.tsx +50 -50
  858. package/theme/ui/Search.tsx +129 -129
  859. package/theme/ui/SegmentedButton.tsx +49 -49
  860. package/theme/ui/SideSheet.tsx +54 -54
  861. package/theme/ui/Slider.tsx +155 -155
  862. package/theme/ui/Snackbar.tsx +67 -67
  863. package/theme/ui/SplitButton.tsx +67 -67
  864. package/theme/ui/Switch.tsx +60 -60
  865. package/theme/ui/Tabs.tsx +114 -114
  866. package/theme/ui/TextField.tsx +278 -278
  867. package/theme/ui/TimePicker.tsx +128 -128
  868. package/theme/ui/TimePickerContainer.tsx +234 -234
  869. package/theme/ui/Toolbar.tsx +67 -67
  870. package/theme/ui/Tooltip.tsx +58 -58
  871. package/theme/ui/TopAppBar.tsx +77 -77
  872. package/theme/ui/bottomSheet.script.ts +8 -8
  873. package/theme/ui/{bottomSheet.ts → bottomSheetControl.ts} +211 -211
  874. package/theme/ui/carousel.script.ts +28 -28
  875. package/theme/ui/carouselLayout.ts +229 -229
  876. package/theme/ui/checkbox.script.ts +8 -8
  877. package/theme/ui/datePicker.script.ts +302 -302
  878. package/theme/ui/dates.ts +185 -185
  879. package/theme/ui/{dialog.ts → dialogControl.ts} +48 -48
  880. package/theme/ui/dropdownMenu.script.ts +166 -166
  881. package/theme/ui/fabMenu.script.ts +57 -57
  882. package/theme/ui/navigationRail.script.ts +47 -47
  883. package/theme/ui/ripple.script.ts +159 -159
  884. package/theme/ui/search.script.ts +92 -92
  885. package/theme/ui/{sideSheet.ts → sideSheetControl.ts} +48 -48
  886. package/theme/ui/slider.script.ts +12 -12
  887. package/theme/ui/sliderUpdate.ts +52 -52
  888. package/theme/ui/snackbar.script.ts +16 -16
  889. package/theme/ui/splitButton.script.ts +63 -63
  890. package/theme/ui/tabs.script.ts +48 -48
  891. package/theme/ui/textField.script.ts +17 -17
  892. package/theme/ui/textFieldUpdate.ts +44 -44
  893. package/theme/ui/timePicker.script.ts +248 -248
  894. package/theme/ui/toolbar.script.ts +43 -43
  895. package/theme/ui/tooltip.script.ts +68 -68
  896. package/worker/consent.js +141 -141
  897. package/worker/editor-github.js +167 -167
  898. package/worker/editor-host.js +74 -74
  899. package/worker/files.js +401 -401
  900. package/worker/github.js +36 -36
  901. package/worker/index.js +188 -184
  902. package/worker/not-found.js +97 -97
  903. package/worker/oauth.js +95 -95
  904. package/worker/publish.js +52 -52
  905. package/worker/push.js +270 -270
  906. package/worker/r2-status.js +50 -50
  907. package/worker/session.js +58 -58
  908. package/worker/store.js +46 -0
package/lib/crop.mjs CHANGED
@@ -1,83 +1,83 @@
1
- // A picture in the text cut to a format (its sides, as 2:3) in the editor (Crop.tsx), written after
2
- // its address in the Markdown, so the file and the picture stay as they are:
3
- // ![Kontoret](/images/uploads/2024/05/kontor.webp#format=2:3&crop=12.5,0,75,100)
4
- // crop: the part of the picture that is shown, in percent of its width and height: from the left,
5
- // from the top, its width and its height. Shown so on the site (rehypeResponsiveImages, images.mjs)
6
- // and on its card in the editor (MarkdownEditor.tsx). Without crop the middle of it is shown.
7
- // A decorative picture ("Dekorativt billede": screen readers skip it) is marked there too, alone or after
8
- // the format and crop, and drawn with alt="" on the site:
9
- // ![](/images/uploads/2024/05/blomst.webp#decorative)
10
- // The mark is part of the address, so Milkdown keeps it as it is, and a visitor never sees it.
11
-
12
- /** The formats a picture can be cut to, as width:height. */
13
- export const FORMATS = ['1:1', '4:5', '3:4', '2:3', '3:2', '4:3', '16:9'];
14
-
15
- const FORMAT = /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/;
16
-
17
- /** Width divided by height of a format ('2:3'), or 0 when it is none. */
18
- export function ratioOf(format) {
19
- const m = String(format ?? '').match(FORMAT);
20
- return m && +m[1] > 0 && +m[2] > 0 ? +m[1] / +m[2] : 0;
21
- }
22
-
23
- const DECORATIVE = 'decorative';
24
-
25
- /** Whether a picture's address is marked decorative, and the address without the mark (else as it is). */
26
- export function decorativeOf(address) {
27
- const text = String(address);
28
- const at = text.indexOf('#');
29
- const params = at < 0 ? [] : text.slice(at + 1).split('&');
30
- const rest = params.filter((p) => p !== DECORATIVE);
31
- if (rest.length === params.length) return { address: text, decorative: false };
32
- return { address: text.slice(0, at) + (rest.length ? `#${rest.join('&')}` : ''), decorative: true };
33
- }
34
-
35
- /** The address marked decorative, or without the mark. */
36
- export function decorated(address, decorative) {
37
- const plain = decorativeOf(address).address;
38
- return decorative ? `${plain}${plain.includes('#') ? '&' : '#'}${DECORATIVE}` : plain;
39
- }
40
-
41
- /** The picture's own address, its format and crop ([left, top, width, height]) when it has them (an
42
- * address without a format is left as it is), and whether it is decorative. */
43
- export function splitPicture(address) {
44
- const { address: own, decorative } = decorativeOf(address);
45
- const [src, fragment = ''] = own.split('#');
46
- const params = new URLSearchParams(fragment);
47
- const format = params.get('format');
48
- if (!ratioOf(format)) return { src: own, format: '', crop: null, decorative };
49
- const crop = (params.get('crop') ?? '').split(',').map(Number);
50
- const fits = crop.length === 4 && crop.every((n) => Number.isFinite(n) && n >= 0 && n <= 100) && crop[2] > 0 && crop[3] > 0;
51
- return { src, format, crop: fits ? crop : null, decorative };
52
- }
53
-
54
- const round = (n) => Math.round(n * 10) / 10;
55
-
56
- /** The address with the format and crop after it (only the address without a format), marked decorative when it is. */
57
- export function joinPicture(src, format, crop, decorative = false) {
58
- if (!ratioOf(format)) return decorated(src, decorative);
59
- return decorated(`${src}#format=${format}${crop ? `&crop=${crop.map(round).join(',')}` : ''}`, decorative);
60
- }
61
-
62
- /** The largest part of a picture (aspect: its width divided by its height) in a format, in the middle. */
63
- export function middleCrop(aspect, format) {
64
- const ratio = ratioOf(format);
65
- if (!ratio || !aspect) return null;
66
- const [w, h] = aspect > ratio ? [(ratio / aspect) * 100, 100] : [100, (aspect / ratio) * 100];
67
- return [(100 - w) / 2, (100 - h) / 2, w, h];
68
- }
69
-
70
- /** The styles of a cut picture: its box (in the format) and the picture in it, as CSS text; how much
71
- * wider than the box the picture is (for its sizes). Without a crop the picture is its own box. */
72
- export function cropStyle(format, crop) {
73
- const [a, b] = format.split(':');
74
- const aspect = `aspect-ratio: ${a} / ${b}`;
75
- if (!crop) return { box: null, img: `${aspect}; object-fit: cover`, scale: 1 };
76
- const [x, y, w] = crop;
77
- return {
78
- box: aspect,
79
- // The picture's width in percent of the box's, moved by percent of its own size.
80
- img: `width: ${Math.round((100 / w) * 100 * 100) / 100}%; transform: translate(${-round(x)}%, ${-round(y)}%)`,
81
- scale: 100 / w,
82
- };
83
- }
1
+ // A picture in the text cut to a format (its sides, as 2:3) in the editor (Crop.tsx), written after
2
+ // its address in the Markdown, so the file and the picture stay as they are:
3
+ // ![Kontoret](/images/uploads/2024/05/kontor.webp#format=2:3&crop=12.5,0,75,100)
4
+ // crop: the part of the picture that is shown, in percent of its width and height: from the left,
5
+ // from the top, its width and its height. Shown so on the site (rehypeResponsiveImages, images.mjs)
6
+ // and on its card in the editor (MarkdownEditor.tsx). Without crop the middle of it is shown.
7
+ // A decorative picture ("Dekorativt billede": screen readers skip it) is marked there too, alone or after
8
+ // the format and crop, and drawn with alt="" on the site:
9
+ // ![](/images/uploads/2024/05/blomst.webp#decorative)
10
+ // The mark is part of the address, so Milkdown keeps it as it is, and a visitor never sees it.
11
+
12
+ /** The formats a picture can be cut to, as width:height. */
13
+ export const FORMATS = ['1:1', '4:5', '3:4', '2:3', '3:2', '4:3', '16:9'];
14
+
15
+ const FORMAT = /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/;
16
+
17
+ /** Width divided by height of a format ('2:3'), or 0 when it is none. */
18
+ export function ratioOf(format) {
19
+ const m = String(format ?? '').match(FORMAT);
20
+ return m && +m[1] > 0 && +m[2] > 0 ? +m[1] / +m[2] : 0;
21
+ }
22
+
23
+ const DECORATIVE = 'decorative';
24
+
25
+ /** Whether a picture's address is marked decorative, and the address without the mark (else as it is). */
26
+ export function decorativeOf(address) {
27
+ const text = String(address);
28
+ const at = text.indexOf('#');
29
+ const params = at < 0 ? [] : text.slice(at + 1).split('&');
30
+ const rest = params.filter((p) => p !== DECORATIVE);
31
+ if (rest.length === params.length) return { address: text, decorative: false };
32
+ return { address: text.slice(0, at) + (rest.length ? `#${rest.join('&')}` : ''), decorative: true };
33
+ }
34
+
35
+ /** The address marked decorative, or without the mark. */
36
+ export function decorated(address, decorative) {
37
+ const plain = decorativeOf(address).address;
38
+ return decorative ? `${plain}${plain.includes('#') ? '&' : '#'}${DECORATIVE}` : plain;
39
+ }
40
+
41
+ /** The picture's own address, its format and crop ([left, top, width, height]) when it has them (an
42
+ * address without a format is left as it is), and whether it is decorative. */
43
+ export function splitPicture(address) {
44
+ const { address: own, decorative } = decorativeOf(address);
45
+ const [src, fragment = ''] = own.split('#');
46
+ const params = new URLSearchParams(fragment);
47
+ const format = params.get('format');
48
+ if (!ratioOf(format)) return { src: own, format: '', crop: null, decorative };
49
+ const crop = (params.get('crop') ?? '').split(',').map(Number);
50
+ const fits = crop.length === 4 && crop.every((n) => Number.isFinite(n) && n >= 0 && n <= 100) && crop[2] > 0 && crop[3] > 0;
51
+ return { src, format, crop: fits ? crop : null, decorative };
52
+ }
53
+
54
+ const round = (n) => Math.round(n * 10) / 10;
55
+
56
+ /** The address with the format and crop after it (only the address without a format), marked decorative when it is. */
57
+ export function joinPicture(src, format, crop, decorative = false) {
58
+ if (!ratioOf(format)) return decorated(src, decorative);
59
+ return decorated(`${src}#format=${format}${crop ? `&crop=${crop.map(round).join(',')}` : ''}`, decorative);
60
+ }
61
+
62
+ /** The largest part of a picture (aspect: its width divided by its height) in a format, in the middle. */
63
+ export function middleCrop(aspect, format) {
64
+ const ratio = ratioOf(format);
65
+ if (!ratio || !aspect) return null;
66
+ const [w, h] = aspect > ratio ? [(ratio / aspect) * 100, 100] : [100, (aspect / ratio) * 100];
67
+ return [(100 - w) / 2, (100 - h) / 2, w, h];
68
+ }
69
+
70
+ /** The styles of a cut picture: its box (in the format) and the picture in it, as CSS text; how much
71
+ * wider than the box the picture is (for its sizes). Without a crop the picture is its own box. */
72
+ export function cropStyle(format, crop) {
73
+ const [a, b] = format.split(':');
74
+ const aspect = `aspect-ratio: ${a} / ${b}`;
75
+ if (!crop) return { box: null, img: `${aspect}; object-fit: cover`, scale: 1 };
76
+ const [x, y, w] = crop;
77
+ return {
78
+ box: aspect,
79
+ // The picture's width in percent of the box's, moved by percent of its own size.
80
+ img: `width: ${Math.round((100 / w) * 100 * 100) / 100}%; transform: translate(${-round(x)}%, ${-round(y)}%)`,
81
+ scale: 100 / w,
82
+ };
83
+ }
package/lib/csp.mjs CHANGED
@@ -1,130 +1,130 @@
1
- // The Content Security Policy of every page the site builds, as a <meta http-equiv> at the top of its <head>: the
2
- // browser runs only the scripts the build put in the page (each inline one by its hash) and those from the site's own
3
- // address, and loads from elsewhere only what is allowed below. So HTML that gets into a page after the build (in the
4
- // editor's preview, by a fault in a script) can run nothing. What the build wrote is trusted: the addresses of the
5
- // scripts, frames and pictures in a page (HTML written in a page's text, an embed) are allowed on that page.
6
- // Allowed everywhere besides: the videos (video.mjs plays them from YouTube's and Vimeo's players), what the site gives
7
- // (`site`: its measuring, packages/editor/site/siteCsp.mjs) and what each app installed gives (`src/apps/<app>/csp.json`, in
8
- // packages/editor/site/siteCsp.mjs too), with the addresses allowed in the editor (the site's own, config.csp). The editor's page (/editor/) has its own, stricter policy: GitHub, the stills of the videos,
9
- // and nothing of the site's or the apps'. Styles may be inline (the pages have style="…" and React sets them).
10
- // A meta tag cannot say who may frame a page; X-Frame-Options in packages/editor/pages/_headers.txt does.
11
- import { createHash } from 'node:crypto';
12
- import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
13
- import { join } from 'node:path';
14
- import { fileURLToPath } from 'node:url';
15
- import { joined } from './cspSources.mjs';
16
-
17
- // What a site or an app may add to the policy, and the checks of it, without Node, so the site's Worker can use them too.
18
- export { allows, isSource, joined, KINDS, kindOf, sourcesProblems } from './cspSources.mjs';
19
-
20
- /** @typedef {import('./cspSources.mjs').Sources} Sources */
21
-
22
- /** What the videos need on the site: their players, put in place when a video is played (video.mjs). */
23
- const VIDEOS = { frame: ['https://www.youtube-nocookie.com', 'https://player.vimeo.com'] };
24
-
25
- /** What the editor needs (GitHub it asks through the site's Worker, on its own address: github.ts): Vimeo's details of a video and the stills of the videos
26
- * (standard/video/view.ts), and what it makes in the browser (pictures, files) as blob: and data:. */
27
- const EDITOR = {
28
- img: ['blob:', 'https://i.ytimg.com', 'https://i.vimeocdn.com'],
29
- connect: ['blob:', 'data:', 'https://vimeo.com'],
30
- };
31
-
32
- // An inline script that runs (not JSON, JSON-LD or a template).
33
- const RUNS = /^(module|text\/javascript|application\/javascript)?$/i;
34
- const SCRIPT = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
35
- const attr = (attrs, name) => new RegExp(`\\s${name}\\s*=\\s*"([^"]*)"`, 'i').exec(attrs)?.[1];
36
-
37
- /** The hash of each inline script that runs in a page, as CSP writes it. @param {string} html */
38
- export function scriptHashes(html) {
39
- const hashes = new Set();
40
- for (const [, attrs, code] of html.matchAll(SCRIPT))
41
- if (attr(attrs, 'src') === undefined && RUNS.test(attr(attrs, 'type') ?? ''))
42
- hashes.add(`'sha256-${createHash('sha256').update(code, 'utf8').digest('base64')}'`);
43
- return [...hashes];
44
- }
45
-
46
- /** The other addresses (origins) a page's HTML loads its scripts, frames and pictures from.
47
- * @param {string} html @returns {Required<Pick<Sources, 'script' | 'frame' | 'img'>>} */
48
- export function pageOrigins(html) {
49
- const found = { script: new Set(), frame: new Set(), img: new Set() };
50
- const add = (set, url) => {
51
- try {
52
- const { protocol, origin } = new URL(url.replace(/&amp;/g, '&'));
53
- if (protocol === 'https:' || protocol === 'http:') set.add(origin);
54
- } catch {}
55
- };
56
- for (const [, attrs] of html.matchAll(/<script\b([^>]*)>/gi)) add(found.script, attr(attrs, 'src') ?? '');
57
- for (const [, attrs] of html.matchAll(/<iframe\b([^>]*)>/gi)) add(found.frame, attr(attrs, 'src') ?? attr(attrs, 'data-src') ?? '');
58
- for (const [, attrs] of html.matchAll(/<(?:img|source)\b([^>]*)>/gi)) {
59
- add(found.img, attr(attrs, 'src') ?? '');
60
- for (const part of (attr(attrs, 'srcset') ?? '').split(',')) add(found.img, part.trim().split(/\s+/)[0]);
61
- }
62
- return { script: [...found.script], frame: [...found.frame], img: [...found.img] };
63
- }
64
-
65
- /** The policy, from its directives (an empty one is left out, `none` when it must say so).
66
- * @param {Record<string, (string | undefined)[]>} directives */
67
- function policy(directives) {
68
- return Object.entries(directives)
69
- .map(([name, values]) => [name, [...new Set(values.filter(Boolean))]])
70
- .map(([name, values]) => `${name} ${values.length ? values.join(' ') : "'none'"}`)
71
- .join('; ');
72
- }
73
-
74
- /** A page's policy: on the site, or the editor's (`editor`). @param {string} html
75
- * @param {{ editor?: boolean, site?: Sources, images?: string[] }} options */
76
- export function pagePolicy(html, { editor = false, site = {}, images = [] }) {
77
- const page = pageOrigins(html);
78
- const s = editor ? joined(EDITOR, { img: images }) : joined(VIDEOS, site, page);
79
- return policy({
80
- 'default-src': ["'self'"],
81
- 'script-src': ["'self'", ...scriptHashes(html), ...s.script],
82
- 'style-src': ["'self'", "'unsafe-inline'", ...s.style],
83
- 'img-src': ["'self'", 'data:', ...s.img],
84
- 'font-src': ["'self'", 'data:'],
85
- 'connect-src': ["'self'", ...s.connect],
86
- 'media-src': ["'self'", ...(editor ? ['blob:', 'data:'] : [])],
87
- // The editor shows a page in a frame of its own (srcdoc, PageFrame in ui.tsx).
88
- 'frame-src': editor ? ["'self'", 'blob:', 'data:'] : s.frame,
89
- 'worker-src': ["'self'"],
90
- 'form-action': ["'self'", ...s.form],
91
- 'object-src': [],
92
- 'base-uri': ["'self'"],
93
- });
94
- }
95
-
96
- /** The policy as the first thing in a page's <head> (after its charset, which must stay in its first bytes).
97
- * @param {string} html @param {string} value */
98
- export function withPolicy(html, value) {
99
- const meta = `<meta http-equiv="Content-Security-Policy" content="${value.replace(/&/g, '&amp;').replace(/"/g, '&quot;')}">`;
100
- const at = /<meta\s+charset\s*=\s*"?[\w-]+"?\s*\/?>/i.exec(html) ?? /<head\b[^>]*>/i.exec(html);
101
- if (!at) return undefined;
102
- const end = at.index + at[0].length;
103
- return html.slice(0, end) + meta + html.slice(end);
104
- }
105
-
106
- /** The Astro integration that writes the policy into every page built (packages/editor/astro.mjs).
107
- * @param {{ site?: Sources }} [options] @returns {import('astro').AstroIntegration} */
108
- export function csp({ site = {} } = {}) {
109
- return {
110
- name: 'content-security-policy',
111
- hooks: {
112
- 'astro:build:done': ({ dir, logger }) => {
113
- const root = fileURLToPath(dir);
114
- const files = readdirSync(root, { recursive: true, encoding: 'utf8' }).filter((f) => f.endsWith('.html'));
115
- const pages = files.map((file) => ({ file, html: readFileSync(join(root, file), 'utf8') }));
116
- // The editor shows the site's pages in its preview, with their pictures.
117
- const images = [...new Set(pages.flatMap((p) => pageOrigins(p.html).img))];
118
- const missing = [];
119
- for (const { file, html } of pages) {
120
- const editor = file.split(/[\\/]/)[0] === 'editor';
121
- const out = withPolicy(html, pagePolicy(html, { editor, site, images }));
122
- if (out) writeFileSync(join(root, file), out);
123
- else missing.push(file);
124
- }
125
- if (missing.length) throw new Error(`Content-Security-Policy: ingen <head> i ${missing.join(', ')}`);
126
- logger.info(`${pages.length} sider har en Content-Security-Policy.`);
127
- },
128
- },
129
- };
130
- }
1
+ // The Content Security Policy of every page the site builds, as a <meta http-equiv> at the top of its <head>: the
2
+ // browser runs only the scripts the build put in the page (each inline one by its hash) and those from the site's own
3
+ // address, and loads from elsewhere only what is allowed below. So HTML that gets into a page after the build (in the
4
+ // editor's preview, by a fault in a script) can run nothing. What the build wrote is trusted: the addresses of the
5
+ // scripts, frames and pictures in a page (HTML written in a page's text, an embed) are allowed on that page.
6
+ // Allowed everywhere besides: the videos (video.mjs plays them from YouTube's and Vimeo's players), what the site gives
7
+ // (`site`: its measuring, packages/editor/site/siteCsp.mjs) and what each app installed gives (`src/apps/<app>/csp.json`, in
8
+ // packages/editor/site/siteCsp.mjs too), with the addresses allowed in the editor (the site's own, config.csp). The editor's page (/editor/) has its own, stricter policy: GitHub, the stills of the videos,
9
+ // and nothing of the site's or the apps'. Styles may be inline (the pages have style="…" and React sets them).
10
+ // A meta tag cannot say who may frame a page; X-Frame-Options in packages/editor/pages/_headers.txt does.
11
+ import { createHash } from 'node:crypto';
12
+ import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { joined } from './cspSources.mjs';
16
+
17
+ // What a site or an app may add to the policy, and the checks of it, without Node, so the site's Worker can use them too.
18
+ export { allows, isSource, joined, KINDS, kindOf, sourcesProblems } from './cspSources.mjs';
19
+
20
+ /** @typedef {import('./cspSources.mjs').Sources} Sources */
21
+
22
+ /** What the videos need on the site: their players, put in place when a video is played (video.mjs). */
23
+ const VIDEOS = { frame: ['https://www.youtube-nocookie.com', 'https://player.vimeo.com'] };
24
+
25
+ /** What the editor needs (GitHub it asks through the site's Worker, on its own address: github.ts): Vimeo's details of a video and the stills of the videos
26
+ * (standard/video/view.ts), and what it makes in the browser (pictures, files) as blob: and data:. */
27
+ const EDITOR = {
28
+ img: ['blob:', 'https://i.ytimg.com', 'https://i.vimeocdn.com'],
29
+ connect: ['blob:', 'data:', 'https://vimeo.com'],
30
+ };
31
+
32
+ // An inline script that runs (not JSON, JSON-LD or a template).
33
+ const RUNS = /^(module|text\/javascript|application\/javascript)?$/i;
34
+ const SCRIPT = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
35
+ const attr = (attrs, name) => new RegExp(`\\s${name}\\s*=\\s*"([^"]*)"`, 'i').exec(attrs)?.[1];
36
+
37
+ /** The hash of each inline script that runs in a page, as CSP writes it. @param {string} html */
38
+ export function scriptHashes(html) {
39
+ const hashes = new Set();
40
+ for (const [, attrs, code] of html.matchAll(SCRIPT))
41
+ if (attr(attrs, 'src') === undefined && RUNS.test(attr(attrs, 'type') ?? ''))
42
+ hashes.add(`'sha256-${createHash('sha256').update(code, 'utf8').digest('base64')}'`);
43
+ return [...hashes];
44
+ }
45
+
46
+ /** The other addresses (origins) a page's HTML loads its scripts, frames and pictures from.
47
+ * @param {string} html @returns {Required<Pick<Sources, 'script' | 'frame' | 'img'>>} */
48
+ export function pageOrigins(html) {
49
+ const found = { script: new Set(), frame: new Set(), img: new Set() };
50
+ const add = (set, url) => {
51
+ try {
52
+ const { protocol, origin } = new URL(url.replace(/&amp;/g, '&'));
53
+ if (protocol === 'https:' || protocol === 'http:') set.add(origin);
54
+ } catch {}
55
+ };
56
+ for (const [, attrs] of html.matchAll(/<script\b([^>]*)>/gi)) add(found.script, attr(attrs, 'src') ?? '');
57
+ for (const [, attrs] of html.matchAll(/<iframe\b([^>]*)>/gi)) add(found.frame, attr(attrs, 'src') ?? attr(attrs, 'data-src') ?? '');
58
+ for (const [, attrs] of html.matchAll(/<(?:img|source)\b([^>]*)>/gi)) {
59
+ add(found.img, attr(attrs, 'src') ?? '');
60
+ for (const part of (attr(attrs, 'srcset') ?? '').split(',')) add(found.img, part.trim().split(/\s+/)[0]);
61
+ }
62
+ return { script: [...found.script], frame: [...found.frame], img: [...found.img] };
63
+ }
64
+
65
+ /** The policy, from its directives (an empty one is left out, `none` when it must say so).
66
+ * @param {Record<string, (string | undefined)[]>} directives */
67
+ function policy(directives) {
68
+ return Object.entries(directives)
69
+ .map(([name, values]) => [name, [...new Set(values.filter(Boolean))]])
70
+ .map(([name, values]) => `${name} ${values.length ? values.join(' ') : "'none'"}`)
71
+ .join('; ');
72
+ }
73
+
74
+ /** A page's policy: on the site, or the editor's (`editor`). @param {string} html
75
+ * @param {{ editor?: boolean, site?: Sources, images?: string[] }} options */
76
+ export function pagePolicy(html, { editor = false, site = {}, images = [] }) {
77
+ const page = pageOrigins(html);
78
+ const s = editor ? joined(EDITOR, { img: images }) : joined(VIDEOS, site, page);
79
+ return policy({
80
+ 'default-src': ["'self'"],
81
+ 'script-src': ["'self'", ...scriptHashes(html), ...s.script],
82
+ 'style-src': ["'self'", "'unsafe-inline'", ...s.style],
83
+ 'img-src': ["'self'", 'data:', ...s.img],
84
+ 'font-src': ["'self'", 'data:'],
85
+ 'connect-src': ["'self'", ...s.connect],
86
+ 'media-src': ["'self'", ...(editor ? ['blob:', 'data:'] : [])],
87
+ // The editor shows a page in a frame of its own (srcdoc, PageFrame in ui.tsx).
88
+ 'frame-src': editor ? ["'self'", 'blob:', 'data:'] : s.frame,
89
+ 'worker-src': ["'self'"],
90
+ 'form-action': ["'self'", ...s.form],
91
+ 'object-src': [],
92
+ 'base-uri': ["'self'"],
93
+ });
94
+ }
95
+
96
+ /** The policy as the first thing in a page's <head> (after its charset, which must stay in its first bytes).
97
+ * @param {string} html @param {string} value */
98
+ export function withPolicy(html, value) {
99
+ const meta = `<meta http-equiv="Content-Security-Policy" content="${value.replace(/&/g, '&amp;').replace(/"/g, '&quot;')}">`;
100
+ const at = /<meta\s+charset\s*=\s*"?[\w-]+"?\s*\/?>/i.exec(html) ?? /<head\b[^>]*>/i.exec(html);
101
+ if (!at) return undefined;
102
+ const end = at.index + at[0].length;
103
+ return html.slice(0, end) + meta + html.slice(end);
104
+ }
105
+
106
+ /** The Astro integration that writes the policy into every page built (packages/editor/astro.mjs).
107
+ * @param {{ site?: Sources }} [options] @returns {import('astro').AstroIntegration} */
108
+ export function csp({ site = {} } = {}) {
109
+ return {
110
+ name: 'content-security-policy',
111
+ hooks: {
112
+ 'astro:build:done': ({ dir, logger }) => {
113
+ const root = fileURLToPath(dir);
114
+ const files = readdirSync(root, { recursive: true, encoding: 'utf8' }).filter((f) => f.endsWith('.html'));
115
+ const pages = files.map((file) => ({ file, html: readFileSync(join(root, file), 'utf8') }));
116
+ // The editor shows the site's pages in its preview, with their pictures.
117
+ const images = [...new Set(pages.flatMap((p) => pageOrigins(p.html).img))];
118
+ const missing = [];
119
+ for (const { file, html } of pages) {
120
+ const editor = file.split(/[\\/]/)[0] === 'editor';
121
+ const out = withPolicy(html, pagePolicy(html, { editor, site, images }));
122
+ if (out) writeFileSync(join(root, file), out);
123
+ else missing.push(file);
124
+ }
125
+ if (missing.length) throw new Error(`Content-Security-Policy: ingen <head> i ${missing.join(', ')}`);
126
+ logger.info(`${pages.length} sider har en Content-Security-Policy.`);
127
+ },
128
+ },
129
+ };
130
+ }
@@ -1,60 +1,60 @@
1
- // What a site or an app may add to the pages' Content Security Policy (csp.mjs), by kind (an app's csp.json, the
2
- // site's own addresses), and the checks of it. No dependencies, so the site's Worker can use it too (the app
3
- // "Cookiescanner" allows an address the policy stopped).
4
-
5
- /** @typedef {{ script?: string[], style?: string[], img?: string[], connect?: string[], frame?: string[], form?: string[] }} Sources */
6
-
7
- /** The kinds of sources a site or an app can add (a Sources' keys, csp.json's). */
8
- export const KINDS = ['script', 'style', 'img', 'connect', 'frame', 'form'];
9
-
10
- // A source as a site or an app gives it: an address (origin) on https, with `*.` for all under a domain. Nothing that
11
- // lets everything through (`*`, `https:`, 'unsafe-inline', 'unsafe-eval').
12
- const SOURCE = /^https:\/\/(\*\.)?[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/;
13
-
14
- /** Whether a source is one a site or an app may give (see SOURCE). @param {unknown} source */
15
- export const isSource = (source) => typeof source === 'string' && SOURCE.test(source);
16
-
17
- /** The kind of sources a directive of the policy takes, as a browser names it when it stops something
18
- * (securitypolicyviolation's violatedDirective: img-src, script-src-elem …); undefined for one a site or an app cannot
19
- * add to (font-src, media-src …). @param {string} directive */
20
- export function kindOf(directive) {
21
- if (directive === 'form-action') return 'form';
22
- if (directive === 'child-src') return 'frame';
23
- const kind = /^(script|style|img|connect|frame)-src(-elem|-attr)?$/.exec(directive)?.[1];
24
- return kind;
25
- }
26
-
27
- /** What is wrong with sources written in a file (an app's csp.json), each as a sentence; none when they are right.
28
- * @param {unknown} sources @param {string} file */
29
- export function sourcesProblems(sources, file) {
30
- if (!sources || typeof sources !== 'object' || Array.isArray(sources)) return [`${file}: skal være et objekt med ${KINDS.join(', ')}.`];
31
- return Object.entries(sources).flatMap(([kind, values]) =>
32
- !KINDS.includes(kind)
33
- ? [`${file}: "${kind}" er ikke en af ${KINDS.join(', ')}.`]
34
- : !Array.isArray(values)
35
- ? [`${file}: "${kind}" skal være en liste af adresser.`]
36
- : values
37
- .filter((v) => !isSource(v))
38
- .map(
39
- (v) =>
40
- `${file}: ${JSON.stringify(v)} under "${kind}" er ikke en adresse som https://eksempel.dk eller https://*.eksempel.dk.`,
41
- ),
42
- );
43
- }
44
-
45
- /** Whether the sources allow an address (its origin, or one under a `*.` domain), as any kind or as `kind`.
46
- * @param {Sources} sources @param {string} url @param {string} [kind] */
47
- export function allows(sources, url, kind) {
48
- const { origin, host } = new URL(url);
49
- return (kind ? (sources[kind] ?? []) : Object.values(sources).flat()).some(
50
- (s) => s === origin || (s.startsWith('https://*.') && host.endsWith(s.slice('https://*'.length))),
51
- );
52
- }
53
-
54
- /** The sources together. @param {Sources[]} all @returns {Required<Sources>} */
55
- export function joined(...all) {
56
- /** @type {Required<Sources>} */
57
- const sum = Object.fromEntries(KINDS.map((kind) => [kind, []]));
58
- for (const sources of all) for (const kind of KINDS) sum[kind].push(...(sources[kind] ?? []));
59
- return sum;
60
- }
1
+ // What a site or an app may add to the pages' Content Security Policy (csp.mjs), by kind (an app's csp.json, the
2
+ // site's own addresses), and the checks of it. No dependencies, so the site's Worker can use it too (the app
3
+ // "Cookiescanner" allows an address the policy stopped).
4
+
5
+ /** @typedef {{ script?: string[], style?: string[], img?: string[], connect?: string[], frame?: string[], form?: string[] }} Sources */
6
+
7
+ /** The kinds of sources a site or an app can add (a Sources' keys, csp.json's). */
8
+ export const KINDS = ['script', 'style', 'img', 'connect', 'frame', 'form'];
9
+
10
+ // A source as a site or an app gives it: an address (origin) on https, with `*.` for all under a domain. Nothing that
11
+ // lets everything through (`*`, `https:`, 'unsafe-inline', 'unsafe-eval').
12
+ const SOURCE = /^https:\/\/(\*\.)?[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/;
13
+
14
+ /** Whether a source is one a site or an app may give (see SOURCE). @param {unknown} source */
15
+ export const isSource = (source) => typeof source === 'string' && SOURCE.test(source);
16
+
17
+ /** The kind of sources a directive of the policy takes, as a browser names it when it stops something
18
+ * (securitypolicyviolation's violatedDirective: img-src, script-src-elem …); undefined for one a site or an app cannot
19
+ * add to (font-src, media-src …). @param {string} directive */
20
+ export function kindOf(directive) {
21
+ if (directive === 'form-action') return 'form';
22
+ if (directive === 'child-src') return 'frame';
23
+ const kind = /^(script|style|img|connect|frame)-src(-elem|-attr)?$/.exec(directive)?.[1];
24
+ return kind;
25
+ }
26
+
27
+ /** What is wrong with sources written in a file (an app's csp.json), each as a sentence; none when they are right.
28
+ * @param {unknown} sources @param {string} file */
29
+ export function sourcesProblems(sources, file) {
30
+ if (!sources || typeof sources !== 'object' || Array.isArray(sources)) return [`${file}: skal være et objekt med ${KINDS.join(', ')}.`];
31
+ return Object.entries(sources).flatMap(([kind, values]) =>
32
+ !KINDS.includes(kind)
33
+ ? [`${file}: "${kind}" er ikke en af ${KINDS.join(', ')}.`]
34
+ : !Array.isArray(values)
35
+ ? [`${file}: "${kind}" skal være en liste af adresser.`]
36
+ : values
37
+ .filter((v) => !isSource(v))
38
+ .map(
39
+ (v) =>
40
+ `${file}: ${JSON.stringify(v)} under "${kind}" er ikke en adresse som https://eksempel.dk eller https://*.eksempel.dk.`,
41
+ ),
42
+ );
43
+ }
44
+
45
+ /** Whether the sources allow an address (its origin, or one under a `*.` domain), as any kind or as `kind`.
46
+ * @param {Sources} sources @param {string} url @param {string} [kind] */
47
+ export function allows(sources, url, kind) {
48
+ const { origin, host } = new URL(url);
49
+ return (kind ? (sources[kind] ?? []) : Object.values(sources).flat()).some(
50
+ (s) => s === origin || (s.startsWith('https://*.') && host.endsWith(s.slice('https://*'.length))),
51
+ );
52
+ }
53
+
54
+ /** The sources together. @param {Sources[]} all @returns {Required<Sources>} */
55
+ export function joined(...all) {
56
+ /** @type {Required<Sources>} */
57
+ const sum = Object.fromEntries(KINDS.map((kind) => [kind, []]));
58
+ for (const sources of all) for (const kind of KINDS) sum[kind].push(...(sources[kind] ?? []));
59
+ return sum;
60
+ }