@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/worker/files.js CHANGED
@@ -1,401 +1,401 @@
1
- // The site's files in its repository on GitHub, for the editor (packages/editor/worker/editor-github.js) and the Worker's own saves
2
- // (packages/editor/worker/github.js), through a cache in R2 (the binding FILES in wrangler.jsonc). See docs/editor.md, Sådan gemmer den.
3
- //
4
- // Git's objects never change, so the cache never has to be told that something changed: a file's content is kept under
5
- // its blob id and a folder under its tree id (its entries' names, ids and modes, and whether GitHub calls each file
6
- // binary), both written once, and only when they give git's own id (packages/editor/lib/git.mjs). A commit's files are
7
- // its root tree. GitHub is asked for the branch's head every time (one small request) and otherwise only for what R2
8
- // does not have; what the Worker saves itself goes into R2 at once. Without the binding (a preview of a branch), or
9
- // when R2 fails, everything comes from GitHub as before, and a save never waits for R2. The keys start with the
10
- // repository (src/site/config.ts), so the sites' files can never mix:
11
- // <owner>/<repo>/blob/<oid> a file's bytes
12
- // <owner>/<repo>/tree/<oid> a folder: { "entries": [{ name, oid, mode, type, binary? }] }, in git's order
13
- // Each request logs how many requests went to GitHub and how many objects were read and written in R2.
14
- import { config } from '@site/config';
15
- import { blobId, sortTree, TREE_MODE, treeId } from '../lib/git.mjs';
16
-
17
- const { owner, name, branch } = config.repo;
18
- const KEY = `${owner}/${name}`;
19
- const API = 'https://api.github.com';
20
- const encoder = new TextEncoder();
21
- // GitHub's text of a file is its bytes as UTF-8; bytes that are not (another encoding) are asked of GitHub.
22
- const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
23
- // The most objects asked of GitHub in one request.
24
- const BATCH = 100;
25
- const FILE_MODE = 0o100644;
26
-
27
- /** An answer from GitHub that was not a success: its status (401: the sign-in is no longer valid). */
28
- export class GitHubError extends Error {
29
- constructor(message, status) {
30
- super(message);
31
- this.status = status;
32
- }
33
- }
34
-
35
- // Each commit's root tree, as GitHub gave it (a commit never changes).
36
- const roots = new Map();
37
-
38
- /**
39
- * The site's files for one request, asked with `token` (the editor's sign-in, or the Worker's GITHUB_PUBLISH_TOKEN).
40
- * `ctx`: the request's, so R2 is written after the answer has gone; without it, `finish` waits for the writing.
41
- */
42
- export function filesOf(env, token, ctx) {
43
- const bucket = env.FILES;
44
- const count = { github: 0, read: 0, written: 0 };
45
- const pending = [];
46
- // The trees already read in this request.
47
- const trees = new Map();
48
-
49
- /** fetch for GitHub, counted. */
50
- function ask(url, init = {}) {
51
- count.github++;
52
- return fetch(url, {
53
- ...init,
54
- headers: { authorization: `Bearer ${token}`, 'user-agent': `${name}-worker`, ...init.headers },
55
- });
56
- }
57
-
58
- async function graphql(query, variables = {}) {
59
- const res = await ask(`${API}/graphql`, {
60
- method: 'POST',
61
- headers: { 'content-type': 'application/json' },
62
- body: JSON.stringify({ query, variables }),
63
- });
64
- const body = await res.json().catch(() => ({}));
65
- if (!res.ok || body.errors?.length) throw new GitHubError(body.errors?.[0]?.message ?? body.message ?? '', res.status);
66
- return body.data;
67
- }
68
-
69
- async function stored(kind, oid) {
70
- if (!bucket) return null;
71
- try {
72
- count.read++;
73
- const object = await bucket.get(`${KEY}/${kind}/${oid}`);
74
- return object ? new Uint8Array(await object.arrayBuffer()) : null;
75
- } catch {
76
- return null;
77
- }
78
- }
79
-
80
- // Writes once: `check` first (an R2 read is cheaper than a write), when the object may be there already.
81
- function keep(kind, oid, value, check = false) {
82
- if (!bucket) return;
83
- later(
84
- (async () => {
85
- const key = `${KEY}/${kind}/${oid}`;
86
- if (check) {
87
- count.read++;
88
- if (await bucket.head(key)) return;
89
- }
90
- count.written++;
91
- await bucket.put(key, value);
92
- })().catch(() => {}),
93
- );
94
- }
95
-
96
- function later(promise) {
97
- pending.push(promise);
98
- ctx?.waitUntil(promise);
99
- }
100
-
101
- /** The commit the branch is at and its root tree, always from GitHub. */
102
- async function head() {
103
- const data = await graphql(
104
- `
105
- query ($owner: String!, $name: String!, $ref: String!) {
106
- repository(owner: $owner, name: $name) {
107
- ref(qualifiedName: $ref) {
108
- target {
109
- oid
110
- ... on Commit {
111
- tree {
112
- oid
113
- }
114
- }
115
- }
116
- }
117
- }
118
- }
119
- `,
120
- { owner, name, ref: `refs/heads/${branch}` },
121
- );
122
- const { oid, tree } = data.repository.ref.target;
123
- roots.set(oid, tree.oid);
124
- return { commit: oid, tree: tree.oid };
125
- }
126
-
127
- /** A commit's root tree. */
128
- async function rootOf(commit) {
129
- if (!roots.has(commit)) {
130
- const data = await graphql(
131
- `
132
- query ($owner: String!, $name: String!, $oid: GitObjectID!) {
133
- repository(owner: $owner, name: $name) {
134
- object(oid: $oid) {
135
- ... on Commit {
136
- tree {
137
- oid
138
- }
139
- }
140
- }
141
- }
142
- }
143
- `,
144
- { owner, name, oid: commit },
145
- );
146
- roots.set(commit, data.repository.object.tree.oid);
147
- }
148
- return roots.get(commit);
149
- }
150
-
151
- /** Trees by their ids: their entries, or null for what is not a tree. From R2, the rest from GitHub in one request. */
152
- async function treesOf(oids) {
153
- const wanted = [...new Set(oids)].filter((oid) => !trees.has(oid));
154
- const found = await Promise.all(wanted.map((oid) => stored('tree', oid)));
155
- const missing = [];
156
- wanted.forEach((oid, i) => {
157
- try {
158
- if (found[i]) return trees.set(oid, JSON.parse(new TextDecoder().decode(found[i])).entries);
159
- } catch {}
160
- missing.push(oid);
161
- });
162
- for (let at = 0; at < missing.length; at += BATCH) {
163
- const some = missing.slice(at, at + BATCH);
164
- const data = await graphql(
165
- `query ($owner: String!, $name: String!) { repository(owner: $owner, name: $name) {
166
- ${some.map((oid, i) => `t${i}: object(oid: ${JSON.stringify(oid)}) { ... on Tree { entries { name oid mode type object { ... on Blob { isBinary } } } } }`).join('\n')}
167
- } }`,
168
- { owner, name },
169
- );
170
- await Promise.all(
171
- some.map(async (oid, i) => {
172
- const got = data.repository[`t${i}`]?.entries;
173
- const entries = got
174
- ? sortTree(
175
- got.map((e) => ({
176
- name: e.name,
177
- oid: e.oid,
178
- mode: e.mode,
179
- type: e.type,
180
- ...(e.type === 'blob' ? { binary: e.object?.isBinary === true } : {}),
181
- })),
182
- )
183
- : null;
184
- trees.set(oid, entries);
185
- if (entries && (await treeId(entries)) === oid) keep('tree', oid, JSON.stringify({ entries }));
186
- }),
187
- );
188
- }
189
- return new Map(oids.map((oid) => [oid, trees.get(oid) ?? null]));
190
- }
191
-
192
- /** The trees at some paths (`{ root, path }`, '' the root itself): their ids, or null where there is no folder. */
193
- async function foldersAt(places) {
194
- let at = places.map(({ root, path }) => ({ oid: root, rest: path ? path.split('/') : [] }));
195
- for (;;) {
196
- const on = at.filter((a) => a.oid && a.rest.length);
197
- if (!on.length) return at.map((a) => a.oid);
198
- const got = await treesOf(on.map((a) => a.oid));
199
- at = at.map((a) => {
200
- if (!a.oid || !a.rest.length) return a;
201
- const entry = got.get(a.oid)?.find((e) => e.name === a.rest[0]);
202
- return { oid: entry?.type === 'tree' ? entry.oid : null, rest: a.rest.slice(1) };
203
- });
204
- }
205
- }
206
-
207
- /** What is at some paths under a root tree: each one's entry (a file, a folder), or null. */
208
- async function entriesAt(root, paths) {
209
- const dirs = await foldersAt(paths.map((p) => ({ root, path: parentOf(p) })));
210
- const got = await treesOf(dirs.filter(Boolean));
211
- return paths.map((p, i) => (dirs[i] && got.get(dirs[i])?.find((e) => e.name === baseOf(p))) || null);
212
- }
213
-
214
- /** The files in some folders under a root tree, `depth` levels of folders down: `{ path, oid, binary }`, a list for
215
- * each folder (empty when it is not there). */
216
- async function filesIn(root, folders, depth) {
217
- const starts = await foldersAt(folders.map((path) => ({ root, path })));
218
- const lists = folders.map(() => []);
219
- let level = starts.flatMap((oid, i) => (oid ? [{ oid, path: folders[i], list: lists[i] }] : []));
220
- for (let d = 1; d <= depth && level.length; d++) {
221
- const got = await treesOf(level.map((l) => l.oid));
222
- const next = [];
223
- for (const l of level)
224
- for (const e of got.get(l.oid) ?? []) {
225
- const path = l.path ? `${l.path}/${e.name}` : e.name;
226
- if (e.type === 'tree') next.push({ oid: e.oid, path, list: l.list });
227
- else if (e.type === 'blob') l.list.push({ path, oid: e.oid, binary: e.binary === true });
228
- }
229
- level = next;
230
- }
231
- return lists;
232
- }
233
-
234
- /** Files' texts by their blob ids, as GitHub gives them: null for one GitHub calls binary, undefined for one that is
235
- * not there. From R2, the rest from GitHub in one request. */
236
- async function texts(oids) {
237
- const wanted = [...new Set(oids)];
238
- const found = await Promise.all(wanted.map((oid) => stored('blob', oid)));
239
- const out = new Map();
240
- const missing = [];
241
- wanted.forEach((oid, i) => {
242
- const text = found[i] && textOf(found[i]);
243
- if (text === null || text === undefined) missing.push(oid);
244
- else out.set(oid, text);
245
- });
246
- for (let at = 0; at < missing.length; at += BATCH) {
247
- const some = missing.slice(at, at + BATCH);
248
- const data = await graphql(
249
- `query ($owner: String!, $name: String!) { repository(owner: $owner, name: $name) {
250
- ${some.map((oid, i) => `b${i}: object(oid: ${JSON.stringify(oid)}) { ... on Blob { text isBinary isTruncated } }`).join('\n')}
251
- } }`,
252
- { owner, name },
253
- );
254
- await Promise.all(
255
- some.map(async (oid, i) => {
256
- const blob = data.repository[`b${i}`];
257
- if (!blob) return;
258
- if (blob.isBinary || blob.text == null) return out.set(oid, null);
259
- out.set(oid, blob.text);
260
- // Kept only whole and unchanged: the text's bytes are the file's.
261
- const bytes = encoder.encode(blob.text);
262
- if (!blob.isTruncated && (await blobId(bytes)) === oid) keep('blob', oid, bytes);
263
- }),
264
- );
265
- }
266
- return new Map(oids.map((oid) => [oid, out.get(oid)]));
267
- }
268
-
269
- /** A file's bytes by its blob id: from R2, else from GitHub (kept when they give the id). `repo`: another repository
270
- * (an app in the store), only from GitHub. Null when GitHub has no such file. */
271
- async function bytes(oid, repo = config.repo) {
272
- const own = repo.owner === owner && repo.name === name;
273
- const found = own ? await stored('blob', oid) : null;
274
- if (found) return found;
275
- const res = await ask(`${API}/repos/${repo.owner}/${repo.name}/git/blobs/${oid}`, {
276
- headers: { accept: 'application/vnd.github.raw+json' },
277
- });
278
- if (res.status === 404) return null;
279
- if (!res.ok) throw new GitHubError('', res.status);
280
- const got = new Uint8Array(await res.arrayBuffer());
281
- if (own && (await blobId(got)) === oid) keep('blob', oid, got);
282
- return got;
283
- }
284
-
285
- /**
286
- * Saves files in one commit on the branch, if it is still at `at` (`{ commit, tree }`). `changes`: `{ path, contents }`,
287
- * the contents as base64 (as GitHub takes them), null to delete the file. Gives the new commit and each saved file's
288
- * blob id. The new blobs and folders go into R2 afterwards: worked out from the folders before the commit, and kept
289
- * only when they give the root tree GitHub made (so they are the commit's).
290
- */
291
- async function save(at, message, changes) {
292
- const kept = changes.filter((c) => c.contents !== null);
293
- const data = await graphql(
294
- `mutation ($input: CreateCommitOnBranchInput!) {
295
- createCommitOnBranch(input: $input) { commit { oid tree { oid }
296
- ${kept.map((c, i) => `f${i}: file(path: ${JSON.stringify(c.path)}) { oid object { ... on Blob { isBinary } } }`).join('\n')}
297
- } }
298
- }`,
299
- {
300
- input: {
301
- branch: { repositoryNameWithOwner: KEY, branchName: branch },
302
- expectedHeadOid: at.commit,
303
- message: { headline: message },
304
- fileChanges: {
305
- additions: kept.map((c) => ({ path: c.path, contents: c.contents })),
306
- deletions: changes.filter((c) => c.contents === null).map((c) => ({ path: c.path })),
307
- },
308
- },
309
- },
310
- );
311
- const done = data.createCommitOnBranch.commit;
312
- roots.set(done.oid, done.tree.oid);
313
- const saved = new Map(kept.map((c, i) => [c.path, { oid: done[`f${i}`].oid, binary: done[`f${i}`].object?.isBinary === true }]));
314
- if (bucket) later(keepSaved(at.tree, done.tree.oid, changes, saved).catch(() => {}));
315
- return { commit: done.oid, blobs: Object.fromEntries([...saved].map(([path, f]) => [path, f.oid])) };
316
- }
317
-
318
- async function keepSaved(before, after, changes, saved) {
319
- // The folders the changes are in, with all above them, and their entries before the commit (none: a new folder).
320
- const dirs = [...new Set(changes.flatMap((c) => ancestorsOf(c.path)))];
321
- const oids = await foldersAt(dirs.map((path) => ({ root: before, path })));
322
- const got = await treesOf(oids.filter(Boolean));
323
- if (oids.some((oid) => oid && !got.get(oid))) return;
324
- const folders = new Map(dirs.map((d, i) => [d, new Map((oids[i] ? got.get(oids[i]) : []).map((e) => [e.name, e]))]));
325
- for (const c of changes) {
326
- const entries = folders.get(parentOf(c.path));
327
- const was = entries.get(baseOf(c.path));
328
- if (c.contents === null) entries.delete(baseOf(c.path));
329
- else {
330
- const { oid, binary } = saved.get(c.path);
331
- entries.set(baseOf(c.path), { name: baseOf(c.path), oid, mode: was?.type === 'blob' ? was.mode : FILE_MODE, type: 'blob', binary });
332
- }
333
- }
334
- // From the deepest folder up: each one's new id in the folder above it (an empty folder is gone).
335
- const made = [];
336
- for (const dir of dirs.sort((a, b) => depthOf(b) - depthOf(a))) {
337
- const entries = sortTree([...folders.get(dir).values()]);
338
- const oid = entries.length ? await treeId(entries) : null;
339
- if (oid) made.push({ oid, entries });
340
- if (!dir) {
341
- if (oid !== after) return;
342
- continue;
343
- }
344
- const above = folders.get(parentOf(dir));
345
- if (oid) above.set(baseOf(dir), { name: baseOf(dir), oid, mode: TREE_MODE, type: 'tree' });
346
- else above.delete(baseOf(dir));
347
- }
348
- for (const { oid, entries } of made) keep('tree', oid, JSON.stringify({ entries }), true);
349
- for (const c of changes) {
350
- if (c.contents === null) continue;
351
- const contents = fromBase64(c.contents);
352
- if ((await blobId(contents)) === saved.get(c.path).oid) keep('blob', saved.get(c.path).oid, contents, true);
353
- }
354
- }
355
-
356
- /** Logs what the request asked of GitHub and R2 (`what`: which), once R2 is written. */
357
- function finish(what) {
358
- const all = Promise.allSettled(pending).then(() => {
359
- if (count.github || count.read || count.written)
360
- console.log(
361
- `Filer (${what}): ${count.github} til GitHub, ${count.read} læst og ${count.written} skrevet i R2${bucket ? '' : ' (ingen R2)'}`,
362
- );
363
- });
364
- ctx?.waitUntil(all);
365
- return ctx ? Promise.resolve() : all;
366
- }
367
-
368
- return { ask, graphql, head, rootOf, treesOf, foldersAt, entriesAt, filesIn, texts, bytes, save, finish };
369
- }
370
-
371
- // A file's text, as GitHub gives it: undefined when its bytes are not UTF-8, or start with a byte order mark, which
372
- // GitHub may give otherwise (both asked of GitHub).
373
- function textOf(bytes) {
374
- try {
375
- const text = decoder.decode(bytes);
376
- return text.startsWith('') ? undefined : text;
377
- } catch {
378
- return undefined;
379
- }
380
- }
381
-
382
- const parentOf = (path) => path.split('/').slice(0, -1).join('/');
383
- const baseOf = (path) => path.split('/').at(-1);
384
- const depthOf = (path) => (path ? path.split('/').length : 0);
385
- // The folders a path is in, from the root ('') down.
386
- const ancestorsOf = (path) => path.split('/').map((_, i, parts) => parts.slice(0, i).join('/'));
387
-
388
- /** Bytes as base64, as GitHub takes a file's contents. */
389
- export function toBase64(bytes) {
390
- let binary = '';
391
- for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
392
- return btoa(binary);
393
- }
394
-
395
- function fromBase64(text) {
396
- if (Uint8Array.fromBase64) return Uint8Array.fromBase64(text);
397
- const binary = atob(text);
398
- const bytes = new Uint8Array(binary.length);
399
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
400
- return bytes;
401
- }
1
+ // The site's files in its repository on GitHub, for the editor (packages/editor/worker/editor-github.js) and the Worker's own saves
2
+ // (packages/editor/worker/github.js), through a cache in R2 (the binding FILES in wrangler.jsonc). See docs/editor.md, Sådan gemmer den.
3
+ //
4
+ // Git's objects never change, so the cache never has to be told that something changed: a file's content is kept under
5
+ // its blob id and a folder under its tree id (its entries' names, ids and modes, and whether GitHub calls each file
6
+ // binary), both written once, and only when they give git's own id (packages/editor/lib/git.mjs). A commit's files are
7
+ // its root tree. GitHub is asked for the branch's head every time (one small request) and otherwise only for what R2
8
+ // does not have; what the Worker saves itself goes into R2 at once. Without the binding (a preview of a branch), or
9
+ // when R2 fails, everything comes from GitHub as before, and a save never waits for R2. The keys start with the
10
+ // repository (src/site/config.ts), so the sites' files can never mix:
11
+ // <owner>/<repo>/blob/<oid> a file's bytes
12
+ // <owner>/<repo>/tree/<oid> a folder: { "entries": [{ name, oid, mode, type, binary? }] }, in git's order
13
+ // Each request logs how many requests went to GitHub and how many objects were read and written in R2.
14
+ import { config } from '@site/config';
15
+ import { blobId, sortTree, TREE_MODE, treeId } from '../lib/git.mjs';
16
+
17
+ const { owner, name, branch } = config.repo;
18
+ const KEY = `${owner}/${name}`;
19
+ const API = 'https://api.github.com';
20
+ const encoder = new TextEncoder();
21
+ // GitHub's text of a file is its bytes as UTF-8; bytes that are not (another encoding) are asked of GitHub.
22
+ const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
23
+ // The most objects asked of GitHub in one request.
24
+ const BATCH = 100;
25
+ const FILE_MODE = 0o100644;
26
+
27
+ /** An answer from GitHub that was not a success: its status (401: the sign-in is no longer valid). */
28
+ export class GitHubError extends Error {
29
+ constructor(message, status) {
30
+ super(message);
31
+ this.status = status;
32
+ }
33
+ }
34
+
35
+ // Each commit's root tree, as GitHub gave it (a commit never changes).
36
+ const roots = new Map();
37
+
38
+ /**
39
+ * The site's files for one request, asked with `token` (the editor's sign-in, or the Worker's GITHUB_PUBLISH_TOKEN).
40
+ * `ctx`: the request's, so R2 is written after the answer has gone; without it, `finish` waits for the writing.
41
+ */
42
+ export function filesOf(env, token, ctx) {
43
+ const bucket = env.FILES;
44
+ const count = { github: 0, read: 0, written: 0 };
45
+ const pending = [];
46
+ // The trees already read in this request.
47
+ const trees = new Map();
48
+
49
+ /** fetch for GitHub, counted. */
50
+ function ask(url, init = {}) {
51
+ count.github++;
52
+ return fetch(url, {
53
+ ...init,
54
+ headers: { authorization: `Bearer ${token}`, 'user-agent': `${name}-worker`, ...init.headers },
55
+ });
56
+ }
57
+
58
+ async function graphql(query, variables = {}) {
59
+ const res = await ask(`${API}/graphql`, {
60
+ method: 'POST',
61
+ headers: { 'content-type': 'application/json' },
62
+ body: JSON.stringify({ query, variables }),
63
+ });
64
+ const body = await res.json().catch(() => ({}));
65
+ if (!res.ok || body.errors?.length) throw new GitHubError(body.errors?.[0]?.message ?? body.message ?? '', res.status);
66
+ return body.data;
67
+ }
68
+
69
+ async function stored(kind, oid) {
70
+ if (!bucket) return null;
71
+ try {
72
+ count.read++;
73
+ const object = await bucket.get(`${KEY}/${kind}/${oid}`);
74
+ return object ? new Uint8Array(await object.arrayBuffer()) : null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ // Writes once: `check` first (an R2 read is cheaper than a write), when the object may be there already.
81
+ function keep(kind, oid, value, check = false) {
82
+ if (!bucket) return;
83
+ later(
84
+ (async () => {
85
+ const key = `${KEY}/${kind}/${oid}`;
86
+ if (check) {
87
+ count.read++;
88
+ if (await bucket.head(key)) return;
89
+ }
90
+ count.written++;
91
+ await bucket.put(key, value);
92
+ })().catch(() => {}),
93
+ );
94
+ }
95
+
96
+ function later(promise) {
97
+ pending.push(promise);
98
+ ctx?.waitUntil(promise);
99
+ }
100
+
101
+ /** The commit the branch is at and its root tree, always from GitHub. */
102
+ async function head() {
103
+ const data = await graphql(
104
+ `
105
+ query ($owner: String!, $name: String!, $ref: String!) {
106
+ repository(owner: $owner, name: $name) {
107
+ ref(qualifiedName: $ref) {
108
+ target {
109
+ oid
110
+ ... on Commit {
111
+ tree {
112
+ oid
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ `,
120
+ { owner, name, ref: `refs/heads/${branch}` },
121
+ );
122
+ const { oid, tree } = data.repository.ref.target;
123
+ roots.set(oid, tree.oid);
124
+ return { commit: oid, tree: tree.oid };
125
+ }
126
+
127
+ /** A commit's root tree. */
128
+ async function rootOf(commit) {
129
+ if (!roots.has(commit)) {
130
+ const data = await graphql(
131
+ `
132
+ query ($owner: String!, $name: String!, $oid: GitObjectID!) {
133
+ repository(owner: $owner, name: $name) {
134
+ object(oid: $oid) {
135
+ ... on Commit {
136
+ tree {
137
+ oid
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ `,
144
+ { owner, name, oid: commit },
145
+ );
146
+ roots.set(commit, data.repository.object.tree.oid);
147
+ }
148
+ return roots.get(commit);
149
+ }
150
+
151
+ /** Trees by their ids: their entries, or null for what is not a tree. From R2, the rest from GitHub in one request. */
152
+ async function treesOf(oids) {
153
+ const wanted = [...new Set(oids)].filter((oid) => !trees.has(oid));
154
+ const found = await Promise.all(wanted.map((oid) => stored('tree', oid)));
155
+ const missing = [];
156
+ wanted.forEach((oid, i) => {
157
+ try {
158
+ if (found[i]) return trees.set(oid, JSON.parse(new TextDecoder().decode(found[i])).entries);
159
+ } catch {}
160
+ missing.push(oid);
161
+ });
162
+ for (let at = 0; at < missing.length; at += BATCH) {
163
+ const some = missing.slice(at, at + BATCH);
164
+ const data = await graphql(
165
+ `query ($owner: String!, $name: String!) { repository(owner: $owner, name: $name) {
166
+ ${some.map((oid, i) => `t${i}: object(oid: ${JSON.stringify(oid)}) { ... on Tree { entries { name oid mode type object { ... on Blob { isBinary } } } } }`).join('\n')}
167
+ } }`,
168
+ { owner, name },
169
+ );
170
+ await Promise.all(
171
+ some.map(async (oid, i) => {
172
+ const got = data.repository[`t${i}`]?.entries;
173
+ const entries = got
174
+ ? sortTree(
175
+ got.map((e) => ({
176
+ name: e.name,
177
+ oid: e.oid,
178
+ mode: e.mode,
179
+ type: e.type,
180
+ ...(e.type === 'blob' ? { binary: e.object?.isBinary === true } : {}),
181
+ })),
182
+ )
183
+ : null;
184
+ trees.set(oid, entries);
185
+ if (entries && (await treeId(entries)) === oid) keep('tree', oid, JSON.stringify({ entries }));
186
+ }),
187
+ );
188
+ }
189
+ return new Map(oids.map((oid) => [oid, trees.get(oid) ?? null]));
190
+ }
191
+
192
+ /** The trees at some paths (`{ root, path }`, '' the root itself): their ids, or null where there is no folder. */
193
+ async function foldersAt(places) {
194
+ let at = places.map(({ root, path }) => ({ oid: root, rest: path ? path.split('/') : [] }));
195
+ for (;;) {
196
+ const on = at.filter((a) => a.oid && a.rest.length);
197
+ if (!on.length) return at.map((a) => a.oid);
198
+ const got = await treesOf(on.map((a) => a.oid));
199
+ at = at.map((a) => {
200
+ if (!a.oid || !a.rest.length) return a;
201
+ const entry = got.get(a.oid)?.find((e) => e.name === a.rest[0]);
202
+ return { oid: entry?.type === 'tree' ? entry.oid : null, rest: a.rest.slice(1) };
203
+ });
204
+ }
205
+ }
206
+
207
+ /** What is at some paths under a root tree: each one's entry (a file, a folder), or null. */
208
+ async function entriesAt(root, paths) {
209
+ const dirs = await foldersAt(paths.map((p) => ({ root, path: parentOf(p) })));
210
+ const got = await treesOf(dirs.filter(Boolean));
211
+ return paths.map((p, i) => (dirs[i] && got.get(dirs[i])?.find((e) => e.name === baseOf(p))) || null);
212
+ }
213
+
214
+ /** The files in some folders under a root tree, `depth` levels of folders down: `{ path, oid, binary }`, a list for
215
+ * each folder (empty when it is not there). */
216
+ async function filesIn(root, folders, depth) {
217
+ const starts = await foldersAt(folders.map((path) => ({ root, path })));
218
+ const lists = folders.map(() => []);
219
+ let level = starts.flatMap((oid, i) => (oid ? [{ oid, path: folders[i], list: lists[i] }] : []));
220
+ for (let d = 1; d <= depth && level.length; d++) {
221
+ const got = await treesOf(level.map((l) => l.oid));
222
+ const next = [];
223
+ for (const l of level)
224
+ for (const e of got.get(l.oid) ?? []) {
225
+ const path = l.path ? `${l.path}/${e.name}` : e.name;
226
+ if (e.type === 'tree') next.push({ oid: e.oid, path, list: l.list });
227
+ else if (e.type === 'blob') l.list.push({ path, oid: e.oid, binary: e.binary === true });
228
+ }
229
+ level = next;
230
+ }
231
+ return lists;
232
+ }
233
+
234
+ /** Files' texts by their blob ids, as GitHub gives them: null for one GitHub calls binary, undefined for one that is
235
+ * not there. From R2, the rest from GitHub in one request. */
236
+ async function texts(oids) {
237
+ const wanted = [...new Set(oids)];
238
+ const found = await Promise.all(wanted.map((oid) => stored('blob', oid)));
239
+ const out = new Map();
240
+ const missing = [];
241
+ wanted.forEach((oid, i) => {
242
+ const text = found[i] && textOf(found[i]);
243
+ if (text === null || text === undefined) missing.push(oid);
244
+ else out.set(oid, text);
245
+ });
246
+ for (let at = 0; at < missing.length; at += BATCH) {
247
+ const some = missing.slice(at, at + BATCH);
248
+ const data = await graphql(
249
+ `query ($owner: String!, $name: String!) { repository(owner: $owner, name: $name) {
250
+ ${some.map((oid, i) => `b${i}: object(oid: ${JSON.stringify(oid)}) { ... on Blob { text isBinary isTruncated } }`).join('\n')}
251
+ } }`,
252
+ { owner, name },
253
+ );
254
+ await Promise.all(
255
+ some.map(async (oid, i) => {
256
+ const blob = data.repository[`b${i}`];
257
+ if (!blob) return;
258
+ if (blob.isBinary || blob.text == null) return out.set(oid, null);
259
+ out.set(oid, blob.text);
260
+ // Kept only whole and unchanged: the text's bytes are the file's.
261
+ const bytes = encoder.encode(blob.text);
262
+ if (!blob.isTruncated && (await blobId(bytes)) === oid) keep('blob', oid, bytes);
263
+ }),
264
+ );
265
+ }
266
+ return new Map(oids.map((oid) => [oid, out.get(oid)]));
267
+ }
268
+
269
+ /** A file's bytes by its blob id: from R2, else from GitHub (kept when they give the id). `repo`: another repository
270
+ * (an app in the store), only from GitHub. Null when GitHub has no such file. */
271
+ async function bytes(oid, repo = config.repo) {
272
+ const own = repo.owner === owner && repo.name === name;
273
+ const found = own ? await stored('blob', oid) : null;
274
+ if (found) return found;
275
+ const res = await ask(`${API}/repos/${repo.owner}/${repo.name}/git/blobs/${oid}`, {
276
+ headers: { accept: 'application/vnd.github.raw+json' },
277
+ });
278
+ if (res.status === 404) return null;
279
+ if (!res.ok) throw new GitHubError('', res.status);
280
+ const got = new Uint8Array(await res.arrayBuffer());
281
+ if (own && (await blobId(got)) === oid) keep('blob', oid, got);
282
+ return got;
283
+ }
284
+
285
+ /**
286
+ * Saves files in one commit on the branch, if it is still at `at` (`{ commit, tree }`). `changes`: `{ path, contents }`,
287
+ * the contents as base64 (as GitHub takes them), null to delete the file. Gives the new commit and each saved file's
288
+ * blob id. The new blobs and folders go into R2 afterwards: worked out from the folders before the commit, and kept
289
+ * only when they give the root tree GitHub made (so they are the commit's).
290
+ */
291
+ async function save(at, message, changes) {
292
+ const kept = changes.filter((c) => c.contents !== null);
293
+ const data = await graphql(
294
+ `mutation ($input: CreateCommitOnBranchInput!) {
295
+ createCommitOnBranch(input: $input) { commit { oid tree { oid }
296
+ ${kept.map((c, i) => `f${i}: file(path: ${JSON.stringify(c.path)}) { oid object { ... on Blob { isBinary } } }`).join('\n')}
297
+ } }
298
+ }`,
299
+ {
300
+ input: {
301
+ branch: { repositoryNameWithOwner: KEY, branchName: branch },
302
+ expectedHeadOid: at.commit,
303
+ message: { headline: message },
304
+ fileChanges: {
305
+ additions: kept.map((c) => ({ path: c.path, contents: c.contents })),
306
+ deletions: changes.filter((c) => c.contents === null).map((c) => ({ path: c.path })),
307
+ },
308
+ },
309
+ },
310
+ );
311
+ const done = data.createCommitOnBranch.commit;
312
+ roots.set(done.oid, done.tree.oid);
313
+ const saved = new Map(kept.map((c, i) => [c.path, { oid: done[`f${i}`].oid, binary: done[`f${i}`].object?.isBinary === true }]));
314
+ if (bucket) later(keepSaved(at.tree, done.tree.oid, changes, saved).catch(() => {}));
315
+ return { commit: done.oid, blobs: Object.fromEntries([...saved].map(([path, f]) => [path, f.oid])) };
316
+ }
317
+
318
+ async function keepSaved(before, after, changes, saved) {
319
+ // The folders the changes are in, with all above them, and their entries before the commit (none: a new folder).
320
+ const dirs = [...new Set(changes.flatMap((c) => ancestorsOf(c.path)))];
321
+ const oids = await foldersAt(dirs.map((path) => ({ root: before, path })));
322
+ const got = await treesOf(oids.filter(Boolean));
323
+ if (oids.some((oid) => oid && !got.get(oid))) return;
324
+ const folders = new Map(dirs.map((d, i) => [d, new Map((oids[i] ? got.get(oids[i]) : []).map((e) => [e.name, e]))]));
325
+ for (const c of changes) {
326
+ const entries = folders.get(parentOf(c.path));
327
+ const was = entries.get(baseOf(c.path));
328
+ if (c.contents === null) entries.delete(baseOf(c.path));
329
+ else {
330
+ const { oid, binary } = saved.get(c.path);
331
+ entries.set(baseOf(c.path), { name: baseOf(c.path), oid, mode: was?.type === 'blob' ? was.mode : FILE_MODE, type: 'blob', binary });
332
+ }
333
+ }
334
+ // From the deepest folder up: each one's new id in the folder above it (an empty folder is gone).
335
+ const made = [];
336
+ for (const dir of dirs.sort((a, b) => depthOf(b) - depthOf(a))) {
337
+ const entries = sortTree([...folders.get(dir).values()]);
338
+ const oid = entries.length ? await treeId(entries) : null;
339
+ if (oid) made.push({ oid, entries });
340
+ if (!dir) {
341
+ if (oid !== after) return;
342
+ continue;
343
+ }
344
+ const above = folders.get(parentOf(dir));
345
+ if (oid) above.set(baseOf(dir), { name: baseOf(dir), oid, mode: TREE_MODE, type: 'tree' });
346
+ else above.delete(baseOf(dir));
347
+ }
348
+ for (const { oid, entries } of made) keep('tree', oid, JSON.stringify({ entries }), true);
349
+ for (const c of changes) {
350
+ if (c.contents === null) continue;
351
+ const contents = fromBase64(c.contents);
352
+ if ((await blobId(contents)) === saved.get(c.path).oid) keep('blob', saved.get(c.path).oid, contents, true);
353
+ }
354
+ }
355
+
356
+ /** Logs what the request asked of GitHub and R2 (`what`: which), once R2 is written. */
357
+ function finish(what) {
358
+ const all = Promise.allSettled(pending).then(() => {
359
+ if (count.github || count.read || count.written)
360
+ console.log(
361
+ `Filer (${what}): ${count.github} til GitHub, ${count.read} læst og ${count.written} skrevet i R2${bucket ? '' : ' (ingen R2)'}`,
362
+ );
363
+ });
364
+ ctx?.waitUntil(all);
365
+ return ctx ? Promise.resolve() : all;
366
+ }
367
+
368
+ return { ask, graphql, head, rootOf, treesOf, foldersAt, entriesAt, filesIn, texts, bytes, save, finish };
369
+ }
370
+
371
+ // A file's text, as GitHub gives it: undefined when its bytes are not UTF-8, or start with a byte order mark, which
372
+ // GitHub may give otherwise (both asked of GitHub).
373
+ function textOf(bytes) {
374
+ try {
375
+ const text = decoder.decode(bytes);
376
+ return text.startsWith('') ? undefined : text;
377
+ } catch {
378
+ return undefined;
379
+ }
380
+ }
381
+
382
+ const parentOf = (path) => path.split('/').slice(0, -1).join('/');
383
+ const baseOf = (path) => path.split('/').at(-1);
384
+ const depthOf = (path) => (path ? path.split('/').length : 0);
385
+ // The folders a path is in, from the root ('') down.
386
+ const ancestorsOf = (path) => path.split('/').map((_, i, parts) => parts.slice(0, i).join('/'));
387
+
388
+ /** Bytes as base64, as GitHub takes a file's contents. */
389
+ export function toBase64(bytes) {
390
+ let binary = '';
391
+ for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
392
+ return btoa(binary);
393
+ }
394
+
395
+ function fromBase64(text) {
396
+ if (Uint8Array.fromBase64) return Uint8Array.fromBase64(text);
397
+ const binary = atob(text);
398
+ const bytes = new Uint8Array(binary.length);
399
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
400
+ return bytes;
401
+ }