@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/src/nav.ts CHANGED
@@ -1,68 +1,68 @@
1
- // How the editor goes between its screens (the address after the "#") in the browser's history, so
2
- // that the back arrow and the phone's back gesture go the same way. The back arrow goes to the screen
3
- // above (Screen.tsx `back`); when that is the screen before it in the history, it goes back there, as
4
- // the gesture does, and otherwise the screen above takes the place of this one. No step is added, so
5
- // the gesture never brings back a screen that was left with the arrow. Every step in the history knows
6
- // its place and the screen before it (`history.state`).
7
-
8
- type Step = { at: number; prev: string | null };
9
-
10
- const step = () => history.state as Step | null;
11
-
12
- /** The screen of an address ("#liste/sider" → "liste/sider", "#" → ""); one with a broken %-sign is
13
- * the start screen. */
14
- export function screenOf(hash: string) {
15
- try {
16
- return decodeURIComponent(hash.replace(/^#/, ''));
17
- } catch {
18
- return '';
19
- }
20
- }
21
-
22
- // The place of the screen that is shown, and while a step is undone (arrived), the place it goes back to.
23
- let at = step()?.at ?? 0;
24
- let undoing: number | null = null;
25
- if (!step()) history.replaceState({ at, prev: null } satisfies Step, '');
26
-
27
- /** Called by the app on every new address, with the screen it came from: null when it is a step back
28
- * that `undo` asked for, else how to undo the step (back to the screen it came from) and take it again
29
- * (when the owner leaves without saving). */
30
- export function arrived(from: string): { undo: () => void; redo: () => void } | null {
31
- const now = step();
32
- if (undoing !== null && now?.at === undoing) {
33
- at = undoing;
34
- undoing = null;
35
- return null;
36
- }
37
- undoing = null;
38
- const before = at;
39
- // A step that was there (back, forward): its own place.
40
- if (now) {
41
- at = now.at;
42
- return {
43
- undo: () => ((undoing = before), history.go(before - now.at)),
44
- redo: () => history.go(now.at - before),
45
- };
46
- }
47
- // A new step (a link, or the address typed): after this one.
48
- const href = location.hash;
49
- at = before + 1;
50
- history.replaceState({ at, prev: from } satisfies Step, '');
51
- return {
52
- undo: () => ((undoing = before), history.back()),
53
- redo: () => (location.hash = href),
54
- };
55
- }
56
-
57
- /** Puts another screen in the place of this one (no new step in the history). */
58
- export function replace(href: string) {
59
- history.replaceState({ at, prev: step()?.prev ?? null } satisfies Step, '', href);
60
- dispatchEvent(new HashChangeEvent('hashchange'));
61
- }
62
-
63
- /** The back arrow: to the screen above, `href`. Back in the history when that is the screen before,
64
- * else in the place of this one. */
65
- export function up(href: string) {
66
- if (step()?.prev === screenOf(href)) history.back();
67
- else replace(href);
68
- }
1
+ // How the editor goes between its screens (the address after the "#") in the browser's history, so
2
+ // that the back arrow and the phone's back gesture go the same way. The back arrow goes to the screen
3
+ // above (Screen.tsx `back`); when that is the screen before it in the history, it goes back there, as
4
+ // the gesture does, and otherwise the screen above takes the place of this one. No step is added, so
5
+ // the gesture never brings back a screen that was left with the arrow. Every step in the history knows
6
+ // its place and the screen before it (`history.state`).
7
+
8
+ type Step = { at: number; prev: string | null };
9
+
10
+ const step = () => history.state as Step | null;
11
+
12
+ /** The screen of an address ("#liste/sider" → "liste/sider", "#" → ""); one with a broken %-sign is
13
+ * the start screen. */
14
+ export function screenOf(hash: string) {
15
+ try {
16
+ return decodeURIComponent(hash.replace(/^#/, ''));
17
+ } catch {
18
+ return '';
19
+ }
20
+ }
21
+
22
+ // The place of the screen that is shown, and while a step is undone (arrived), the place it goes back to.
23
+ let at = step()?.at ?? 0;
24
+ let undoing: number | null = null;
25
+ if (!step()) history.replaceState({ at, prev: null } satisfies Step, '');
26
+
27
+ /** Called by the app on every new address, with the screen it came from: null when it is a step back
28
+ * that `undo` asked for, else how to undo the step (back to the screen it came from) and take it again
29
+ * (when the owner leaves without saving). */
30
+ export function arrived(from: string): { undo: () => void; redo: () => void } | null {
31
+ const now = step();
32
+ if (undoing !== null && now?.at === undoing) {
33
+ at = undoing;
34
+ undoing = null;
35
+ return null;
36
+ }
37
+ undoing = null;
38
+ const before = at;
39
+ // A step that was there (back, forward): its own place.
40
+ if (now) {
41
+ at = now.at;
42
+ return {
43
+ undo: () => ((undoing = before), history.go(before - now.at)),
44
+ redo: () => history.go(now.at - before),
45
+ };
46
+ }
47
+ // A new step (a link, or the address typed): after this one.
48
+ const href = location.hash;
49
+ at = before + 1;
50
+ history.replaceState({ at, prev: from } satisfies Step, '');
51
+ return {
52
+ undo: () => ((undoing = before), history.back()),
53
+ redo: () => (location.hash = href),
54
+ };
55
+ }
56
+
57
+ /** Puts another screen in the place of this one (no new step in the history). */
58
+ export function replace(href: string) {
59
+ history.replaceState({ at, prev: step()?.prev ?? null } satisfies Step, '', href);
60
+ dispatchEvent(new HashChangeEvent('hashchange'));
61
+ }
62
+
63
+ /** The back arrow: to the screen above, `href`. Back in the history when that is the screen before,
64
+ * else in the place of this one. */
65
+ export function up(href: string) {
66
+ if (step()?.prev === screenOf(href)) history.back();
67
+ else replace(href);
68
+ }
package/src/notFound.ts CHANGED
@@ -1,28 +1,28 @@
1
- // The addresses visitors came to that do not exist, as the site's Worker keeps them (packages/editor/worker/not-found.js,
2
- // /api/not-found on the editor's own address), for "Ikke fundet" under "Videresendelser" (Redirects.tsx).
3
- // Asked with the GitHub sign-in (the Worker's cookie, sent with the request): the Worker lets only someone who can save
4
- // the site see them.
5
-
6
- /** An address that does not exist: how many visits, the first and the last (ISO dates), and where the
7
- * last visitor came from (an address of the site, another site's name, or ''). */
8
- export type Missing = { path: string; count: number; first: string; last: string; from: string };
9
-
10
- const API = '/api/not-found';
11
-
12
- /** The addresses, most visits first; null when the site does not keep them (no Worker, no storage). */
13
- export async function loadNotFound(): Promise<Missing[] | null> {
14
- const res = await fetch(API, { cache: 'no-store' });
15
- if (res.status === 503 || !res.headers.get('content-type')?.includes('application/json')) return null;
16
- if (!res.ok) throw new Error(String(res.status));
17
- return ((await res.json()) as { entries: Missing[] }).entries;
18
- }
19
-
20
- /** Takes the addresses off the list (they now send on, or are not worth it). */
21
- export async function forgetNotFound(paths: string[]): Promise<void> {
22
- const res = await fetch(API, {
23
- method: 'DELETE',
24
- headers: { 'content-type': 'application/json' },
25
- body: JSON.stringify({ paths }),
26
- });
27
- if (!res.ok) throw new Error(String(res.status));
28
- }
1
+ // The addresses visitors came to that do not exist, as the site's Worker keeps them (packages/editor/worker/not-found.js,
2
+ // /api/not-found on the editor's own address), for "Ikke fundet" under "Videresendelser" (Redirects.tsx).
3
+ // Asked with the GitHub sign-in (the Worker's cookie, sent with the request): the Worker lets only someone who can save
4
+ // the site see them.
5
+
6
+ /** An address that does not exist: how many visits, the first and the last (ISO dates), and where the
7
+ * last visitor came from (an address of the site, another site's name, or ''). */
8
+ export type Missing = { path: string; count: number; first: string; last: string; from: string };
9
+
10
+ const API = '/api/not-found';
11
+
12
+ /** The addresses, most visits first; null when the site does not keep them (no Worker, no storage). */
13
+ export async function loadNotFound(): Promise<Missing[] | null> {
14
+ const res = await fetch(API, { cache: 'no-store' });
15
+ if (res.status === 503 || !res.headers.get('content-type')?.includes('application/json')) return null;
16
+ if (!res.ok) throw new Error(String(res.status));
17
+ return ((await res.json()) as { entries: Missing[] }).entries;
18
+ }
19
+
20
+ /** Takes the addresses off the list (they now send on, or are not worth it). */
21
+ export async function forgetNotFound(paths: string[]): Promise<void> {
22
+ const res = await fetch(API, {
23
+ method: 'DELETE',
24
+ headers: { 'content-type': 'application/json' },
25
+ body: JSON.stringify({ paths }),
26
+ });
27
+ if (!res.ok) throw new Error(String(res.status));
28
+ }
@@ -1,223 +1,223 @@
1
- // The notifications (#notifikationer, Notifications.tsx): what the editor and the apps have to say that is worth
2
- // reading again, newest first, with when it came and the link it had. Each has a type: the editor's own (`site`: the
3
- // site is updated or not, `saved`: something is saved, deleted, restored or installed, `failed`: it could not be,
4
- // `updates`: an app from the store updated on its own, AppList.tsx autoUpdate), or
5
- // an app's, which it declares (`notifications` in its index.ts, AppNotifications in apps.ts) as `<app>/<type>`. A
6
- // message about something going on ("Gemmer…") or about a form that cannot be saved has no type and is only shown at
7
- // the bottom.
8
- // Each type can be turned off under "Konto" › "Notifikationer" (Library.tsx Account): then it is not kept here or sent
9
- // to the phone (the editor's own are still shown at the bottom, as the answer to what was done; an app's not at all).
10
- // There too "På telefonen": when it is on, a notification comes on the phone or computer as well while the editor is
11
- // open but not on screen (another app or tab; notifyDevice), and, through the site's Worker (Web Push, packages/editor/worker/push.js),
12
- // when it is closed: this browser is signed up there with the types turned off and the editor's language (signUp), and
13
- // after a save the Worker tells it when the site has been built with it (waitOnServer); an app's part of the Worker
14
- // tells it the app's notifications. The bell in the top bar (Screen.tsx NotificationsLink) has a dot while some are not
15
- // seen; opening the notifications marks them seen. Kept in this browser: the last 50, the choices and the ids of the
16
- // apps' notifications already told.
17
- import { useSyncExternalStore } from 'react';
18
- import { siteName } from '@site/editor';
19
- import { locale } from './i18n';
20
-
21
- export const EDITOR_TYPES = ['site', 'saved', 'failed', 'updates'] as const;
22
- export type EditorType = (typeof EDITOR_TYPES)[number];
23
-
24
- /** The type of an app's notification, as it is kept and turned off. */
25
- export const appType = (app: string, type: string) => `${app}/${type}`;
26
-
27
- export type Notification = {
28
- text: string;
29
- type: string;
30
- error?: boolean;
31
- at: number;
32
- link?: { text: string; href: string };
33
- };
34
- type Kept = { items: Notification[]; seen: number; told: string[] };
35
- /** `off`: the types turned off; `device`: "På telefonen" (not chosen yet: on when the browser allows it). */
36
- type Choices = { off: string[]; device?: boolean };
37
-
38
- const KEY = 'editor-notifications';
39
- const CHOICES = 'editor-notification-settings';
40
- const MAX = 50;
41
- const MAX_TOLD = 200;
42
-
43
- function read<T>(key: string, fallback: T, ok: (value: T) => boolean): T {
44
- try {
45
- const saved = JSON.parse(localStorage.getItem(key) ?? 'null') as T | null;
46
- if (saved && ok(saved)) return { ...fallback, ...saved };
47
- } catch {}
48
- return fallback;
49
- }
50
-
51
- let kept = read<Kept>(KEY, { items: [], seen: 0, told: [] }, (k) => Array.isArray(k.items));
52
- let choices = read<Choices>(CHOICES, { off: [] }, (c) => Array.isArray(c.off));
53
- const listeners = new Set<() => void>();
54
-
55
- function store(key: string, value: unknown) {
56
- try {
57
- localStorage.setItem(key, JSON.stringify(value));
58
- } catch {}
59
- listeners.forEach((l) => l());
60
- }
61
- const set = (next: Kept) => store(KEY, (kept = next));
62
- const choose = (next: Choices) => store(CHOICES, (choices = next));
63
-
64
- const subscribe = (changed: () => void) => {
65
- listeners.add(changed);
66
- return () => listeners.delete(changed);
67
- };
68
-
69
- /** Whether notifications of the type are on. */
70
- export const isOn = (type: string) => !choices.off.includes(type);
71
-
72
- /** Keeps a notification and sends it to the phone, when its type is on (not `device` when the Worker sends it). */
73
- export function remember(item: Omit<Notification, 'at'>, device = true) {
74
- if (!isOn(item.type)) return;
75
- set({ ...kept, items: [{ ...item, at: Date.now() }, ...kept.items].slice(0, MAX) });
76
- if (device) notifyDevice(item.text);
77
- }
78
-
79
- /** Whether an app's notification (by its own id) has been told before; marks it told. */
80
- export function told(type: string, id: string) {
81
- const key = `${type}:${id}`;
82
- if (kept.told.includes(key)) return true;
83
- set({ ...kept, told: [key, ...kept.told].slice(0, MAX_TOLD) });
84
- return false;
85
- }
86
-
87
- /** Marks them all seen (the notifications are open). */
88
- export function markSeen() {
89
- if (kept.items.some((n) => n.at > kept.seen)) set({ ...kept, seen: Date.now() });
90
- }
91
-
92
- export function clearNotifications() {
93
- set({ ...kept, items: [], seen: Date.now() });
94
- }
95
-
96
- /** Turns a type on or off. */
97
- export function setOn(type: string, on: boolean) {
98
- choose({ ...choices, off: on ? choices.off.filter((t) => t !== type) : [...choices.off, type] });
99
- if (signedUp) signUp().catch(() => {});
100
- }
101
-
102
- const allowed = () => typeof Notification !== 'undefined' && Notification.permission === 'granted';
103
- const deviceOn = () => choices.device ?? allowed();
104
-
105
- /** Turns "På telefonen" on or off; on asks the browser first. False when the browser does not allow it. */
106
- export async function setDevice(on: boolean): Promise<boolean> {
107
- if (on && typeof Notification === 'undefined') return false;
108
- if (on && Notification.permission === 'default') await Notification.requestPermission().catch(() => {});
109
- if (on && !allowed()) return false;
110
- choose({ ...choices, device: on });
111
- await (on ? signUp() : signOff()).catch(() => {});
112
- return true;
113
- }
114
-
115
- // The Worker's notifications (packages/editor/worker/push.js), with the GitHub sign-in (the Worker's cookie, sent with the request): only
116
- // someone who can save the site may.
117
- const PUSH = '/api/push';
118
- // The endpoint this browser is signed up with at the Worker, while it is.
119
- let signedUp: string | null = null;
120
-
121
- function api(path: string, method: string, body?: unknown) {
122
- return fetch(path, {
123
- method,
124
- cache: 'no-store',
125
- headers: { 'content-type': 'application/json' },
126
- body: body === undefined ? undefined : JSON.stringify(body),
127
- });
128
- }
129
-
130
- // The service worker (registered when the editor opens, deploy.ts registerWorker), once it is running; none when the
131
- // browser has no push (an iPhone outside the home screen).
132
- const pushWorker = async (ready = true) =>
133
- typeof PushManager === 'undefined' || !navigator.serviceWorker
134
- ? undefined
135
- : ready
136
- ? await navigator.serviceWorker.ready
137
- : await navigator.serviceWorker.getRegistration('/editor/');
138
-
139
- const b64url = (key: ArrayBuffer | null | undefined) =>
140
- key
141
- ? btoa(String.fromCharCode(...new Uint8Array(key)))
142
- .replace(/\+/g, '-')
143
- .replace(/\//g, '_')
144
- .replace(/=+$/, '')
145
- : '';
146
-
147
- /** Signs this browser up at the Worker, or tells it the choices again: with the browser's push subscription to the
148
- * Worker's key (a new one when the key is another), the types turned off and the editor's language. Nothing when the
149
- * browser cannot (an iPhone outside the home screen) or the site keeps none (a preview of a branch). */
150
- async function signUp() {
151
- const worker = await pushWorker();
152
- if (!worker) return;
153
- const res = await api(PUSH, 'GET');
154
- if (!res.ok) return;
155
- const { key } = (await res.json()) as { key: string };
156
- let subscription = await worker.pushManager.getSubscription();
157
- if (subscription && b64url(subscription.options.applicationServerKey) !== key) {
158
- await subscription.unsubscribe();
159
- subscription = null;
160
- }
161
- subscription ??= await worker.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
162
- const signed = await api(PUSH, 'PUT', { subscription: subscription.toJSON(), off: choices.off, locale });
163
- signedUp = signed.ok ? subscription.endpoint : null;
164
- }
165
-
166
- /** Signs this browser off at the Worker ("På telefonen" off, "Log ud": before the sign-in goes), and from the push
167
- * service. */
168
- export async function signOff() {
169
- signedUp = null;
170
- const subscription = await (await pushWorker(false))?.pushManager.getSubscription();
171
- if (!subscription) return;
172
- await api(PUSH, 'DELETE', { endpoint: subscription.endpoint }).catch(() => {});
173
- await subscription.unsubscribe().catch(() => {});
174
- }
175
-
176
- /** When the editor opens: signs up again while "På telefonen" is on (the Worker may have a new key, or have forgotten
177
- * a browser not seen for half a year), else signs off. */
178
- export function syncDevice() {
179
- (deviceOn() && allowed() ? signUp() : signOff()).catch(() => {});
180
- }
181
-
182
- /** After a save: asks the Worker to say on the phone when the site has been built with `commit` (`text`), or that it
183
- * was not after ten minutes (`failText`). True when it will (this browser is signed up, with the type on). */
184
- export async function waitOnServer(commit: string, text: string, failText: string): Promise<boolean> {
185
- if (!signedUp || !isOn('site')) return false;
186
- const res = await api(`${PUSH}/wait`, 'PUT', { endpoint: signedUp, commit, text, failText }).catch(() => null);
187
- return !!res?.ok;
188
- }
189
-
190
- /** Takes the wait back: the editor said it itself. */
191
- export function stopServerWait() {
192
- if (signedUp) api(`${PUSH}/wait`, 'DELETE', { endpoint: signedUp }).catch(() => {});
193
- }
194
-
195
- // Asks whether the editor may show notifications on the phone, once, while "På telefonen" is not chosen yet.
196
- // Browsers only ask after a tap, so this is called from "Gem".
197
- export function askToNotify() {
198
- if (choices.device === undefined && typeof Notification !== 'undefined' && Notification.permission === 'default')
199
- setDevice(true).catch(() => {});
200
- }
201
-
202
- // The message as a notification on the phone or computer (through the service worker, packages/editor/pages/editor/sw.js), when
203
- // "På telefonen" is on and the editor is not on screen.
204
- async function notifyDevice(text: string) {
205
- if (document.visibilityState === 'visible' || !deviceOn() || !allowed()) return;
206
- const options = { body: text, icon: '/editor/icon-192.png', tag: 'site' };
207
- try {
208
- const worker = await navigator.serviceWorker?.getRegistration('/editor/');
209
- if (worker) await worker.showNotification(siteName, options);
210
- else new Notification(siteName, options);
211
- } catch {}
212
- }
213
-
214
- export const useNotifications = () => useSyncExternalStore(subscribe, () => kept);
215
-
216
- /** The choices: the types turned off and whether "På telefonen" is on. */
217
- export const useNotificationChoices = () => {
218
- const now = useSyncExternalStore(subscribe, () => choices);
219
- return { off: now.off, device: now.device ?? allowed() };
220
- };
221
-
222
- /** How many are not seen yet. */
223
- export const unseen = ({ items, seen }: Kept) => items.filter((n) => n.at > seen).length;
1
+ // The notifications (#notifikationer, Notifications.tsx): what the editor and the apps have to say that is worth
2
+ // reading again, newest first, with when it came and the link it had. Each has a type: the editor's own (`site`: the
3
+ // site is updated or not, `saved`: something is saved, deleted, restored or installed, `failed`: it could not be,
4
+ // `updates`: an app from the store updated on its own, AppList.tsx autoUpdate), or
5
+ // an app's, which it declares (`notifications` in its index.ts, AppNotifications in apps.ts) as `<app>/<type>`. A
6
+ // message about something going on ("Gemmer…") or about a form that cannot be saved has no type and is only shown at
7
+ // the bottom.
8
+ // Each type can be turned off under "Konto" › "Notifikationer" (Library.tsx Account): then it is not kept here or sent
9
+ // to the phone (the editor's own are still shown at the bottom, as the answer to what was done; an app's not at all).
10
+ // There too "På telefonen": when it is on, a notification comes on the phone or computer as well while the editor is
11
+ // open but not on screen (another app or tab; notifyDevice), and, through the site's Worker (Web Push, packages/editor/worker/push.js),
12
+ // when it is closed: this browser is signed up there with the types turned off and the editor's language (signUp), and
13
+ // after a save the Worker tells it when the site has been built with it (waitOnServer); an app's part of the Worker
14
+ // tells it the app's notifications. The bell in the top bar (Screen.tsx NotificationsLink) has a dot while some are not
15
+ // seen; opening the notifications marks them seen. Kept in this browser: the last 50, the choices and the ids of the
16
+ // apps' notifications already told.
17
+ import { useSyncExternalStore } from 'react';
18
+ import { siteName } from '@site/editor';
19
+ import { locale } from './i18n';
20
+
21
+ export const EDITOR_TYPES = ['site', 'saved', 'failed', 'updates'] as const;
22
+ export type EditorType = (typeof EDITOR_TYPES)[number];
23
+
24
+ /** The type of an app's notification, as it is kept and turned off. */
25
+ export const appType = (app: string, type: string) => `${app}/${type}`;
26
+
27
+ export type Notification = {
28
+ text: string;
29
+ type: string;
30
+ error?: boolean;
31
+ at: number;
32
+ link?: { text: string; href: string };
33
+ };
34
+ type Kept = { items: Notification[]; seen: number; told: string[] };
35
+ /** `off`: the types turned off; `device`: "På telefonen" (not chosen yet: on when the browser allows it). */
36
+ type Choices = { off: string[]; device?: boolean };
37
+
38
+ const KEY = 'editor-notifications';
39
+ const CHOICES = 'editor-notification-settings';
40
+ const MAX = 50;
41
+ const MAX_TOLD = 200;
42
+
43
+ function read<T>(key: string, fallback: T, ok: (value: T) => boolean): T {
44
+ try {
45
+ const saved = JSON.parse(localStorage.getItem(key) ?? 'null') as T | null;
46
+ if (saved && ok(saved)) return { ...fallback, ...saved };
47
+ } catch {}
48
+ return fallback;
49
+ }
50
+
51
+ let kept = read<Kept>(KEY, { items: [], seen: 0, told: [] }, (k) => Array.isArray(k.items));
52
+ let choices = read<Choices>(CHOICES, { off: [] }, (c) => Array.isArray(c.off));
53
+ const listeners = new Set<() => void>();
54
+
55
+ function store(key: string, value: unknown) {
56
+ try {
57
+ localStorage.setItem(key, JSON.stringify(value));
58
+ } catch {}
59
+ listeners.forEach((l) => l());
60
+ }
61
+ const set = (next: Kept) => store(KEY, (kept = next));
62
+ const choose = (next: Choices) => store(CHOICES, (choices = next));
63
+
64
+ const subscribe = (changed: () => void) => {
65
+ listeners.add(changed);
66
+ return () => listeners.delete(changed);
67
+ };
68
+
69
+ /** Whether notifications of the type are on. */
70
+ export const isOn = (type: string) => !choices.off.includes(type);
71
+
72
+ /** Keeps a notification and sends it to the phone, when its type is on (not `device` when the Worker sends it). */
73
+ export function remember(item: Omit<Notification, 'at'>, device = true) {
74
+ if (!isOn(item.type)) return;
75
+ set({ ...kept, items: [{ ...item, at: Date.now() }, ...kept.items].slice(0, MAX) });
76
+ if (device) notifyDevice(item.text);
77
+ }
78
+
79
+ /** Whether an app's notification (by its own id) has been told before; marks it told. */
80
+ export function told(type: string, id: string) {
81
+ const key = `${type}:${id}`;
82
+ if (kept.told.includes(key)) return true;
83
+ set({ ...kept, told: [key, ...kept.told].slice(0, MAX_TOLD) });
84
+ return false;
85
+ }
86
+
87
+ /** Marks them all seen (the notifications are open). */
88
+ export function markSeen() {
89
+ if (kept.items.some((n) => n.at > kept.seen)) set({ ...kept, seen: Date.now() });
90
+ }
91
+
92
+ export function clearNotifications() {
93
+ set({ ...kept, items: [], seen: Date.now() });
94
+ }
95
+
96
+ /** Turns a type on or off. */
97
+ export function setOn(type: string, on: boolean) {
98
+ choose({ ...choices, off: on ? choices.off.filter((t) => t !== type) : [...choices.off, type] });
99
+ if (signedUp) signUp().catch(() => {});
100
+ }
101
+
102
+ const allowed = () => typeof Notification !== 'undefined' && Notification.permission === 'granted';
103
+ const deviceOn = () => choices.device ?? allowed();
104
+
105
+ /** Turns "På telefonen" on or off; on asks the browser first. False when the browser does not allow it. */
106
+ export async function setDevice(on: boolean): Promise<boolean> {
107
+ if (on && typeof Notification === 'undefined') return false;
108
+ if (on && Notification.permission === 'default') await Notification.requestPermission().catch(() => {});
109
+ if (on && !allowed()) return false;
110
+ choose({ ...choices, device: on });
111
+ await (on ? signUp() : signOff()).catch(() => {});
112
+ return true;
113
+ }
114
+
115
+ // The Worker's notifications (packages/editor/worker/push.js), with the GitHub sign-in (the Worker's cookie, sent with the request): only
116
+ // someone who can save the site may.
117
+ const PUSH = '/api/push';
118
+ // The endpoint this browser is signed up with at the Worker, while it is.
119
+ let signedUp: string | null = null;
120
+
121
+ function api(path: string, method: string, body?: unknown) {
122
+ return fetch(path, {
123
+ method,
124
+ cache: 'no-store',
125
+ headers: { 'content-type': 'application/json' },
126
+ body: body === undefined ? undefined : JSON.stringify(body),
127
+ });
128
+ }
129
+
130
+ // The service worker (registered when the editor opens, deploy.ts registerWorker), once it is running; none when the
131
+ // browser has no push (an iPhone outside the home screen).
132
+ const pushWorker = async (ready = true) =>
133
+ typeof PushManager === 'undefined' || !navigator.serviceWorker
134
+ ? undefined
135
+ : ready
136
+ ? await navigator.serviceWorker.ready
137
+ : await navigator.serviceWorker.getRegistration('/editor/');
138
+
139
+ const b64url = (key: ArrayBuffer | null | undefined) =>
140
+ key
141
+ ? btoa(String.fromCharCode(...new Uint8Array(key)))
142
+ .replace(/\+/g, '-')
143
+ .replace(/\//g, '_')
144
+ .replace(/=+$/, '')
145
+ : '';
146
+
147
+ /** Signs this browser up at the Worker, or tells it the choices again: with the browser's push subscription to the
148
+ * Worker's key (a new one when the key is another), the types turned off and the editor's language. Nothing when the
149
+ * browser cannot (an iPhone outside the home screen) or the site keeps none (a preview of a branch). */
150
+ async function signUp() {
151
+ const worker = await pushWorker();
152
+ if (!worker) return;
153
+ const res = await api(PUSH, 'GET');
154
+ if (!res.ok) return;
155
+ const { key } = (await res.json()) as { key: string };
156
+ let subscription = await worker.pushManager.getSubscription();
157
+ if (subscription && b64url(subscription.options.applicationServerKey) !== key) {
158
+ await subscription.unsubscribe();
159
+ subscription = null;
160
+ }
161
+ subscription ??= await worker.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
162
+ const signed = await api(PUSH, 'PUT', { subscription: subscription.toJSON(), off: choices.off, locale });
163
+ signedUp = signed.ok ? subscription.endpoint : null;
164
+ }
165
+
166
+ /** Signs this browser off at the Worker ("På telefonen" off, "Log ud": before the sign-in goes), and from the push
167
+ * service. */
168
+ export async function signOff() {
169
+ signedUp = null;
170
+ const subscription = await (await pushWorker(false))?.pushManager.getSubscription();
171
+ if (!subscription) return;
172
+ await api(PUSH, 'DELETE', { endpoint: subscription.endpoint }).catch(() => {});
173
+ await subscription.unsubscribe().catch(() => {});
174
+ }
175
+
176
+ /** When the editor opens: signs up again while "På telefonen" is on (the Worker may have a new key, or have forgotten
177
+ * a browser not seen for half a year), else signs off. */
178
+ export function syncDevice() {
179
+ (deviceOn() && allowed() ? signUp() : signOff()).catch(() => {});
180
+ }
181
+
182
+ /** After a save: asks the Worker to say on the phone when the site has been built with `commit` (`text`), or that it
183
+ * was not after ten minutes (`failText`). True when it will (this browser is signed up, with the type on). */
184
+ export async function waitOnServer(commit: string, text: string, failText: string): Promise<boolean> {
185
+ if (!signedUp || !isOn('site')) return false;
186
+ const res = await api(`${PUSH}/wait`, 'PUT', { endpoint: signedUp, commit, text, failText }).catch(() => null);
187
+ return !!res?.ok;
188
+ }
189
+
190
+ /** Takes the wait back: the editor said it itself. */
191
+ export function stopServerWait() {
192
+ if (signedUp) api(`${PUSH}/wait`, 'DELETE', { endpoint: signedUp }).catch(() => {});
193
+ }
194
+
195
+ // Asks whether the editor may show notifications on the phone, once, while "På telefonen" is not chosen yet.
196
+ // Browsers only ask after a tap, so this is called from "Gem".
197
+ export function askToNotify() {
198
+ if (choices.device === undefined && typeof Notification !== 'undefined' && Notification.permission === 'default')
199
+ setDevice(true).catch(() => {});
200
+ }
201
+
202
+ // The message as a notification on the phone or computer (through the service worker, packages/editor/pages/editor/sw.js), when
203
+ // "På telefonen" is on and the editor is not on screen.
204
+ async function notifyDevice(text: string) {
205
+ if (document.visibilityState === 'visible' || !deviceOn() || !allowed()) return;
206
+ const options = { body: text, icon: '/editor/icon-192.png', tag: 'site' };
207
+ try {
208
+ const worker = await navigator.serviceWorker?.getRegistration('/editor/');
209
+ if (worker) await worker.showNotification(siteName, options);
210
+ else new Notification(siteName, options);
211
+ } catch {}
212
+ }
213
+
214
+ export const useNotifications = () => useSyncExternalStore(subscribe, () => kept);
215
+
216
+ /** The choices: the types turned off and whether "På telefonen" is on. */
217
+ export const useNotificationChoices = () => {
218
+ const now = useSyncExternalStore(subscribe, () => choices);
219
+ return { off: now.off, device: now.device ?? allowed() };
220
+ };
221
+
222
+ /** How many are not seen yet. */
223
+ export const unseen = ({ items, seen }: Kept) => items.filter((n) => n.at > seen).length;