@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
@@ -1,225 +1,225 @@
1
- // The visitor's yes or no to each category of cookies in the cookie banner (@morphemecms/cms/lib/cookieCategories.mjs:
2
- // preferences, statistics, marketing, targeting; necessary ones are not asked about), and the site's events. The apps that measure
3
- // (Google Analytics, Google Ads, Google Tag Manager: src/apps/) load their tags with afterConsent, only after a yes to
4
- // their category (Google consent mode, "basic": the Google tag is not loaded at all until the visitor says yes, and it
5
- // is told which categories the visitor said yes to), and send the events on. Where events are sent from:
6
- // `tracking.hosts` in packages/editor/site/tracking.ts.
7
- //
8
- // Events (GA4 event names; mark them as key events in GA4 and import them in Google Ads):
9
- // - book_appointment: a time was booked (the app Calendly: in the booking sheet or on the booking page)
10
- // - open_booking: the booking calendar was opened
11
- // - click_to_call: a phone link was tapped
12
- // - email_click: an e-mail link was clicked
13
- import { CONSENT_CATEGORIES, isConsentCategory, type ConsentCategory } from '@morphemecms/cms/lib/cookieCategories.mjs';
14
- import { cookieList } from './cookieList';
15
- import { tracking } from './tracking';
16
- import { site } from './site';
17
-
18
- /** The visitor's yes (true) or no to each category the cookie banner asked about. */
19
- export type Consent = Partial<Record<ConsentCategory, boolean>>;
20
- const KEY = 'consent';
21
- const MAX_AGE = 365 * 24 * 60 * 60 * 1000; // ask again after a year
22
-
23
- declare global {
24
- interface Window {
25
- dataLayer: unknown[];
26
- gtag: (...args: unknown[]) => void;
27
- }
28
- }
29
-
30
- const params = new URLSearchParams(location.search);
31
- try {
32
- if (params.get('tracking') === 'test') sessionStorage.setItem('tracking-test', '1');
33
- } catch {}
34
- /** Whether events are sent from another address than the site's own (?tracking=test): GA4's debug mode. */
35
- export const testing = (() => {
36
- try {
37
- return sessionStorage.getItem('tracking-test') === '1';
38
- } catch {
39
- return false;
40
- }
41
- })();
42
- const live = tracking.hosts.includes(location.hostname) || testing;
43
-
44
- /** The visitor's answer in the cookie banner, within the last year; undefined without one (or with one from before
45
- * the banner asked about each category, which is asked again). */
46
- export function storedConsent(): Consent | undefined {
47
- try {
48
- const saved = JSON.parse(localStorage.getItem(KEY) ?? 'null');
49
- if (!saved || typeof saved.choices !== 'object' || !saved.choices || Date.now() - saved.at >= MAX_AGE) return undefined;
50
- return Object.fromEntries(
51
- Object.entries(saved.choices).filter(([category, yes]) => isConsentCategory(category) && typeof yes === 'boolean'),
52
- ) as Consent;
53
- } catch {
54
- return undefined;
55
- }
56
- }
57
-
58
- const granted = (category: ConsentCategory) => storedConsent()?.[category] === true;
59
-
60
- // Google's consent mode: what its tags may store, by the visitor's answer (statistics: Analytics; marketing: Ads). A yes
61
- // to marketing is a yes to measuring whether the ads work; personalised ads (remarketing) only after a yes to targeted
62
- // ads, which the banner asks about only when an app is set to use them (`cookies` in its index.ts), so a site that does
63
- // not use them never allows them. Targeted ads store what ads do (ad_storage, ad_user_data) too.
64
- function googleSignals() {
65
- const yes = (...categories: ConsentCategory[]) => (categories.some(granted) ? 'granted' : 'denied');
66
- return {
67
- analytics_storage: yes('statistics'),
68
- ad_storage: yes('marketing', 'targeting'),
69
- ad_user_data: yes('marketing', 'targeting'),
70
- ad_personalization: yes('targeting'),
71
- };
72
- }
73
-
74
- // Google signals (Analytics data joined with Google accounts, for ads and demographics) and ad personalisation in any
75
- // tag, likewise only after a yes to targeted ads, whatever the accounts' own settings say.
76
- function googleSettings() {
77
- window.gtag('set', 'allow_google_signals', granted('targeting'));
78
- window.gtag('set', 'allow_ad_personalization_signals', granted('targeting'));
79
- }
80
-
81
- /** Google's data layer with the visitor's answer in consent mode, for a tag of Google's (googleTag, Google Tag
82
- * Manager's container, src/apps/gtm/), set up once, before the first tag loads; a new answer updates it (choose). */
83
- export function googleConsent() {
84
- window.dataLayer = window.dataLayer || [];
85
- if (typeof window.gtag === 'function') return;
86
- window.gtag = function gtag() {
87
- // eslint-disable-next-line prefer-rest-params
88
- window.dataLayer.push(arguments);
89
- };
90
- window.gtag('consent', 'default', googleSignals());
91
- googleSettings();
92
- window.gtag('js', new Date());
93
- }
94
-
95
- let tagged = false;
96
-
97
- /** The Google tag (gtag.js) with this ID (Google Analytics' G-…, Google Ads' AW-…), for the apps that measure with it
98
- * (src/apps/google-analytics/, src/apps/google-ads/), in afterConsent: loaded once for all of them, each ID configured. */
99
- export function googleTag(id: string, config: Record<string, unknown> = {}) {
100
- googleConsent();
101
- window.gtag('config', id, config);
102
- if (tagged) return;
103
- tagged = true;
104
- const s = document.createElement('script');
105
- s.async = true;
106
- s.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(id)}`;
107
- document.head.appendChild(s);
108
- }
109
-
110
- // The cookies of each category on the site's own address, to be removed after a no to it: those of the cookie list
111
- // ("Lister" › "Cookies", `*` for the rest of a name), and the others Google's tag may set (GA4: _ga, _ga_<id>, and
112
- // _gid and _gat from older tags; Ads: _gcl_au and the other _gcl_ ones).
113
- const pattern = (name: string) => new RegExp(`^${name.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`);
114
- const COOKIES: Record<ConsentCategory, RegExp[]> = {
115
- preferences: [],
116
- statistics: [/^(_ga(_.+)?|_gid|_gat(_.+)?)$/],
117
- marketing: [/^_gcl_.+$/],
118
- targeting: [],
119
- };
120
- for (const c of cookieList) if (isConsentCategory(c.category)) COOKIES[c.category].push(pattern(c.name));
121
-
122
- // Each one is removed on the hostname and on every parent domain (Google's are set on example.dk for
123
- // www.example.dk).
124
- function removeCookies(categories: ConsentCategory[]) {
125
- const patterns = categories.flatMap((category) => COOKIES[category]);
126
- const names = document.cookie
127
- .split(';')
128
- .map((c) => c.split('=')[0].trim())
129
- .filter((name) => patterns.some((p) => p.test(name)));
130
- if (!names.length) return;
131
- const labels = location.hostname.split('.');
132
- const domains = ['', ...labels.slice(0, -1).map((_, i) => `; domain=.${labels.slice(i).join('.')}`)];
133
- for (const name of names)
134
- for (const domain of domains) document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${domain}`;
135
- }
136
-
137
- /** The random id of this browser's answers in the consent log (packages/editor/worker/consent.js), shown in the cookie banner so the
138
- * visitor can point to their own; null before the first answer. */
139
- export function consentId(): string | null {
140
- try {
141
- const id = JSON.parse(localStorage.getItem(KEY) ?? 'null')?.id;
142
- return typeof id === 'string' ? id : null;
143
- } catch {
144
- return null;
145
- }
146
- }
147
-
148
- /** Saves the visitor's answer (a yes or no to each category the banner asks about), in the browser and in the consent
149
- * log, starts what waited for a yes and removes the cookies of each category said no to. A no to all the categories of
150
- * something already started on this page reloads it, so it stops. */
151
- export function choose(consent: Consent) {
152
- const id = consentId() ?? crypto.randomUUID();
153
- try {
154
- localStorage.setItem(KEY, JSON.stringify({ choices: consent, at: Date.now(), id }));
155
- } catch {}
156
- logConsent(id, consent);
157
- if (typeof window.gtag === 'function') {
158
- window.gtag('consent', 'update', googleSignals());
159
- googleSettings();
160
- }
161
- removeCookies(CONSENT_CATEGORIES.filter((category) => !granted(category)));
162
- if (started.some((categories) => !categories.some(granted))) return location.reload();
163
- for (const wait of waiting.splice(0)) afterConsent(wait.categories, wait.run);
164
- }
165
-
166
- // The answer to the consent log on the site's Worker (/api/consent), also when the page reloads right after
167
- // (keepalive). Not the answers of a test (?tracking=test: the cookie scan's visitors, src/apps/cookiescanner/).
168
- function logConsent(id: string, consent: Consent) {
169
- if (testing) return;
170
- fetch('/api/consent', { method: 'POST', keepalive: true, body: JSON.stringify({ id, choices: consent }) }).catch(() => {});
171
- }
172
-
173
- export function track(name: string, data: Record<string, unknown> = {}) {
174
- if (!CONSENT_CATEGORIES.some(granted)) return;
175
- if (!live) return console.info('[tracking]', name, data);
176
- // The apps that measure send it on (Google Analytics, Google Ads, Google Tag Manager: src/apps/).
177
- dispatchEvent(new CustomEvent('tracking', { detail: { name, data } }));
178
- }
179
-
180
- // What the apps start after a yes (afterConsent), waiting for one; and the categories of each started on this page.
181
- const waiting: { categories: ConsentCategory[]; run: () => void }[] = [];
182
- const started: ConsentCategory[][] = [];
183
-
184
- let told = false;
185
-
186
- function start(categories: ConsentCategory[], run: () => void) {
187
- if (!live) {
188
- if (!told)
189
- console.info(
190
- `[tracking] Ikke på ${tracking.hosts[0]}: tags hentes ikke, og events logges kun her. Tilføj ?tracking=test for at sende dem.`,
191
- );
192
- told = true;
193
- return;
194
- }
195
- started.push(categories);
196
- run();
197
- }
198
-
199
- /** The categories an app's tag waits for, as its slot writes them on its <meta> (data-consent, from `cookies` in its
200
- * index.ts). */
201
- export const consentOf = (meta: HTMLElement | null) => (meta?.dataset.consent ?? '').split(' ').filter(isConsentCategory);
202
-
203
- /** Runs `run` once the visitor has said yes to one of `categories` in the cookie banner (now, if they already have),
204
- * on the site's own address (`hosts`, or with ?tracking=test): for an app that loads something that sets cookies
205
- * (`cookies` in its index.ts, e.g. Google Analytics, src/apps/google-analytics/). A no to all of them after a yes
206
- * reloads the page, so it stops. */
207
- export function afterConsent(categories: ConsentCategory[], run: () => void) {
208
- if (categories.some(granted)) start(categories, run);
209
- else waiting.push({ categories, run });
210
- }
211
-
212
- // The cookies of each category the visitor has not said yes to, also any left from before (a yes earlier).
213
- removeCookies(CONSENT_CATEGORIES.filter((category) => !granted(category)));
214
-
215
- // A time was booked: an app that books says so with the event "booking:done" on the window, with how
216
- // (`method`, e.g. "calendly": src/apps/calendly/).
217
- addEventListener('booking:done', (e) => track('book_appointment', { method: (e as CustomEvent).detail?.method }));
218
-
219
- document.addEventListener('click', (e) => {
220
- const link = (e.target as Element | null)?.closest?.('a');
221
- const href = link?.getAttribute('href') ?? '';
222
- if (href.startsWith('tel:')) track('click_to_call', { link_url: href });
223
- else if (href.startsWith('mailto:')) track('email_click', { link_url: href });
224
- else if (href === site.bookingPath) track('open_booking', { page: location.pathname });
225
- });
1
+ // The visitor's yes or no to each category of cookies in the cookie banner (@morphemecms/cms/lib/cookieCategories.mjs:
2
+ // preferences, statistics, marketing, targeting; necessary ones are not asked about), and the site's events. The apps that measure
3
+ // (Google Analytics, Google Ads, Google Tag Manager: src/apps/) load their tags with afterConsent, only after a yes to
4
+ // their category (Google consent mode, "basic": the Google tag is not loaded at all until the visitor says yes, and it
5
+ // is told which categories the visitor said yes to), and send the events on. Where events are sent from:
6
+ // `tracking.hosts` in packages/editor/site/tracking.ts.
7
+ //
8
+ // Events (GA4 event names; mark them as key events in GA4 and import them in Google Ads):
9
+ // - book_appointment: a time was booked (the app Calendly: in the booking sheet or on the booking page)
10
+ // - open_booking: the booking calendar was opened
11
+ // - click_to_call: a phone link was tapped
12
+ // - email_click: an e-mail link was clicked
13
+ import { CONSENT_CATEGORIES, isConsentCategory, type ConsentCategory } from '@morphemecms/cms/lib/cookieCategories.mjs';
14
+ import { cookieList } from './cookieList';
15
+ import { tracking } from './tracking';
16
+ import { site } from './site';
17
+
18
+ /** The visitor's yes (true) or no to each category the cookie banner asked about. */
19
+ export type Consent = Partial<Record<ConsentCategory, boolean>>;
20
+ const KEY = 'consent';
21
+ const MAX_AGE = 365 * 24 * 60 * 60 * 1000; // ask again after a year
22
+
23
+ declare global {
24
+ interface Window {
25
+ dataLayer: unknown[];
26
+ gtag: (...args: unknown[]) => void;
27
+ }
28
+ }
29
+
30
+ const params = new URLSearchParams(location.search);
31
+ try {
32
+ if (params.get('tracking') === 'test') sessionStorage.setItem('tracking-test', '1');
33
+ } catch {}
34
+ /** Whether events are sent from another address than the site's own (?tracking=test): GA4's debug mode. */
35
+ export const testing = (() => {
36
+ try {
37
+ return sessionStorage.getItem('tracking-test') === '1';
38
+ } catch {
39
+ return false;
40
+ }
41
+ })();
42
+ const live = tracking.hosts.includes(location.hostname) || testing;
43
+
44
+ /** The visitor's answer in the cookie banner, within the last year; undefined without one (or with one from before
45
+ * the banner asked about each category, which is asked again). */
46
+ export function storedConsent(): Consent | undefined {
47
+ try {
48
+ const saved = JSON.parse(localStorage.getItem(KEY) ?? 'null');
49
+ if (!saved || typeof saved.choices !== 'object' || !saved.choices || Date.now() - saved.at >= MAX_AGE) return undefined;
50
+ return Object.fromEntries(
51
+ Object.entries(saved.choices).filter(([category, yes]) => isConsentCategory(category) && typeof yes === 'boolean'),
52
+ ) as Consent;
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ const granted = (category: ConsentCategory) => storedConsent()?.[category] === true;
59
+
60
+ // Google's consent mode: what its tags may store, by the visitor's answer (statistics: Analytics; marketing: Ads). A yes
61
+ // to marketing is a yes to measuring whether the ads work; personalised ads (remarketing) only after a yes to targeted
62
+ // ads, which the banner asks about only when an app is set to use them (`cookies` in its index.ts), so a site that does
63
+ // not use them never allows them. Targeted ads store what ads do (ad_storage, ad_user_data) too.
64
+ function googleSignals() {
65
+ const yes = (...categories: ConsentCategory[]) => (categories.some(granted) ? 'granted' : 'denied');
66
+ return {
67
+ analytics_storage: yes('statistics'),
68
+ ad_storage: yes('marketing', 'targeting'),
69
+ ad_user_data: yes('marketing', 'targeting'),
70
+ ad_personalization: yes('targeting'),
71
+ };
72
+ }
73
+
74
+ // Google signals (Analytics data joined with Google accounts, for ads and demographics) and ad personalisation in any
75
+ // tag, likewise only after a yes to targeted ads, whatever the accounts' own settings say.
76
+ function googleSettings() {
77
+ window.gtag('set', 'allow_google_signals', granted('targeting'));
78
+ window.gtag('set', 'allow_ad_personalization_signals', granted('targeting'));
79
+ }
80
+
81
+ /** Google's data layer with the visitor's answer in consent mode, for a tag of Google's (googleTag, Google Tag
82
+ * Manager's container, src/apps/gtm/), set up once, before the first tag loads; a new answer updates it (choose). */
83
+ export function googleConsent() {
84
+ window.dataLayer = window.dataLayer || [];
85
+ if (typeof window.gtag === 'function') return;
86
+ window.gtag = function gtag() {
87
+ // eslint-disable-next-line prefer-rest-params
88
+ window.dataLayer.push(arguments);
89
+ };
90
+ window.gtag('consent', 'default', googleSignals());
91
+ googleSettings();
92
+ window.gtag('js', new Date());
93
+ }
94
+
95
+ let tagged = false;
96
+
97
+ /** The Google tag (gtag.js) with this ID (Google Analytics' G-…, Google Ads' AW-…), for the apps that measure with it
98
+ * (src/apps/google-analytics/, src/apps/google-ads/), in afterConsent: loaded once for all of them, each ID configured. */
99
+ export function googleTag(id: string, config: Record<string, unknown> = {}) {
100
+ googleConsent();
101
+ window.gtag('config', id, config);
102
+ if (tagged) return;
103
+ tagged = true;
104
+ const s = document.createElement('script');
105
+ s.async = true;
106
+ s.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(id)}`;
107
+ document.head.appendChild(s);
108
+ }
109
+
110
+ // The cookies of each category on the site's own address, to be removed after a no to it: those of the cookie list
111
+ // ("Lister" › "Cookies", `*` for the rest of a name), and the others Google's tag may set (GA4: _ga, _ga_<id>, and
112
+ // _gid and _gat from older tags; Ads: _gcl_au and the other _gcl_ ones).
113
+ const pattern = (name: string) => new RegExp(`^${name.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`);
114
+ const COOKIES: Record<ConsentCategory, RegExp[]> = {
115
+ preferences: [],
116
+ statistics: [/^(_ga(_.+)?|_gid|_gat(_.+)?)$/],
117
+ marketing: [/^_gcl_.+$/],
118
+ targeting: [],
119
+ };
120
+ for (const c of cookieList) if (isConsentCategory(c.category)) COOKIES[c.category].push(pattern(c.name));
121
+
122
+ // Each one is removed on the hostname and on every parent domain (Google's are set on example.dk for
123
+ // www.example.dk).
124
+ function removeCookies(categories: ConsentCategory[]) {
125
+ const patterns = categories.flatMap((category) => COOKIES[category]);
126
+ const names = document.cookie
127
+ .split(';')
128
+ .map((c) => c.split('=')[0].trim())
129
+ .filter((name) => patterns.some((p) => p.test(name)));
130
+ if (!names.length) return;
131
+ const labels = location.hostname.split('.');
132
+ const domains = ['', ...labels.slice(0, -1).map((_, i) => `; domain=.${labels.slice(i).join('.')}`)];
133
+ for (const name of names)
134
+ for (const domain of domains) document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${domain}`;
135
+ }
136
+
137
+ /** The random id of this browser's answers in the consent log (packages/editor/worker/consent.js), shown in the cookie banner so the
138
+ * visitor can point to their own; null before the first answer. */
139
+ export function consentId(): string | null {
140
+ try {
141
+ const id = JSON.parse(localStorage.getItem(KEY) ?? 'null')?.id;
142
+ return typeof id === 'string' ? id : null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ /** Saves the visitor's answer (a yes or no to each category the banner asks about), in the browser and in the consent
149
+ * log, starts what waited for a yes and removes the cookies of each category said no to. A no to all the categories of
150
+ * something already started on this page reloads it, so it stops. */
151
+ export function choose(consent: Consent) {
152
+ const id = consentId() ?? crypto.randomUUID();
153
+ try {
154
+ localStorage.setItem(KEY, JSON.stringify({ choices: consent, at: Date.now(), id }));
155
+ } catch {}
156
+ logConsent(id, consent);
157
+ if (typeof window.gtag === 'function') {
158
+ window.gtag('consent', 'update', googleSignals());
159
+ googleSettings();
160
+ }
161
+ removeCookies(CONSENT_CATEGORIES.filter((category) => !granted(category)));
162
+ if (started.some((categories) => !categories.some(granted))) return location.reload();
163
+ for (const wait of waiting.splice(0)) afterConsent(wait.categories, wait.run);
164
+ }
165
+
166
+ // The answer to the consent log on the site's Worker (/api/consent), also when the page reloads right after
167
+ // (keepalive). Not the answers of a test (?tracking=test: the cookie scan's visitors, src/apps/cookiescanner/).
168
+ function logConsent(id: string, consent: Consent) {
169
+ if (testing) return;
170
+ fetch('/api/consent', { method: 'POST', keepalive: true, body: JSON.stringify({ id, choices: consent }) }).catch(() => {});
171
+ }
172
+
173
+ export function track(name: string, data: Record<string, unknown> = {}) {
174
+ if (!CONSENT_CATEGORIES.some(granted)) return;
175
+ if (!live) return console.info('[tracking]', name, data);
176
+ // The apps that measure send it on (Google Analytics, Google Ads, Google Tag Manager: src/apps/).
177
+ dispatchEvent(new CustomEvent('tracking', { detail: { name, data } }));
178
+ }
179
+
180
+ // What the apps start after a yes (afterConsent), waiting for one; and the categories of each started on this page.
181
+ const waiting: { categories: ConsentCategory[]; run: () => void }[] = [];
182
+ const started: ConsentCategory[][] = [];
183
+
184
+ let told = false;
185
+
186
+ function start(categories: ConsentCategory[], run: () => void) {
187
+ if (!live) {
188
+ if (!told)
189
+ console.info(
190
+ `[tracking] Ikke på ${tracking.hosts[0]}: tags hentes ikke, og events logges kun her. Tilføj ?tracking=test for at sende dem.`,
191
+ );
192
+ told = true;
193
+ return;
194
+ }
195
+ started.push(categories);
196
+ run();
197
+ }
198
+
199
+ /** The categories an app's tag waits for, as its slot writes them on its <meta> (data-consent, from `cookies` in its
200
+ * index.ts). */
201
+ export const consentOf = (meta: HTMLElement | null) => (meta?.dataset.consent ?? '').split(' ').filter(isConsentCategory);
202
+
203
+ /** Runs `run` once the visitor has said yes to one of `categories` in the cookie banner (now, if they already have),
204
+ * on the site's own address (`hosts`, or with ?tracking=test): for an app that loads something that sets cookies
205
+ * (`cookies` in its index.ts, e.g. Google Analytics, src/apps/google-analytics/). A no to all of them after a yes
206
+ * reloads the page, so it stops. */
207
+ export function afterConsent(categories: ConsentCategory[], run: () => void) {
208
+ if (categories.some(granted)) start(categories, run);
209
+ else waiting.push({ categories, run });
210
+ }
211
+
212
+ // The cookies of each category the visitor has not said yes to, also any left from before (a yes earlier).
213
+ removeCookies(CONSENT_CATEGORIES.filter((category) => !granted(category)));
214
+
215
+ // A time was booked: an app that books says so with the event "booking:done" on the window, with how
216
+ // (`method`, e.g. "calendly": src/apps/calendly/).
217
+ addEventListener('booking:done', (e) => track('book_appointment', { method: (e as CustomEvent).detail?.method }));
218
+
219
+ document.addEventListener('click', (e) => {
220
+ const link = (e.target as Element | null)?.closest?.('a');
221
+ const href = link?.getAttribute('href') ?? '';
222
+ if (href.startsWith('tel:')) track('click_to_call', { link_url: href });
223
+ else if (href.startsWith('mailto:')) track('email_click', { link_url: href });
224
+ else if (href === site.bookingPath) track('open_booking', { page: location.pathname });
225
+ });
package/site/tracking.ts CHANGED
@@ -1,9 +1,9 @@
1
- // Where the site measures from: only these hostnames load the apps' tags (Google Analytics, Google Ads, Google Tag
2
- // Manager, src/apps/) and send events (src/scripts/tracking.ts). Elsewhere (staging) events are only logged to the
3
- // console, unless the address has ?tracking=test, which sends them (in GA4's debug mode). The IDs are the apps'
4
- // settings (src/data/apps/).
5
- import { config } from '@site/config';
6
-
7
- export const tracking = {
8
- hosts: [config.domain, `www.${config.domain}`],
9
- };
1
+ // Where the site measures from: only these hostnames load the apps' tags (Google Analytics, Google Ads, Google Tag
2
+ // Manager, src/apps/) and send events (src/scripts/tracking.ts). Elsewhere (staging) events are only logged to the
3
+ // console, unless the address has ?tracking=test, which sends them (in GA4's debug mode). The IDs are the apps'
4
+ // settings (src/data/apps/).
5
+ import { config } from '@site/config';
6
+
7
+ export const tracking = {
8
+ hosts: [config.domain, `www.${config.domain}`],
9
+ };