@morphemecms/cms 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -3
  3. package/astro.d.mts +2 -0
  4. package/astro.mjs +162 -0
  5. package/bin/morpheme.mjs +38 -0
  6. package/client.d.ts +10 -0
  7. package/defaults/blockTexts.ts +33 -0
  8. package/defaults/collections.ts +774 -0
  9. package/defaults/colors.ts +125 -0
  10. package/defaults/company.ts +20 -0
  11. package/defaults/editor.ts +178 -0
  12. package/defaults/internalLinks.ts +30 -0
  13. package/defaults/opens.ts +18 -0
  14. package/defaults/popup.tsx +93 -0
  15. package/defaults/privacy.ts +79 -0
  16. package/defaults/rules.ts +450 -0
  17. package/defaults/schema.ts +16 -0
  18. package/defaults/sitedata.ts +129 -0
  19. package/defaults/standard.ts +106 -0
  20. package/defaults/standardPages.ts +87 -0
  21. package/defaults/texts.ts +481 -0
  22. package/demo/README.md +26 -0
  23. package/demo/public/apple-touch-icon.png +0 -0
  24. package/demo/public/editor/apple-touch-icon.png +0 -0
  25. package/demo/public/editor/icon-192.png +0 -0
  26. package/demo/public/editor/icon-512.png +0 -0
  27. package/demo/public/editor/icon-maskable-512.png +0 -0
  28. package/demo/public/favicon.ico +0 -0
  29. package/demo/public/favicon.svg +1 -0
  30. package/demo/public/images/uploads/2026/10/andromeda.webp +0 -0
  31. package/demo/public/images/uploads/2026/10/earth-from-space.webp +0 -0
  32. package/demo/public/images/uploads/2026/10/editor-blog.webp +0 -0
  33. package/demo/public/images/uploads/2026/10/editor-colors.webp +0 -0
  34. package/demo/public/images/uploads/2026/10/editor-home.webp +0 -0
  35. package/demo/public/images/uploads/2026/10/editor-page.webp +0 -0
  36. package/demo/public/images/uploads/2026/10/editor-pages.webp +0 -0
  37. package/demo/public/images/uploads/2026/10/editor-seo.webp +0 -0
  38. package/demo/public/images/uploads/2026/10/editor-start.webp +0 -0
  39. package/demo/public/images/uploads/2026/10/editor-themes.webp +0 -0
  40. package/demo/public/images/uploads/2026/10/moon.webp +0 -0
  41. package/demo/public/images/uploads/2026/10/saturn.webp +0 -0
  42. package/demo/public/logo.svg +1 -0
  43. package/demo/src/content/blog/build-a-page-from-blocks.md +19 -0
  44. package/demo/src/content/blog/colors-everyone-can-read.md +19 -0
  45. package/demo/src/content/blog/edit-your-website-from-your-phone.md +21 -0
  46. package/demo/src/content/blog/every-save-is-a-version.md +21 -0
  47. package/demo/src/content/blog/schedule-a-post.md +17 -0
  48. package/demo/src/content/blog/secure-from-day-one.md +17 -0
  49. package/demo/src/content/blog/seo-without-the-guesswork.md +23 -0
  50. package/demo/src/content/blog/switch-themes-without-losing-anything.md +23 -0
  51. package/demo/src/content/blog/the-app-store.md +21 -0
  52. package/demo/src/content/pages/about.md +22 -0
  53. package/demo/src/content/pages/app-store.md +30 -0
  54. package/demo/src/content/pages/blocks.md +98 -0
  55. package/demo/src/content/pages/blog-page.md +11 -0
  56. package/demo/src/content/pages/contact.md +16 -0
  57. package/demo/src/content/pages/get-started.md +20 -0
  58. package/demo/src/content/pages/home.md +26 -0
  59. package/demo/src/content/pages/page-not-found.md +11 -0
  60. package/demo/src/content/pages/privacy-policy.md +24 -0
  61. package/demo/src/data/blocks.json +25 -0
  62. package/demo/src/data/booking.json +6 -0
  63. package/demo/src/data/categories/apps.json +6 -0
  64. package/demo/src/data/categories/design.json +6 -0
  65. package/demo/src/data/categories/editing.json +6 -0
  66. package/demo/src/data/categories/security.json +6 -0
  67. package/demo/src/data/categories/seo.json +6 -0
  68. package/demo/src/data/colors.json +75 -0
  69. package/demo/src/data/contact.json +8 -0
  70. package/demo/src/data/cookies.json +21 -0
  71. package/demo/src/data/footer.json +27 -0
  72. package/demo/src/data/menu.json +35 -0
  73. package/demo/src/data/mobile-menu.json +25 -0
  74. package/demo/src/data/popups/booking-bar.json +13 -0
  75. package/demo/src/data/popups/try-the-editor.json +16 -0
  76. package/demo/src/data/standard-pages.json +8 -0
  77. package/demo/src/data/testimonials/sample-bakery.json +10 -0
  78. package/demo/src/data/testimonials/sample-clinic.json +10 -0
  79. package/demo/src/data/testimonials/sample-studio.json +10 -0
  80. package/demo/src/site/colors.ts +11 -0
  81. package/demo/src/site/config.ts +19 -0
  82. package/icons/lucide/LICENSE +43 -0
  83. package/icons/lucide/LICENSE-material-design-icons +20 -0
  84. package/icons/lucide/address-book.svg +1 -0
  85. package/icons/lucide/app-window.svg +1 -0
  86. package/icons/lucide/arrow-counter-clockwise.svg +1 -0
  87. package/icons/lucide/arrow-down.svg +1 -0
  88. package/icons/lucide/arrow-left.svg +1 -0
  89. package/icons/lucide/arrow-right.svg +1 -0
  90. package/icons/lucide/arrow-square-out.svg +1 -0
  91. package/icons/lucide/arrow-u-up-left.svg +1 -0
  92. package/icons/lucide/arrow-u-up-right.svg +1 -0
  93. package/icons/lucide/arrow-up-right.svg +1 -0
  94. package/icons/lucide/arrow-up.svg +1 -0
  95. package/icons/lucide/arrows-down-up.svg +1 -0
  96. package/icons/lucide/arrows-in-line-horizontal.svg +1 -0
  97. package/icons/lucide/arrows-out-line-horizontal.svg +1 -0
  98. package/icons/lucide/article.svg +1 -0
  99. package/icons/lucide/bank.svg +1 -0
  100. package/icons/lucide/bell.svg +1 -0
  101. package/icons/lucide/bicycle.svg +1 -0
  102. package/icons/lucide/binoculars.svg +1 -0
  103. package/icons/lucide/book-open-text.svg +1 -0
  104. package/icons/lucide/book-open.svg +1 -0
  105. package/icons/lucide/browser.svg +1 -0
  106. package/icons/lucide/buildings.svg +1 -0
  107. package/icons/lucide/bus.svg +1 -0
  108. package/icons/lucide/calendar-blank.svg +1 -0
  109. package/icons/lucide/calendar-check.svg +1 -0
  110. package/icons/lucide/car.svg +1 -0
  111. package/icons/lucide/cards.svg +1 -0
  112. package/icons/lucide/caret-down.svg +1 -0
  113. package/icons/lucide/caret-left.svg +1 -0
  114. package/icons/lucide/caret-right.svg +1 -0
  115. package/icons/lucide/caret-up.svg +1 -0
  116. package/icons/lucide/certificate.svg +1 -0
  117. package/icons/lucide/chart-line-up.svg +1 -0
  118. package/icons/lucide/chats.svg +1 -0
  119. package/icons/lucide/check-circle.svg +1 -0
  120. package/icons/lucide/check.svg +1 -0
  121. package/icons/lucide/circle-half.svg +1 -0
  122. package/icons/lucide/circle.svg +1 -0
  123. package/icons/lucide/clock-counter-clockwise.svg +1 -0
  124. package/icons/lucide/clock.svg +1 -0
  125. package/icons/lucide/code.svg +1 -0
  126. package/icons/lucide/coffee.svg +1 -0
  127. package/icons/lucide/columns.svg +1 -0
  128. package/icons/lucide/cookie.svg +1 -0
  129. package/icons/lucide/copy.svg +1 -0
  130. package/icons/lucide/credit-card.svg +1 -0
  131. package/icons/lucide/currency-circle-dollar.svg +1 -0
  132. package/icons/lucide/cursor-click.svg +1 -0
  133. package/icons/lucide/database.svg +1 -0
  134. package/icons/lucide/desktop.svg +1 -0
  135. package/icons/lucide/device-mobile.svg +1 -0
  136. package/icons/lucide/door-open.svg +1 -0
  137. package/icons/lucide/download-simple.svg +1 -0
  138. package/icons/lucide/envelope-simple.svg +1 -0
  139. package/icons/lucide/eye-slash.svg +1 -0
  140. package/icons/lucide/eye.svg +1 -0
  141. package/icons/lucide/file-pdf.svg +1 -0
  142. package/icons/lucide/file-text.svg +1 -0
  143. package/icons/lucide/file-video.svg +1 -0
  144. package/icons/lucide/file-zip.svg +1 -0
  145. package/icons/lucide/flower-lotus.svg +1 -0
  146. package/icons/lucide/folder-simple.svg +1 -0
  147. package/icons/lucide/funnel.svg +1 -0
  148. package/icons/lucide/gauge.svg +1 -0
  149. package/icons/lucide/gear.svg +1 -0
  150. package/icons/lucide/github-logo.svg +1 -0
  151. package/icons/lucide/globe.svg +1 -0
  152. package/icons/lucide/google-logo.svg +1 -0
  153. package/icons/lucide/graduation-cap.svg +1 -0
  154. package/icons/lucide/hammer.svg +1 -0
  155. package/icons/lucide/hand-heart.svg +1 -0
  156. package/icons/lucide/handshake.svg +1 -0
  157. package/icons/lucide/hard-drives.svg +1 -0
  158. package/icons/lucide/heart.svg +1 -0
  159. package/icons/lucide/house.svg +1 -0
  160. package/icons/lucide/image-square.svg +1 -0
  161. package/icons/lucide/image.svg +1 -0
  162. package/icons/lucide/info.svg +1 -0
  163. package/icons/lucide/keyboard.svg +1 -0
  164. package/icons/lucide/leaf.svg +1 -0
  165. package/icons/lucide/lifebuoy.svg +1 -0
  166. package/icons/lucide/lightbulb.svg +1 -0
  167. package/icons/lucide/link-break.svg +1 -0
  168. package/icons/lucide/link-simple.svg +1 -0
  169. package/icons/lucide/list-bullets.svg +1 -0
  170. package/icons/lucide/list-checks.svg +1 -0
  171. package/icons/lucide/list-numbers.svg +1 -0
  172. package/icons/lucide/list.svg +1 -0
  173. package/icons/lucide/lock-simple-open.svg +1 -0
  174. package/icons/lucide/lock-simple.svg +1 -0
  175. package/icons/lucide/magnifying-glass.svg +1 -0
  176. package/icons/lucide/map-pin.svg +1 -0
  177. package/icons/lucide/map-trifold.svg +1 -0
  178. package/icons/lucide/medal.svg +1 -0
  179. package/icons/lucide/megaphone.svg +1 -0
  180. package/icons/lucide/minus.svg +1 -0
  181. package/icons/lucide/moon.svg +1 -0
  182. package/icons/lucide/newspaper.svg +1 -0
  183. package/icons/lucide/pack.json +23 -0
  184. package/icons/lucide/paint-brush-broad.svg +1 -0
  185. package/icons/lucide/palette.svg +1 -0
  186. package/icons/lucide/paper-plane-tilt.svg +1 -0
  187. package/icons/lucide/pencil-simple.svg +1 -0
  188. package/icons/lucide/phone.svg +1 -0
  189. package/icons/lucide/play-fill.svg +1 -0
  190. package/icons/lucide/plus.svg +1 -0
  191. package/icons/lucide/prohibit.svg +1 -0
  192. package/icons/lucide/puzzle-piece.svg +1 -0
  193. package/icons/lucide/question.svg +1 -0
  194. package/icons/lucide/quotes.svg +1 -0
  195. package/icons/lucide/rows.svg +1 -0
  196. package/icons/lucide/ruler.svg +1 -0
  197. package/icons/lucide/scan.svg +1 -0
  198. package/icons/lucide/shapes.svg +1 -0
  199. package/icons/lucide/share-network.svg +1 -0
  200. package/icons/lucide/shield-check.svg +1 -0
  201. package/icons/lucide/sidebar-simple.svg +1 -0
  202. package/icons/lucide/sign-out.svg +1 -0
  203. package/icons/lucide/signpost.svg +1 -0
  204. package/icons/lucide/sliders-horizontal.svg +1 -0
  205. package/icons/lucide/smiley.svg +1 -0
  206. package/icons/lucide/sparkle.svg +1 -0
  207. package/icons/lucide/square-half-bottom.svg +1 -0
  208. package/icons/lucide/square.svg +1 -0
  209. package/icons/lucide/squares-four.svg +1 -0
  210. package/icons/lucide/sticker.svg +1 -0
  211. package/icons/lucide/storefront.svg +1 -0
  212. package/icons/lucide/sun.svg +1 -0
  213. package/icons/lucide/swatches.svg +1 -0
  214. package/icons/lucide/tag.svg +1 -0
  215. package/icons/lucide/text-aa.svg +1 -0
  216. package/icons/lucide/text-align-left.svg +1 -0
  217. package/icons/lucide/text-b.svg +1 -0
  218. package/icons/lucide/text-h-one.svg +1 -0
  219. package/icons/lucide/text-h-three.svg +1 -0
  220. package/icons/lucide/text-h-two.svg +1 -0
  221. package/icons/lucide/text-italic.svg +1 -0
  222. package/icons/lucide/text-t.svg +1 -0
  223. package/icons/lucide/ticket.svg +1 -0
  224. package/icons/lucide/toggle-right.svg +1 -0
  225. package/icons/lucide/train.svg +1 -0
  226. package/icons/lucide/trash.svg +1 -0
  227. package/icons/lucide/upload-simple.svg +1 -0
  228. package/icons/lucide/user-circle.svg +1 -0
  229. package/icons/lucide/user.svg +1 -0
  230. package/icons/lucide/users-three.svg +1 -0
  231. package/icons/lucide/users.svg +1 -0
  232. package/icons/lucide/video-camera.svg +1 -0
  233. package/icons/lucide/warning-circle.svg +1 -0
  234. package/icons/lucide/warning.svg +1 -0
  235. package/icons/lucide/wave-sine.svg +1 -0
  236. package/icons/lucide/wheelchair.svg +1 -0
  237. package/icons/lucide/wrench.svg +1 -0
  238. package/icons/lucide/x.svg +1 -0
  239. package/icons/material-symbols/LICENSE +201 -0
  240. package/icons/material-symbols/LICENSE-material-design-icons +20 -0
  241. package/icons/material-symbols/address-book.svg +1 -0
  242. package/icons/material-symbols/app-window.svg +1 -0
  243. package/icons/material-symbols/arrow-counter-clockwise.svg +1 -0
  244. package/icons/material-symbols/arrow-down.svg +1 -0
  245. package/icons/material-symbols/arrow-left.svg +1 -0
  246. package/icons/material-symbols/arrow-right.svg +1 -0
  247. package/icons/material-symbols/arrow-square-out.svg +1 -0
  248. package/icons/material-symbols/arrow-u-up-left.svg +1 -0
  249. package/icons/material-symbols/arrow-u-up-right.svg +1 -0
  250. package/icons/material-symbols/arrow-up-right.svg +1 -0
  251. package/icons/material-symbols/arrow-up.svg +1 -0
  252. package/icons/material-symbols/arrows-down-up.svg +1 -0
  253. package/icons/material-symbols/arrows-in-line-horizontal.svg +1 -0
  254. package/icons/material-symbols/arrows-out-line-horizontal.svg +1 -0
  255. package/icons/material-symbols/article.svg +1 -0
  256. package/icons/material-symbols/bank.svg +1 -0
  257. package/icons/material-symbols/bell.svg +1 -0
  258. package/icons/material-symbols/bicycle.svg +1 -0
  259. package/icons/material-symbols/binoculars.svg +1 -0
  260. package/icons/material-symbols/book-open-text.svg +1 -0
  261. package/icons/material-symbols/book-open.svg +1 -0
  262. package/icons/material-symbols/browser.svg +1 -0
  263. package/icons/material-symbols/buildings.svg +1 -0
  264. package/icons/material-symbols/bus.svg +1 -0
  265. package/icons/material-symbols/calendar-blank.svg +1 -0
  266. package/icons/material-symbols/calendar-check.svg +1 -0
  267. package/icons/material-symbols/car.svg +1 -0
  268. package/icons/material-symbols/cards.svg +1 -0
  269. package/icons/material-symbols/caret-down.svg +1 -0
  270. package/icons/material-symbols/caret-left.svg +1 -0
  271. package/icons/material-symbols/caret-right.svg +1 -0
  272. package/icons/material-symbols/caret-up.svg +1 -0
  273. package/icons/material-symbols/certificate.svg +1 -0
  274. package/icons/material-symbols/chart-line-up.svg +1 -0
  275. package/icons/material-symbols/chats.svg +1 -0
  276. package/icons/material-symbols/check-circle.svg +1 -0
  277. package/icons/material-symbols/check.svg +1 -0
  278. package/icons/material-symbols/circle-half.svg +1 -0
  279. package/icons/material-symbols/circle.svg +1 -0
  280. package/icons/material-symbols/clock-counter-clockwise.svg +1 -0
  281. package/icons/material-symbols/clock.svg +1 -0
  282. package/icons/material-symbols/code.svg +1 -0
  283. package/icons/material-symbols/coffee.svg +1 -0
  284. package/icons/material-symbols/columns.svg +1 -0
  285. package/icons/material-symbols/cookie.svg +1 -0
  286. package/icons/material-symbols/copy.svg +1 -0
  287. package/icons/material-symbols/credit-card.svg +1 -0
  288. package/icons/material-symbols/currency-circle-dollar.svg +1 -0
  289. package/icons/material-symbols/cursor-click.svg +1 -0
  290. package/icons/material-symbols/database.svg +1 -0
  291. package/icons/material-symbols/desktop.svg +1 -0
  292. package/icons/material-symbols/device-mobile.svg +1 -0
  293. package/icons/material-symbols/door-open.svg +1 -0
  294. package/icons/material-symbols/download-simple.svg +1 -0
  295. package/icons/material-symbols/envelope-simple.svg +1 -0
  296. package/icons/material-symbols/eye-slash.svg +1 -0
  297. package/icons/material-symbols/eye.svg +1 -0
  298. package/icons/material-symbols/file-pdf.svg +1 -0
  299. package/icons/material-symbols/file-text.svg +1 -0
  300. package/icons/material-symbols/file-video.svg +1 -0
  301. package/icons/material-symbols/file-zip.svg +1 -0
  302. package/icons/material-symbols/flower-lotus.svg +1 -0
  303. package/icons/material-symbols/folder-simple.svg +1 -0
  304. package/icons/material-symbols/funnel.svg +1 -0
  305. package/icons/material-symbols/gauge.svg +1 -0
  306. package/icons/material-symbols/gear.svg +1 -0
  307. package/icons/material-symbols/github-logo.svg +1 -0
  308. package/icons/material-symbols/globe.svg +1 -0
  309. package/icons/material-symbols/google-logo.svg +1 -0
  310. package/icons/material-symbols/graduation-cap.svg +1 -0
  311. package/icons/material-symbols/hammer.svg +1 -0
  312. package/icons/material-symbols/hand-heart.svg +1 -0
  313. package/icons/material-symbols/handshake.svg +1 -0
  314. package/icons/material-symbols/hard-drives.svg +1 -0
  315. package/icons/material-symbols/heart.svg +1 -0
  316. package/icons/material-symbols/house.svg +1 -0
  317. package/icons/material-symbols/image-square.svg +1 -0
  318. package/icons/material-symbols/image.svg +1 -0
  319. package/icons/material-symbols/info.svg +1 -0
  320. package/icons/material-symbols/keyboard.svg +1 -0
  321. package/icons/material-symbols/leaf.svg +1 -0
  322. package/icons/material-symbols/lifebuoy.svg +1 -0
  323. package/icons/material-symbols/lightbulb.svg +1 -0
  324. package/icons/material-symbols/link-break.svg +1 -0
  325. package/icons/material-symbols/link-simple.svg +1 -0
  326. package/icons/material-symbols/list-bullets.svg +1 -0
  327. package/icons/material-symbols/list-checks.svg +1 -0
  328. package/icons/material-symbols/list-numbers.svg +1 -0
  329. package/icons/material-symbols/list.svg +1 -0
  330. package/icons/material-symbols/lock-simple-open.svg +1 -0
  331. package/icons/material-symbols/lock-simple.svg +1 -0
  332. package/icons/material-symbols/magnifying-glass.svg +1 -0
  333. package/icons/material-symbols/map-pin.svg +1 -0
  334. package/icons/material-symbols/map-trifold.svg +1 -0
  335. package/icons/material-symbols/medal.svg +1 -0
  336. package/icons/material-symbols/megaphone.svg +1 -0
  337. package/icons/material-symbols/minus.svg +1 -0
  338. package/icons/material-symbols/moon.svg +1 -0
  339. package/icons/material-symbols/newspaper.svg +1 -0
  340. package/icons/material-symbols/pack.json +29 -0
  341. package/icons/material-symbols/paint-brush-broad.svg +1 -0
  342. package/icons/material-symbols/palette.svg +1 -0
  343. package/icons/material-symbols/paper-plane-tilt.svg +1 -0
  344. package/icons/material-symbols/pencil-simple.svg +1 -0
  345. package/icons/material-symbols/phone.svg +1 -0
  346. package/icons/material-symbols/play-fill.svg +1 -0
  347. package/icons/material-symbols/plus.svg +1 -0
  348. package/icons/material-symbols/prohibit.svg +1 -0
  349. package/icons/material-symbols/puzzle-piece.svg +1 -0
  350. package/icons/material-symbols/question.svg +1 -0
  351. package/icons/material-symbols/quotes.svg +1 -0
  352. package/icons/material-symbols/rows.svg +1 -0
  353. package/icons/material-symbols/ruler.svg +1 -0
  354. package/icons/material-symbols/scan.svg +1 -0
  355. package/icons/material-symbols/shapes.svg +1 -0
  356. package/icons/material-symbols/share-network.svg +1 -0
  357. package/icons/material-symbols/shield-check.svg +1 -0
  358. package/icons/material-symbols/sidebar-simple.svg +1 -0
  359. package/icons/material-symbols/sign-out.svg +1 -0
  360. package/icons/material-symbols/signpost.svg +1 -0
  361. package/icons/material-symbols/sliders-horizontal.svg +1 -0
  362. package/icons/material-symbols/smiley.svg +1 -0
  363. package/icons/material-symbols/sparkle.svg +1 -0
  364. package/icons/material-symbols/square-half-bottom.svg +1 -0
  365. package/icons/material-symbols/square.svg +1 -0
  366. package/icons/material-symbols/squares-four.svg +1 -0
  367. package/icons/material-symbols/sticker.svg +1 -0
  368. package/icons/material-symbols/storefront.svg +1 -0
  369. package/icons/material-symbols/sun.svg +1 -0
  370. package/icons/material-symbols/swatches.svg +1 -0
  371. package/icons/material-symbols/tag.svg +1 -0
  372. package/icons/material-symbols/text-aa.svg +1 -0
  373. package/icons/material-symbols/text-align-left.svg +1 -0
  374. package/icons/material-symbols/text-b.svg +1 -0
  375. package/icons/material-symbols/text-h-one.svg +1 -0
  376. package/icons/material-symbols/text-h-three.svg +1 -0
  377. package/icons/material-symbols/text-h-two.svg +1 -0
  378. package/icons/material-symbols/text-italic.svg +1 -0
  379. package/icons/material-symbols/text-t.svg +1 -0
  380. package/icons/material-symbols/ticket.svg +1 -0
  381. package/icons/material-symbols/toggle-right.svg +1 -0
  382. package/icons/material-symbols/train.svg +1 -0
  383. package/icons/material-symbols/trash.svg +1 -0
  384. package/icons/material-symbols/upload-simple.svg +1 -0
  385. package/icons/material-symbols/user-circle.svg +1 -0
  386. package/icons/material-symbols/user.svg +1 -0
  387. package/icons/material-symbols/users-three.svg +1 -0
  388. package/icons/material-symbols/users.svg +1 -0
  389. package/icons/material-symbols/video-camera.svg +1 -0
  390. package/icons/material-symbols/warning-circle.svg +1 -0
  391. package/icons/material-symbols/warning.svg +1 -0
  392. package/icons/material-symbols/wave-sine.svg +1 -0
  393. package/icons/material-symbols/wheelchair.svg +1 -0
  394. package/icons/material-symbols/wrench.svg +1 -0
  395. package/icons/material-symbols/x.svg +1 -0
  396. package/icons/phosphor/LICENSE +21 -0
  397. package/icons/phosphor/address-book.svg +1 -0
  398. package/icons/phosphor/app-window.svg +1 -0
  399. package/icons/phosphor/arrow-counter-clockwise.svg +1 -0
  400. package/icons/phosphor/arrow-down.svg +1 -0
  401. package/icons/phosphor/arrow-left.svg +1 -0
  402. package/icons/phosphor/arrow-right.svg +1 -0
  403. package/icons/phosphor/arrow-square-out.svg +1 -0
  404. package/icons/phosphor/arrow-u-up-left.svg +1 -0
  405. package/icons/phosphor/arrow-u-up-right.svg +1 -0
  406. package/icons/phosphor/arrow-up-right.svg +1 -0
  407. package/icons/phosphor/arrow-up.svg +1 -0
  408. package/icons/phosphor/arrows-down-up.svg +1 -0
  409. package/icons/phosphor/arrows-in-line-horizontal.svg +1 -0
  410. package/icons/phosphor/arrows-out-line-horizontal.svg +1 -0
  411. package/icons/phosphor/article.svg +1 -0
  412. package/icons/phosphor/bank.svg +1 -0
  413. package/icons/phosphor/bell.svg +1 -0
  414. package/icons/phosphor/bicycle.svg +1 -0
  415. package/icons/phosphor/binoculars.svg +1 -0
  416. package/icons/phosphor/book-open-text.svg +1 -0
  417. package/icons/phosphor/book-open.svg +1 -0
  418. package/icons/phosphor/browser.svg +1 -0
  419. package/icons/phosphor/buildings.svg +1 -0
  420. package/icons/phosphor/bus.svg +1 -0
  421. package/icons/phosphor/calendar-blank.svg +1 -0
  422. package/icons/phosphor/calendar-check.svg +1 -0
  423. package/icons/phosphor/car.svg +1 -0
  424. package/icons/phosphor/cards.svg +1 -0
  425. package/icons/phosphor/caret-down.svg +1 -0
  426. package/icons/phosphor/caret-left.svg +1 -0
  427. package/icons/phosphor/caret-right.svg +1 -0
  428. package/icons/phosphor/caret-up.svg +1 -0
  429. package/icons/phosphor/certificate.svg +1 -0
  430. package/icons/phosphor/chart-line-up.svg +1 -0
  431. package/icons/phosphor/chats.svg +1 -0
  432. package/icons/phosphor/check-circle.svg +1 -0
  433. package/icons/phosphor/check.svg +1 -0
  434. package/icons/phosphor/circle-half.svg +1 -0
  435. package/icons/phosphor/circle.svg +1 -0
  436. package/icons/phosphor/clock-counter-clockwise.svg +1 -0
  437. package/icons/phosphor/clock.svg +1 -0
  438. package/icons/phosphor/code.svg +1 -0
  439. package/icons/phosphor/coffee.svg +1 -0
  440. package/icons/phosphor/columns.svg +1 -0
  441. package/icons/phosphor/cookie.svg +1 -0
  442. package/icons/phosphor/copy.svg +1 -0
  443. package/icons/phosphor/credit-card.svg +1 -0
  444. package/icons/phosphor/currency-circle-dollar.svg +1 -0
  445. package/icons/phosphor/cursor-click.svg +1 -0
  446. package/icons/phosphor/database.svg +1 -0
  447. package/icons/phosphor/desktop.svg +1 -0
  448. package/icons/phosphor/device-mobile.svg +1 -0
  449. package/icons/phosphor/door-open.svg +1 -0
  450. package/icons/phosphor/download-simple.svg +1 -0
  451. package/icons/phosphor/envelope-simple.svg +1 -0
  452. package/icons/phosphor/eye-slash.svg +1 -0
  453. package/icons/phosphor/eye.svg +1 -0
  454. package/icons/phosphor/file-pdf.svg +1 -0
  455. package/icons/phosphor/file-text.svg +1 -0
  456. package/icons/phosphor/file-video.svg +1 -0
  457. package/icons/phosphor/file-zip.svg +1 -0
  458. package/icons/phosphor/flower-lotus.svg +1 -0
  459. package/icons/phosphor/folder-simple.svg +1 -0
  460. package/icons/phosphor/funnel.svg +1 -0
  461. package/icons/phosphor/gauge.svg +1 -0
  462. package/icons/phosphor/gear.svg +1 -0
  463. package/icons/phosphor/github-logo.svg +1 -0
  464. package/icons/phosphor/globe.svg +1 -0
  465. package/icons/phosphor/google-logo.svg +1 -0
  466. package/icons/phosphor/graduation-cap.svg +1 -0
  467. package/icons/phosphor/hammer.svg +1 -0
  468. package/icons/phosphor/hand-heart.svg +1 -0
  469. package/icons/phosphor/handshake.svg +1 -0
  470. package/icons/phosphor/hard-drives.svg +1 -0
  471. package/icons/phosphor/heart.svg +1 -0
  472. package/icons/phosphor/house.svg +1 -0
  473. package/icons/phosphor/image-square.svg +1 -0
  474. package/icons/phosphor/image.svg +1 -0
  475. package/icons/phosphor/info.svg +1 -0
  476. package/icons/phosphor/keyboard.svg +1 -0
  477. package/icons/phosphor/leaf.svg +1 -0
  478. package/icons/phosphor/lifebuoy.svg +1 -0
  479. package/icons/phosphor/lightbulb.svg +1 -0
  480. package/icons/phosphor/link-break.svg +1 -0
  481. package/icons/phosphor/link-simple.svg +1 -0
  482. package/icons/phosphor/list-bullets.svg +1 -0
  483. package/icons/phosphor/list-checks.svg +1 -0
  484. package/icons/phosphor/list-numbers.svg +1 -0
  485. package/icons/phosphor/list.svg +1 -0
  486. package/icons/phosphor/lock-simple-open.svg +1 -0
  487. package/icons/phosphor/lock-simple.svg +1 -0
  488. package/icons/phosphor/magnifying-glass.svg +1 -0
  489. package/icons/phosphor/map-pin.svg +1 -0
  490. package/icons/phosphor/map-trifold.svg +1 -0
  491. package/icons/phosphor/medal.svg +1 -0
  492. package/icons/phosphor/megaphone.svg +1 -0
  493. package/icons/phosphor/minus.svg +1 -0
  494. package/icons/phosphor/moon.svg +1 -0
  495. package/icons/phosphor/newspaper.svg +1 -0
  496. package/icons/phosphor/pack.json +8 -0
  497. package/icons/phosphor/paint-brush-broad.svg +1 -0
  498. package/icons/phosphor/palette.svg +1 -0
  499. package/icons/phosphor/paper-plane-tilt.svg +1 -0
  500. package/icons/phosphor/pencil-simple.svg +1 -0
  501. package/icons/phosphor/phone.svg +1 -0
  502. package/icons/phosphor/play-fill.svg +1 -0
  503. package/icons/phosphor/plus.svg +1 -0
  504. package/icons/phosphor/prohibit.svg +1 -0
  505. package/icons/phosphor/puzzle-piece.svg +1 -0
  506. package/icons/phosphor/question.svg +1 -0
  507. package/icons/phosphor/quotes.svg +1 -0
  508. package/icons/phosphor/rows.svg +1 -0
  509. package/icons/phosphor/ruler.svg +1 -0
  510. package/icons/phosphor/scan.svg +1 -0
  511. package/icons/phosphor/shapes.svg +1 -0
  512. package/icons/phosphor/share-network.svg +1 -0
  513. package/icons/phosphor/shield-check.svg +1 -0
  514. package/icons/phosphor/sidebar-simple.svg +1 -0
  515. package/icons/phosphor/sign-out.svg +1 -0
  516. package/icons/phosphor/signpost.svg +1 -0
  517. package/icons/phosphor/sliders-horizontal.svg +1 -0
  518. package/icons/phosphor/smiley.svg +1 -0
  519. package/icons/phosphor/sparkle.svg +1 -0
  520. package/icons/phosphor/square-half-bottom.svg +1 -0
  521. package/icons/phosphor/square.svg +1 -0
  522. package/icons/phosphor/squares-four.svg +1 -0
  523. package/icons/phosphor/sticker.svg +1 -0
  524. package/icons/phosphor/storefront.svg +1 -0
  525. package/icons/phosphor/sun.svg +1 -0
  526. package/icons/phosphor/swatches.svg +1 -0
  527. package/icons/phosphor/tag.svg +1 -0
  528. package/icons/phosphor/text-aa.svg +1 -0
  529. package/icons/phosphor/text-align-left.svg +1 -0
  530. package/icons/phosphor/text-b.svg +1 -0
  531. package/icons/phosphor/text-h-one.svg +1 -0
  532. package/icons/phosphor/text-h-three.svg +1 -0
  533. package/icons/phosphor/text-h-two.svg +1 -0
  534. package/icons/phosphor/text-italic.svg +1 -0
  535. package/icons/phosphor/text-t.svg +1 -0
  536. package/icons/phosphor/ticket.svg +1 -0
  537. package/icons/phosphor/toggle-right.svg +1 -0
  538. package/icons/phosphor/train.svg +1 -0
  539. package/icons/phosphor/trash.svg +1 -0
  540. package/icons/phosphor/upload-simple.svg +1 -0
  541. package/icons/phosphor/user-circle.svg +1 -0
  542. package/icons/phosphor/user.svg +1 -0
  543. package/icons/phosphor/users-three.svg +1 -0
  544. package/icons/phosphor/users.svg +1 -0
  545. package/icons/phosphor/video-camera.svg +1 -0
  546. package/icons/phosphor/warning-circle.svg +1 -0
  547. package/icons/phosphor/warning.svg +1 -0
  548. package/icons/phosphor/wave-sine.svg +1 -0
  549. package/icons/phosphor/wheelchair.svg +1 -0
  550. package/icons/phosphor/wrench.svg +1 -0
  551. package/icons/phosphor/x.svg +1 -0
  552. package/lib/apps.d.mts +28 -0
  553. package/lib/apps.mjs +139 -0
  554. package/lib/blocks.d.mts +23 -0
  555. package/lib/blocks.mjs +90 -0
  556. package/lib/colors.ts +86 -0
  557. package/lib/contactBar.ts +18 -0
  558. package/lib/cookieCategories.d.mts +17 -0
  559. package/lib/cookieCategories.mjs +28 -0
  560. package/lib/crop.d.mts +34 -0
  561. package/lib/crop.mjs +83 -0
  562. package/lib/csp.d.mts +22 -0
  563. package/lib/csp.mjs +130 -0
  564. package/lib/cspSources.d.mts +24 -0
  565. package/lib/cspSources.mjs +60 -0
  566. package/lib/dynamic-colors.ts +426 -0
  567. package/lib/git.d.mts +24 -0
  568. package/lib/git.mjs +64 -0
  569. package/lib/hooks.ts +51 -0
  570. package/lib/inline.ts +20 -0
  571. package/lib/license-texts/Apache-2.0.txt +191 -0
  572. package/lib/license-texts/BSD-2-Clause.txt +24 -0
  573. package/lib/license-texts/BSD-3-Clause.txt +28 -0
  574. package/lib/license-texts/ISC.txt +15 -0
  575. package/lib/license-texts/MIT.txt +21 -0
  576. package/lib/licenses.d.mts +190 -0
  577. package/lib/licenses.mjs +683 -0
  578. package/lib/markdown.ts +42 -0
  579. package/lib/motion-schemes.d.mts +2 -0
  580. package/lib/motion-schemes.mjs +5 -0
  581. package/lib/motion.ts +131 -0
  582. package/lib/notice.d.mts +3 -0
  583. package/lib/notice.mjs +34 -0
  584. package/lib/scheduled.d.mts +15 -0
  585. package/lib/scheduled.mjs +37 -0
  586. package/lib/snackbar.ts +149 -0
  587. package/lib/status.d.mts +7 -0
  588. package/lib/status.mjs +18 -0
  589. package/lib/svg.d.mts +22 -0
  590. package/lib/svg.mjs +231 -0
  591. package/lib/text-colors.d.mts +11 -0
  592. package/lib/text-colors.mjs +43 -0
  593. package/lib/video-id.d.mts +14 -0
  594. package/lib/video-id.mjs +72 -0
  595. package/lib/video.d.mts +20 -0
  596. package/lib/video.mjs +91 -0
  597. package/package.json +131 -6
  598. package/pages/404.astro +10 -0
  599. package/pages/[...slug].astro +51 -0
  600. package/pages/_headers.txt +31 -0
  601. package/pages/blog/index.astro +10 -0
  602. package/pages/blog/kategori/[category].astro +18 -0
  603. package/pages/editor/code-images.json.js +25 -0
  604. package/pages/editor/index.astro +75 -0
  605. package/pages/editor/manifest.webmanifest.js +23 -0
  606. package/pages/editor/sw.js +33 -0
  607. package/pages/editor/sw.js.js +4 -0
  608. package/pages/editor/thumbnails.json.js +21 -0
  609. package/pages/editor/version.json.js +22 -0
  610. package/pages/headers.js +4 -0
  611. package/pages/index.astro +10 -0
  612. package/pages/ny.astro +6 -0
  613. package/pages/robots.txt.js +3 -0
  614. package/pages/rss.xml.js +20 -0
  615. package/scripts/app-screenshots.mjs +164 -0
  616. package/scripts/app-workers.mjs +6 -0
  617. package/scripts/bin.mjs +12 -0
  618. package/scripts/built-site.mjs +99 -0
  619. package/scripts/check-consent.mjs +113 -0
  620. package/scripts/check-content.mjs +149 -0
  621. package/scripts/check-demo.mjs +24 -0
  622. package/scripts/check-themes.mjs +45 -0
  623. package/scripts/check-worker.mjs +569 -0
  624. package/scripts/compare-html.mjs +81 -0
  625. package/scripts/demo-site.mjs +42 -0
  626. package/scripts/editor-icons.mjs +56 -0
  627. package/scripts/editor-roundtrip.mjs +134 -0
  628. package/scripts/editor-screens.mjs +101 -0
  629. package/scripts/git-env.mjs +9 -0
  630. package/scripts/icon-packs.mjs +151 -0
  631. package/scripts/link-counts.mjs +22 -0
  632. package/scripts/page-colors.mjs +42 -0
  633. package/scripts/releases.d.mts +10 -0
  634. package/scripts/releases.mjs +80 -0
  635. package/scripts/responsive-images.mjs +94 -0
  636. package/scripts/scheduled-list.mjs +15 -0
  637. package/scripts/theme-screenshots.mjs +88 -0
  638. package/scripts/types.mjs +21 -0
  639. package/scripts/video-thumbnails.mjs +89 -0
  640. package/scripts/worker-modules.mjs +34 -0
  641. package/site/appCookies.d.mts +9 -0
  642. package/site/appCookies.mjs +74 -0
  643. package/site/apps.ts +21 -0
  644. package/site/autolinks.d.mts +11 -0
  645. package/site/autolinks.mjs +200 -0
  646. package/site/blockScripts.ts +31 -0
  647. package/site/categories.ts +16 -0
  648. package/site/consent.ts +26 -0
  649. package/site/content.config.ts +84 -0
  650. package/site/content.ts +104 -0
  651. package/site/cookieList.ts +17 -0
  652. package/site/defineSite.d.mts +142 -0
  653. package/site/defineSite.mjs +136 -0
  654. package/site/directives/interact.d.mts +2 -0
  655. package/site/directives/interact.mjs +31 -0
  656. package/site/editor-version.d.mts +1 -0
  657. package/site/editor-version.mjs +26 -0
  658. package/site/fill.ts +13 -0
  659. package/site/format.ts +11 -0
  660. package/site/home.ts +66 -0
  661. package/site/iconPacks.d.mts +20 -0
  662. package/site/iconPacks.mjs +43 -0
  663. package/site/icons.d.mts +693 -0
  664. package/site/icons.mjs +605 -0
  665. package/site/images.d.mts +43 -0
  666. package/site/images.mjs +157 -0
  667. package/site/motion.ts +13 -0
  668. package/site/noindex.d.mts +6 -0
  669. package/site/noindex.mjs +21 -0
  670. package/site/pageColors.ts +110 -0
  671. package/site/popups.ts +98 -0
  672. package/site/schema.ts +189 -0
  673. package/site/sections.ts +3 -0
  674. package/site/seo.ts +28 -0
  675. package/site/site.ts +76 -0
  676. package/site/siteConfig.d.mts +1 -0
  677. package/site/siteConfig.mjs +8 -0
  678. package/site/siteCsp.d.mts +10 -0
  679. package/site/siteCsp.mjs +75 -0
  680. package/site/siteModules.d.mts +7 -0
  681. package/site/siteModules.mjs +27 -0
  682. package/site/standardPages.ts +59 -0
  683. package/site/testimonials.ts +48 -0
  684. package/site/text.ts +43 -0
  685. package/site/theme.d.mts +83 -0
  686. package/site/theme.mjs +472 -0
  687. package/site/tracking.client.ts +225 -0
  688. package/site/tracking.ts +9 -0
  689. package/src/ActionParts.tsx +205 -0
  690. package/src/Advanced.tsx +152 -0
  691. package/src/App.tsx +904 -0
  692. package/src/AppList.tsx +1345 -0
  693. package/src/BlockDialog.tsx +360 -0
  694. package/src/BlockParts.tsx +652 -0
  695. package/src/BlockSettings.tsx +533 -0
  696. package/src/BrandForm.tsx +164 -0
  697. package/src/Colors.tsx +476 -0
  698. package/src/CommunicationParts.tsx +199 -0
  699. package/src/CompanyForm.tsx +209 -0
  700. package/src/Consents.tsx +175 -0
  701. package/src/ContainmentParts.tsx +185 -0
  702. package/src/Crop.tsx +152 -0
  703. package/src/DataForm.tsx +434 -0
  704. package/src/Deleted.tsx +177 -0
  705. package/src/EntryForm.tsx +740 -0
  706. package/src/FileView.tsx +30 -0
  707. package/src/FormFrame.tsx +237 -0
  708. package/src/Keywords.tsx +225 -0
  709. package/src/Library.tsx +1563 -0
  710. package/src/Licenses.tsx +336 -0
  711. package/src/LinkSheet.tsx +138 -0
  712. package/src/MarkdownEditor.tsx +1075 -0
  713. package/src/Media.tsx +677 -0
  714. package/src/NavigationParts.tsx +350 -0
  715. package/src/NavigationRestParts.tsx +247 -0
  716. package/src/Notifications.tsx +59 -0
  717. package/src/PagePicker.tsx +136 -0
  718. package/src/Parts.tsx +991 -0
  719. package/src/PartsShown.tsx +168 -0
  720. package/src/PickerParts.tsx +183 -0
  721. package/src/Redirects.tsx +450 -0
  722. package/src/Screen.tsx +184 -0
  723. package/src/SectionsField.tsx +65 -0
  724. package/src/SelectionParts.tsx +134 -0
  725. package/src/SeoScores.tsx +184 -0
  726. package/src/Sheet.tsx +35 -0
  727. package/src/ThemeStore.tsx +376 -0
  728. package/src/Tokens.tsx +346 -0
  729. package/src/Versions.tsx +214 -0
  730. package/src/addresses.ts +75 -0
  731. package/src/allIcons.ts +7 -0
  732. package/src/appFiles.ts +29 -0
  733. package/src/apps.ts +281 -0
  734. package/src/ask.tsx +123 -0
  735. package/src/blocks.ts +251 -0
  736. package/src/brand.ts +153 -0
  737. package/src/cache.ts +42 -0
  738. package/src/check.ts +285 -0
  739. package/src/checkSite.ts +256 -0
  740. package/src/consents.ts +26 -0
  741. package/src/cookieList.ts +70 -0
  742. package/src/countries.ts +59 -0
  743. package/src/deploy.ts +155 -0
  744. package/src/editor.css +602 -0
  745. package/src/examples.ts +85 -0
  746. package/src/fields.tsx +942 -0
  747. package/src/frontmatter.d.mts +6 -0
  748. package/src/frontmatter.mjs +42 -0
  749. package/src/github.ts +376 -0
  750. package/src/i18n/da-DK/advanced.ts +28 -0
  751. package/src/i18n/da-DK/app.ts +71 -0
  752. package/src/i18n/da-DK/apps.ts +90 -0
  753. package/src/i18n/da-DK/blockSettings.ts +38 -0
  754. package/src/i18n/da-DK/blocks.ts +408 -0
  755. package/src/i18n/da-DK/check.ts +25 -0
  756. package/src/i18n/da-DK/colors.ts +64 -0
  757. package/src/i18n/da-DK/common.ts +118 -0
  758. package/src/i18n/da-DK/company.ts +24 -0
  759. package/src/i18n/da-DK/consents.ts +23 -0
  760. package/src/i18n/da-DK/forms.ts +116 -0
  761. package/src/i18n/da-DK/history.ts +35 -0
  762. package/src/i18n/da-DK/index.ts +49 -0
  763. package/src/i18n/da-DK/keywords.ts +19 -0
  764. package/src/i18n/da-DK/library.ts +135 -0
  765. package/src/i18n/da-DK/licenses.ts +25 -0
  766. package/src/i18n/da-DK/media.ts +79 -0
  767. package/src/i18n/da-DK/parts.ts +696 -0
  768. package/src/i18n/da-DK/preview.ts +13 -0
  769. package/src/i18n/da-DK/redirects.ts +51 -0
  770. package/src/i18n/da-DK/seo.ts +20 -0
  771. package/src/i18n/da-DK/text.ts +35 -0
  772. package/src/i18n/da-DK/theme.ts +41 -0
  773. package/src/i18n/en-US/advanced.ts +32 -0
  774. package/src/i18n/en-US/app.ts +84 -0
  775. package/src/i18n/en-US/apps.ts +134 -0
  776. package/src/i18n/en-US/blockSettings.ts +48 -0
  777. package/src/i18n/en-US/blocks.ts +441 -0
  778. package/src/i18n/en-US/check.ts +31 -0
  779. package/src/i18n/en-US/colors.ts +82 -0
  780. package/src/i18n/en-US/common.ts +144 -0
  781. package/src/i18n/en-US/company.ts +25 -0
  782. package/src/i18n/en-US/consents.ts +25 -0
  783. package/src/i18n/en-US/forms.ts +145 -0
  784. package/src/i18n/en-US/history.ts +43 -0
  785. package/src/i18n/en-US/index.ts +51 -0
  786. package/src/i18n/en-US/keywords.ts +24 -0
  787. package/src/i18n/en-US/library.ts +176 -0
  788. package/src/i18n/en-US/licenses.ts +36 -0
  789. package/src/i18n/en-US/media.ts +80 -0
  790. package/src/i18n/en-US/parts.ts +718 -0
  791. package/src/i18n/en-US/preview.ts +15 -0
  792. package/src/i18n/en-US/redirects.ts +66 -0
  793. package/src/i18n/en-US/seo.ts +26 -0
  794. package/src/i18n/en-US/text.ts +41 -0
  795. package/src/i18n/en-US/theme.ts +41 -0
  796. package/src/i18n/index.ts +124 -0
  797. package/src/icon.tsx +160 -0
  798. package/src/iconPacks.ts +89 -0
  799. package/src/images.ts +163 -0
  800. package/src/licenses.ts +79 -0
  801. package/src/markdown.d.mts +12 -0
  802. package/src/markdown.mjs +204 -0
  803. package/src/nav.ts +68 -0
  804. package/src/notFound.ts +28 -0
  805. package/src/notifications.ts +223 -0
  806. package/src/pagePreview.tsx +204 -0
  807. package/src/r2Caching.ts +55 -0
  808. package/src/redirects.ts +149 -0
  809. package/src/registry.tsx +188 -0
  810. package/src/schema.ts +417 -0
  811. package/src/sections.d.mts +45 -0
  812. package/src/sections.mjs +303 -0
  813. package/src/seo.ts +333 -0
  814. package/src/shared.ts +44 -0
  815. package/src/sidebar.ts +31 -0
  816. package/src/site.ts +317 -0
  817. package/src/standard/Block.astro +19 -0
  818. package/src/standard/BlockSite.astro +18 -0
  819. package/src/standard/accordion/index.tsx +47 -0
  820. package/src/standard/accordion/load.ts +23 -0
  821. package/src/standard/accordion/shared.ts +15 -0
  822. package/src/standard/accordion/view.ts +32 -0
  823. package/src/standard/buttons/fields.ts +98 -0
  824. package/src/standard/buttons/index.tsx +23 -0
  825. package/src/standard/buttons/load.ts +34 -0
  826. package/src/standard/buttons/shared.ts +12 -0
  827. package/src/standard/buttons/view.ts +30 -0
  828. package/src/standard/cards/index.tsx +71 -0
  829. package/src/standard/cards/load.ts +17 -0
  830. package/src/standard/cards/shared.ts +27 -0
  831. package/src/standard/cards/view.ts +29 -0
  832. package/src/standard/checklist/index.tsx +27 -0
  833. package/src/standard/checklist/load.ts +5 -0
  834. package/src/standard/checklist/view.ts +13 -0
  835. package/src/standard/cookies/index.tsx +17 -0
  836. package/src/standard/cookies/list.ts +56 -0
  837. package/src/standard/cookies/load.ts +11 -0
  838. package/src/standard/cookies/shared.ts +20 -0
  839. package/src/standard/cookies/view.ts +28 -0
  840. package/src/standard/faq/index.tsx +29 -0
  841. package/src/standard/faq/load.ts +14 -0
  842. package/src/standard/faq/view.ts +21 -0
  843. package/src/standard/head.ts +23 -0
  844. package/src/standard/hero/fields.ts +48 -0
  845. package/src/standard/hero/index.tsx +57 -0
  846. package/src/standard/hero/load.ts +23 -0
  847. package/src/standard/hero/view.ts +38 -0
  848. package/src/standard/links/index.tsx +41 -0
  849. package/src/standard/links/load.ts +17 -0
  850. package/src/standard/links/view.ts +25 -0
  851. package/src/standard/list/index.tsx +50 -0
  852. package/src/standard/list/load.ts +5 -0
  853. package/src/standard/list/view.ts +18 -0
  854. package/src/standard/load.ts +7 -0
  855. package/src/standard/notice/index.tsx +30 -0
  856. package/src/standard/notice/load.ts +16 -0
  857. package/src/standard/notice/view.ts +10 -0
  858. package/src/standard/points/index.tsx +42 -0
  859. package/src/standard/points/load.ts +5 -0
  860. package/src/standard/points/view.ts +12 -0
  861. package/src/standard/posts/index.tsx +132 -0
  862. package/src/standard/posts/load.ts +36 -0
  863. package/src/standard/posts/shared.ts +21 -0
  864. package/src/standard/posts/view.ts +63 -0
  865. package/src/standard/pricecards/index.tsx +57 -0
  866. package/src/standard/pricecards/load.ts +11 -0
  867. package/src/standard/pricecards/view.ts +50 -0
  868. package/src/standard/sharedTexts.ts +6 -0
  869. package/src/standard/site.ts +124 -0
  870. package/src/standard/siteLooks.ts +19 -0
  871. package/src/standard/sites.ts +13 -0
  872. package/src/standard/steps/index.tsx +28 -0
  873. package/src/standard/steps/load.ts +5 -0
  874. package/src/standard/steps/view.ts +11 -0
  875. package/src/standard/testimonials/index.tsx +87 -0
  876. package/src/standard/testimonials/list.ts +43 -0
  877. package/src/standard/testimonials/load.ts +58 -0
  878. package/src/standard/testimonials/shared.ts +13 -0
  879. package/src/standard/testimonials/view.ts +76 -0
  880. package/src/standard/themeBlocks.ts +13 -0
  881. package/src/standard/video/index.tsx +23 -0
  882. package/src/standard/video/load.ts +10 -0
  883. package/src/standard/video/view.ts +42 -0
  884. package/src/standard/view.tsx +105 -0
  885. package/src/standard/views.ts +37 -0
  886. package/src/store.ts +499 -0
  887. package/src/theme.ts +90 -0
  888. package/src/themeUi.ts +97 -0
  889. package/src/themes.ts +91 -0
  890. package/src/ui.tsx +3402 -0
  891. package/src/virtual.d.ts +5 -0
  892. package/src/when.ts +22 -0
  893. package/starter/.githooks/pre-push +4 -0
  894. package/starter/.nvmrc +1 -0
  895. package/starter/.prettierignore +11 -0
  896. package/starter/.prettierrc.json +7 -0
  897. package/starter/README.md +26 -0
  898. package/starter/astro.config.mjs +5 -0
  899. package/starter/package.json +67 -0
  900. package/starter/public/_redirects +1 -0
  901. package/starter/public/apple-touch-icon.png +0 -0
  902. package/starter/public/editor/apple-touch-icon.png +0 -0
  903. package/starter/public/editor/icon-192.png +0 -0
  904. package/starter/public/editor/icon-512.png +0 -0
  905. package/starter/public/editor/icon-maskable-512.png +0 -0
  906. package/starter/public/favicon.ico +0 -0
  907. package/starter/public/favicon.svg +1 -0
  908. package/starter/public/logo.svg +1 -0
  909. package/starter/src/content/pages/blog.md +8 -0
  910. package/starter/src/content/pages/forside.md +9 -0
  911. package/starter/src/content/pages/kontakt.md +7 -0
  912. package/starter/src/content/pages/privatlivspolitik.md +9 -0
  913. package/starter/src/content/pages/siden-findes-ikke.md +7 -0
  914. package/starter/src/content.config.ts +2 -0
  915. package/starter/src/data/apps.json +1 -0
  916. package/starter/src/data/block-settings.json +3 -0
  917. package/starter/src/data/blocks.json +1 -0
  918. package/starter/src/data/booking.json +6 -0
  919. package/starter/src/data/colors.json +70 -0
  920. package/starter/src/data/contact.json +8 -0
  921. package/starter/src/data/cookies.json +21 -0
  922. package/starter/src/data/csp.json +1 -0
  923. package/starter/src/data/footer.json +15 -0
  924. package/starter/src/data/internal-links.json +4 -0
  925. package/starter/src/data/menu.json +4 -0
  926. package/starter/src/data/mobile-menu.json +4 -0
  927. package/starter/src/data/old-addresses.json +1 -0
  928. package/starter/src/data/redirects.json +1 -0
  929. package/starter/src/data/standard-pages.json +8 -0
  930. package/starter/src/data/theme.json +1 -0
  931. package/starter/src/env.d.ts +2 -0
  932. package/starter/src/site/config.ts +13 -0
  933. package/starter/src/site/worker.js +14 -0
  934. package/starter/tsconfig.json +17 -0
  935. package/starter/worker/apps.js +6 -0
  936. package/starter/wrangler.jsonc +39 -0
  937. package/theme/LICENSE +21 -0
  938. package/theme/blocks/ArrowLink.tsx +14 -0
  939. package/theme/blocks/BlockHead.tsx +26 -0
  940. package/theme/blocks/Panel.tsx +32 -0
  941. package/theme/blocks/Person.tsx +26 -0
  942. package/theme/blocks/accordion/PageRow.tsx +25 -0
  943. package/theme/blocks/accordion/View.tsx +17 -0
  944. package/theme/blocks/buttons/Buttons.tsx +28 -0
  945. package/theme/blocks/buttons/View.tsx +12 -0
  946. package/theme/blocks/cards/LinkCards.tsx +72 -0
  947. package/theme/blocks/cards/View.tsx +20 -0
  948. package/theme/blocks/checklist/Checks.tsx +15 -0
  949. package/theme/blocks/checklist/View.tsx +14 -0
  950. package/theme/blocks/cookies/View.tsx +36 -0
  951. package/theme/blocks/faq/Accordion.tsx +34 -0
  952. package/theme/blocks/faq/View.tsx +16 -0
  953. package/theme/blocks/faq/script.ts +56 -0
  954. package/theme/blocks/hero/View.tsx +44 -0
  955. package/theme/blocks/links/LinkGroups.tsx +24 -0
  956. package/theme/blocks/links/View.tsx +17 -0
  957. package/theme/blocks/list/Rows.tsx +24 -0
  958. package/theme/blocks/list/View.tsx +14 -0
  959. package/theme/blocks/notice/View.tsx +13 -0
  960. package/theme/blocks/points/PointList.tsx +34 -0
  961. package/theme/blocks/points/View.tsx +14 -0
  962. package/theme/blocks/posts/View.tsx +26 -0
  963. package/theme/blocks/pricecards/View.tsx +40 -0
  964. package/theme/blocks/steps/StepList.tsx +23 -0
  965. package/theme/blocks/steps/View.tsx +14 -0
  966. package/theme/blocks/testimonials/List.tsx +46 -0
  967. package/theme/blocks/testimonials/Slider.tsx +44 -0
  968. package/theme/blocks/testimonials/Testimonial.tsx +124 -0
  969. package/theme/blocks/testimonials/View.tsx +21 -0
  970. package/theme/blocks/testimonials/script.ts +52 -0
  971. package/theme/blocks/video/View.tsx +41 -0
  972. package/theme/components/ActionBar.astro +63 -0
  973. package/theme/components/AppSlot.astro +21 -0
  974. package/theme/components/ArrowLink.tsx +21 -0
  975. package/theme/components/BlogIndex.astro +48 -0
  976. package/theme/components/BlogPosts.astro +18 -0
  977. package/theme/components/BlogPosts.tsx +45 -0
  978. package/theme/components/BookButton.tsx +22 -0
  979. package/theme/components/Container.astro +11 -0
  980. package/theme/components/CookieConsent.astro +121 -0
  981. package/theme/components/EndBlock.astro +21 -0
  982. package/theme/components/FontPreload.astro +6 -0
  983. package/theme/components/Footer.astro +85 -0
  984. package/theme/components/Header.astro +186 -0
  985. package/theme/components/Icon.astro +16 -0
  986. package/theme/components/Img.astro +29 -0
  987. package/theme/components/Img.tsx +33 -0
  988. package/theme/components/Inline.astro +11 -0
  989. package/theme/components/Logo.astro +23 -0
  990. package/theme/components/LogoMark.astro +10 -0
  991. package/theme/components/Markdown.astro +17 -0
  992. package/theme/components/MobileMenu.tsx +224 -0
  993. package/theme/components/MobileNav.astro +33 -0
  994. package/theme/components/PageHeader.astro +22 -0
  995. package/theme/components/PageSection.astro +24 -0
  996. package/theme/components/PhoneButton.tsx +11 -0
  997. package/theme/components/Popup.astro +145 -0
  998. package/theme/components/PopupButton.tsx +24 -0
  999. package/theme/components/PopupView.tsx +48 -0
  1000. package/theme/components/PostList.tsx +58 -0
  1001. package/theme/components/PostMeta.tsx +17 -0
  1002. package/theme/components/RichText.astro +28 -0
  1003. package/theme/components/Section.astro +29 -0
  1004. package/theme/components/SectionHeading.tsx +8 -0
  1005. package/theme/components/TextSection.astro +16 -0
  1006. package/theme/components/home/PageBlock.astro +19 -0
  1007. package/theme/components/home/Text.astro +13 -0
  1008. package/theme/components/loadPopup.ts +22 -0
  1009. package/theme/components/loadPosts.ts +16 -0
  1010. package/theme/components/posts.ts +15 -0
  1011. package/theme/editor/looks.mjs +7 -0
  1012. package/theme/editor/previews.tsx +56 -0
  1013. package/theme/layouts/Base.astro +273 -0
  1014. package/theme/layouts/PageLayout.astro +126 -0
  1015. package/theme/layouts/PostLayout.astro +137 -0
  1016. package/theme/screenshots/1-front.jpg +0 -0
  1017. package/theme/screenshots/2-front-dark.jpg +0 -0
  1018. package/theme/screenshots/3-blog.jpg +0 -0
  1019. package/theme/screenshots/4-contact.jpg +0 -0
  1020. package/theme/scripts/motion.ts +29 -0
  1021. package/theme/styles/base.css +320 -0
  1022. package/theme/styles/blocks.css +1808 -0
  1023. package/theme/styles/components.css +206 -0
  1024. package/theme/styles/global.css +1302 -0
  1025. package/theme/styles/ui.css +6544 -0
  1026. package/theme/templates/Blog.astro +38 -0
  1027. package/theme/templates/Category.astro +19 -0
  1028. package/theme/templates/Home.astro +36 -0
  1029. package/theme/templates/New.astro +68 -0
  1030. package/theme/templates/NotFound.astro +117 -0
  1031. package/theme/theme.json +28 -0
  1032. package/theme/ui/Badge.tsx +45 -0
  1033. package/theme/ui/BottomSheet.tsx +148 -0
  1034. package/theme/ui/Button.tsx +79 -0
  1035. package/theme/ui/ButtonGroup.tsx +31 -0
  1036. package/theme/ui/Card.tsx +45 -0
  1037. package/theme/ui/Carousel.tsx +124 -0
  1038. package/theme/ui/Checkbox.tsx +59 -0
  1039. package/theme/ui/Chip.tsx +101 -0
  1040. package/theme/ui/ChipSet.tsx +27 -0
  1041. package/theme/ui/DatePicker.tsx +149 -0
  1042. package/theme/ui/DatePickerContainer.tsx +316 -0
  1043. package/theme/ui/Dialog.tsx +100 -0
  1044. package/theme/ui/Divider.tsx +27 -0
  1045. package/theme/ui/ExposedDropdownMenu.tsx +77 -0
  1046. package/theme/ui/Fab.tsx +88 -0
  1047. package/theme/ui/FabMenu.tsx +67 -0
  1048. package/theme/ui/IconButton.tsx +78 -0
  1049. package/theme/ui/List.tsx +287 -0
  1050. package/theme/ui/LoadingIndicator.tsx +29 -0
  1051. package/theme/ui/Menu.tsx +278 -0
  1052. package/theme/ui/NavigationBar.tsx +62 -0
  1053. package/theme/ui/NavigationDrawer.tsx +80 -0
  1054. package/theme/ui/NavigationRail.tsx +154 -0
  1055. package/theme/ui/ProgressIndicator.tsx +162 -0
  1056. package/theme/ui/RadioButton.tsx +50 -0
  1057. package/theme/ui/Search.tsx +129 -0
  1058. package/theme/ui/SegmentedButton.tsx +49 -0
  1059. package/theme/ui/SideSheet.tsx +54 -0
  1060. package/theme/ui/Slider.tsx +155 -0
  1061. package/theme/ui/Snackbar.tsx +67 -0
  1062. package/theme/ui/SplitButton.tsx +67 -0
  1063. package/theme/ui/Switch.tsx +60 -0
  1064. package/theme/ui/Tabs.tsx +114 -0
  1065. package/theme/ui/TextField.tsx +278 -0
  1066. package/theme/ui/TimePicker.tsx +128 -0
  1067. package/theme/ui/TimePickerContainer.tsx +234 -0
  1068. package/theme/ui/Toolbar.tsx +67 -0
  1069. package/theme/ui/Tooltip.tsx +58 -0
  1070. package/theme/ui/TopAppBar.tsx +77 -0
  1071. package/theme/ui/bottomSheet.script.ts +8 -0
  1072. package/theme/ui/bottomSheet.ts +211 -0
  1073. package/theme/ui/carousel.script.ts +28 -0
  1074. package/theme/ui/carouselLayout.ts +229 -0
  1075. package/theme/ui/checkbox.script.ts +8 -0
  1076. package/theme/ui/datePicker.script.ts +302 -0
  1077. package/theme/ui/dates.ts +185 -0
  1078. package/theme/ui/dialog.ts +48 -0
  1079. package/theme/ui/dropdownMenu.script.ts +166 -0
  1080. package/theme/ui/fabMenu.script.ts +57 -0
  1081. package/theme/ui/navigationRail.script.ts +47 -0
  1082. package/theme/ui/ripple.script.ts +159 -0
  1083. package/theme/ui/search.script.ts +92 -0
  1084. package/theme/ui/sideSheet.ts +48 -0
  1085. package/theme/ui/slider.script.ts +12 -0
  1086. package/theme/ui/sliderUpdate.ts +52 -0
  1087. package/theme/ui/snackbar.script.ts +16 -0
  1088. package/theme/ui/splitButton.script.ts +63 -0
  1089. package/theme/ui/tabs.script.ts +48 -0
  1090. package/theme/ui/textField.script.ts +17 -0
  1091. package/theme/ui/textFieldUpdate.ts +44 -0
  1092. package/theme/ui/timePicker.script.ts +248 -0
  1093. package/theme/ui/toolbar.script.ts +43 -0
  1094. package/theme/ui/tooltip.script.ts +68 -0
  1095. package/worker/consent.js +141 -0
  1096. package/worker/editor-github.js +167 -0
  1097. package/worker/editor-host.js +74 -0
  1098. package/worker/files.js +401 -0
  1099. package/worker/github.js +36 -0
  1100. package/worker/index.js +184 -0
  1101. package/worker/not-found.js +97 -0
  1102. package/worker/oauth.js +95 -0
  1103. package/worker/publish.js +52 -0
  1104. package/worker/push.js +270 -0
  1105. package/worker/r2-status.js +50 -0
  1106. package/worker/session.js +58 -0
package/src/App.tsx ADDED
@@ -0,0 +1,904 @@
1
+ // The editor at /editor/ (docs/editor.md): sign in with GitHub, pick a part of the site, edit and
2
+ // save. The address after # says what is open, so reloading keeps it:
3
+ // (empty) the start page
4
+ // liste/<id> the list of a part, e.g. liste/priser, and liste/<id>/<app> a tab an installed app adds to it
5
+ // (AppDefinition.tabs): liste/cookies/cookiescanner
6
+ // header what is in the header on every page (the menu)
7
+ // lister the short lists (the testimonials, the apps' lists)
8
+ // indstillinger the settings of the whole site (Library.tsx Settings), opened by the gear in the top bar:
9
+ // indstillinger/appstore the app store, its apps, and indstillinger/appstore/temaer its themes (AppList.tsx StorePage)
10
+ // ny/<id> a new entry, ny/<id>/<kind> one of a kind (a pop-up: standard, or an app's)
11
+ // virksomheden the business, and virksomheden/<part> its parts: kontakt, betaling; and
12
+ // virksomheden/betaling/<part> its own parts: mobilepay, bank
13
+ // <screen> a screen with parts of the site's own (partScreens), and <screen>/<part> its parts:
14
+ // privatliv/cookiebanner, privatliv/privatlivssider, or one of the editor's own screens (Part.opens):
15
+ // privatliv/samtykker the visitors' answers in the cookie banner (Consents.tsx); and the settings of the apps installed with
16
+ // its category: maaling/google-analytics, promovering/calendly
17
+ // filer the uploaded pictures, PDFs, videos and zip files (Media.tsx)
18
+ // seo "Søgemaskineoptimering" (Library.tsx Seo): seo/videresendelser the redirects' two lists
19
+ // (Redirects.tsx): seo/videresendelser/alle the redirects, and seo/videresendelser/ikke-fundet
20
+ // the addresses visitors came to that do not exist; seo/vurdering the SEO rating of every page and
21
+ // post (SeoScores.tsx); and seo/<part> the site's own (seoParts)
22
+ // udseende how the site looks (Library.tsx Theme): udseende/temaer the themes installed (Themes) and udseende/temaer/<theme>
23
+ // a theme's page, with the button that swaps to it (ThemePage), udseende/ikonpakker the icon packs (IconPacks),
24
+ // udseende/bevaegelse how it moves (MotionSchemes),
25
+ // udseende/farver the colours (Colors.tsx;
26
+ // udseende/farver/reservefarver the site's own while an app changes the pages'), udseende/logo the logo
27
+ // and icon (BrandForm.tsx), udseende/dele the parts (Library.tsx PartsHome): udseende/dele/editor the editor's, every
28
+ // one in every state (Parts.tsx), udseende/dele/ui the theme's UI components, udseende/dele/blokke the
29
+ // blocks, the standard ones and the apps', and udseende/dele/liste/<list> each kind of a list of kinds
30
+ // ("Promovering"), its own and the apps' (BlockParts.tsx), and udseende/dele/vaerdier the
31
+ // theme's design tokens, "Værdier" (Tokens.tsx)
32
+ // blokvalg the blocks that can be put in, "Blokindstillinger" (BlockSettings.tsx)
33
+ // avanceret "Avanceret", under "Indstillinger": the copy of the site's files in R2 (Advanced.tsx)
34
+ // licenser "Licenser", under "Indstillinger": the licences of what the site and the editor are made with, and
35
+ // licenser/<id> one with its text (Licenses.tsx)
36
+ // apps the apps that come with the site, apps/<app> one with its button to install it and, while it
37
+ // is installed, a row to its settings: on the screen with its category (maaling/<app>), else
38
+ // apps/<app>/indstillinger (AppList.tsx settingsRoute)
39
+ // <form>/<field> the page of a field of a part's form with its choices on a page of their own (Field.page), over the
40
+ // form, which keeps what is in it (formAt): apps/content-colors/indstillinger/variant
41
+ // blokke the blocks, and blokke/<block> the texts all its copies have (blockTexts.ts)
42
+ // slettet/<id> what was deleted from a list, or slettet/filer the files (Deleted.tsx)
43
+ // <file path> a file being edited, e.g. src/content/blog/mit-foerste-indlaeg.md
44
+ import { useEffect, useMemo, useRef, useState } from 'react';
45
+ import {
46
+ AuthError,
47
+ commit,
48
+ deletedFiles,
49
+ loadBytes,
50
+ loadFiles,
51
+ head,
52
+ loadFolders,
53
+ login,
54
+ logout,
55
+ rememberTexts,
56
+ savedAt,
57
+ versionsOf,
58
+ versionText,
59
+ viewer,
60
+ type Change,
61
+ type DeletedFile,
62
+ type Version,
63
+ type RepoFile,
64
+ type Viewer,
65
+ } from './github';
66
+ import {
67
+ changesText,
68
+ hasUnsaved,
69
+ kindAndName,
70
+ lower,
71
+ nameOf,
72
+ savedText,
73
+ setUnsaved,
74
+ keepCurrent,
75
+ registerWorker,
76
+ waitForSite,
77
+ } from './deploy';
78
+ import { readCache, writeCache } from './cache';
79
+ import { forgetNotFound, loadNotFound, type Missing } from './notFound';
80
+ import { collectionOf, slugOf, type Collection, type FolderCollection, type Part, type Relation } from './schema';
81
+ import EntryForm, { newMarkdown } from './EntryForm';
82
+ import DataForm from './DataForm';
83
+ import CompanyForm from './CompanyForm';
84
+ import Media, { mediaOf } from './Media';
85
+ import Redirects, { REDIRECTS_ROUTE, TAB_ROUTES } from './Redirects';
86
+ import Consents from './Consents';
87
+ import SeoScores, { SEO_SCORES_ROUTE } from './SeoScores';
88
+ import Deleted from './Deleted';
89
+ import Parts from './Parts';
90
+ import { BlocksParts, ComponentParts, KindParts } from './BlockParts';
91
+ import Tokens from './Tokens';
92
+ import BrandForm from './BrandForm';
93
+ import BlockSettings from './BlockSettings';
94
+ import Advanced, { ADVANCED_ROUTE } from './Advanced';
95
+ import Licenses, { LICENSES_ROUTE, LicensePage } from './Licenses';
96
+ import { StoreThemePage } from './ThemeStore';
97
+ import {
98
+ AppPage,
99
+ AppScreenPage,
100
+ Apps,
101
+ appPartsIn,
102
+ autoUpdate,
103
+ DeveloperPage,
104
+ appSettingsPart,
105
+ settingsRoute,
106
+ StoreAppPage,
107
+ StorePage,
108
+ } from './AppList';
109
+ import { installedApps } from './apps';
110
+ import {
111
+ DEVELOPER_ROUTE,
112
+ readApp,
113
+ readCatalog,
114
+ readScreenshots,
115
+ readThemes,
116
+ readThemeScreenshots,
117
+ STORE_BEFORE,
118
+ STORE_ROUTE,
119
+ STORE_THEMES,
120
+ type ShownApp,
121
+ type ShownTheme,
122
+ type StoreApp,
123
+ type StoreFile,
124
+ type StoreTheme,
125
+ } from './store';
126
+ import Colors, { colorsOf, FALLBACK_ROUTE } from './Colors';
127
+ import { applyColors, applyMotion } from './theme';
128
+ import { apps, notOffered } from './registry';
129
+ import {
130
+ Account,
131
+ Blocks,
132
+ Company,
133
+ Header,
134
+ List,
135
+ Lists,
136
+ PartsHome,
137
+ partsIn,
138
+ PartsScreen,
139
+ Seo,
140
+ Settings,
141
+ Start,
142
+ Theme,
143
+ ThemePage,
144
+ Themes,
145
+ IconPacks,
146
+ MotionSchemes,
147
+ motionOf,
148
+ } from './Library';
149
+ import {
150
+ appearance,
151
+ asWritten,
152
+ blockParts,
153
+ collections,
154
+ companyParts,
155
+ groups,
156
+ icons,
157
+ listRowsOf,
158
+ lists,
159
+ partScreens,
160
+ seoParts,
161
+ } from './site';
162
+ import { config, header, relationsOf, type SiteData, siteName, siteOf, testimonialsOf, usedBy, whatIsAt } from '@site/editor';
163
+ import { themes } from './themes';
164
+ import Screen from './Screen';
165
+ import { Button, EmptyNote, NavigationRail, NavigationRailItem, NavigationRailSection, ProblemNote, startRipples } from './ui';
166
+ import { setSidebar, useSidebar } from './sidebar';
167
+ import { Ask, ask } from './ask';
168
+ import { LinkAsk } from './LinkSheet';
169
+ import { type ListRows, type Option, type TestimonialRow } from './fields';
170
+ import { endOf, entriesOf, lookup, readAll } from './redirects';
171
+ import { inForm, leaveTo, type Status } from './FormFrame';
172
+ import { arrived, screenOf, up } from './nav';
173
+ import { siteT, siteText, t } from './i18n';
174
+ import { snackbar } from '../lib/snackbar';
175
+ import { appType, clearNotifications, isOn, remember, signOff, stopServerWait, syncDevice, told, waitOnServer } from './notifications';
176
+ import Notifications from './Notifications';
177
+ import { EditorOwn, InEditor } from './standard/view';
178
+ import './editor.css';
179
+
180
+ // What the forms and lists need from the app.
181
+ export type Env = {
182
+ relations: Record<Relation, Option[]>;
183
+ /** The rows of the lists (the apps' and the standard blocks'), by the list's id, and the testimonials, in their
184
+ * order, for the cards of the blocks. */
185
+ lists: ListRows;
186
+ testimonials: TestimonialRow[];
187
+ /** The rest of the site, for the previews of the blocks (sitedata.ts). */
188
+ site: SiteData;
189
+ /** The pictures uploaded to the site (Media.tsx), for the gallery a picture is chosen from. */
190
+ media: RepoFile[];
191
+ /** All the files the editor has, for what links to a page that moves (addresses.ts). */
192
+ files: RepoFile[];
193
+ /** Shows a message at the bottom of the screen; null hides it. */
194
+ notify: (status: Status) => void;
195
+ /** Saves files as one commit (github.ts) and later tells when the site has the change. `change`: what it changed, in
196
+ * the editor's language (t.app.changes, deploy.ts), for that message. Returns the new blob ids. */
197
+ commit: (message: string, changes: Change[], change: string) => Promise<Record<string, string>>;
198
+ /** When some files were last saved (github.ts); a new file is left out. */
199
+ savedAt: (paths: string[]) => Promise<Record<string, Date>>;
200
+ /** The commits that changed a file, and its text in one of them (github.ts; Versions.tsx). */
201
+ versions: (path: string, after?: string) => Promise<{ versions: Version[]; more: string | null }>;
202
+ versionText: (version: Version, path: string) => Promise<string | null>;
203
+ /** The files deleted from a folder (github.ts deletedFiles; Deleted.tsx). */
204
+ deleted: (folder: string, options?: { text?: boolean }) => Promise<DeletedFile[]>;
205
+ /** Why a new page or post cannot have this address (it is taken), or null; redirects: the files of
206
+ * the redirects as they are now (redirects.ts, loaded with `load`). */
207
+ taken: (address: string, redirects: RepoFile[]) => Taken | null;
208
+ /** Single files outside the folders the app loads (github.ts loadFiles). */
209
+ load: (paths: string[]) => Promise<RepoFile[]>;
210
+ /** All the files in a folder of the site, at any depth (an app's, to install, update or uninstall it). */
211
+ folder: (path: string) => Promise<RepoFile[]>;
212
+ /** The app store (store.ts): its apps and its themes, an app's or a theme's files from it, and their pictures (as
213
+ * addresses in this browser). */
214
+ store: {
215
+ catalog: () => Promise<ShownApp[]>;
216
+ themes: () => Promise<ShownTheme[]>;
217
+ files: (of: {
218
+ path: string;
219
+ ref: string;
220
+ files: StoreFile[];
221
+ label?: string;
222
+ name?: string;
223
+ }) => Promise<{ path: string; content: string | Uint8Array }[]>;
224
+ screenshots: (app: StoreApp) => Promise<{ src: string; label: string }[]>;
225
+ themeScreenshots: (theme: Pick<StoreTheme, 'screenshots' | 'files'>) => Promise<{ key: string; src: string }[]>;
226
+ };
227
+ /** A picture's bytes by its blob id (github.ts loadBytes). */
228
+ bytes: (oid: string, address?: string) => Promise<Uint8Array>;
229
+ /** The addresses visitors came to that do not exist (notFound.ts; null: the site does not keep
230
+ * them), and taking some off that list. */
231
+ notFound: { list: () => Promise<Missing[] | null>; forget: (paths: string[]) => Promise<void> };
232
+ /** An app's own addresses in the site's Worker (/api/<app>/<path>, its worker.js), asked with the GitHub sign-in, for
233
+ * its page (AppDefinition.page). */
234
+ appApi: (app: string, path?: string, init?: RequestInit) => Promise<Response>;
235
+ /** Files deleted on their own (a file in Filer). */
236
+ forget: (paths: string[]) => void;
237
+ /** A file was saved; more: other files saved with it, and files it removed (a post that moved). */
238
+ onSaved: (file: RepoFile, more?: { files: RepoFile[]; removed: string[] }) => void;
239
+ remove: (collection: Collection & { folder: string }, file: RepoFile, deleting: (b: boolean) => void) => void;
240
+ nextOrder: (collection: FolderCollection) => number;
241
+ /** Saves a list in a new order; says what changed (deploy.ts). */
242
+ reorder: (collection: Collection, files: RepoFile[]) => Promise<string>;
243
+ };
244
+
245
+ // The uploaded pictures only by their paths (no text), for "Filer" (Media.tsx).
246
+ const FOLDERS = config.folders;
247
+ const FOLDERS_KEY = JSON.stringify(FOLDERS);
248
+
249
+ const json = (file: RepoFile) => JSON.parse(file.text) as Record<string, unknown>;
250
+
251
+ // Addresses of the site that are not pages.
252
+ const RESERVED = config.reserved;
253
+
254
+ /** Why a new page or post cannot have an address (/<slug>/): a message; with `redirect` when the site
255
+ * sends it on elsewhere, which the site's owner may remove (EntryForm.tsx): where it ends after every redirect
256
+ * (redirects.ts), its name, and how many other addresses send on through it. */
257
+ export type Taken = { text: string; redirect?: { end: string; name: string; via: number } };
258
+
259
+ // Why a new page or post cannot have an address (/<slug>/), or null. redirects: the files of the
260
+ // redirects (redirects.ts), as they are now.
261
+ function taken(address: string, relations: Record<Relation, Option[]>, redirects: RepoFile[]): Taken | null {
262
+ const pages = [...relations.pages, ...relations.fixed].map((o) => o.value);
263
+ const posts = relations.blog.map((o) => `/${o.value}/`);
264
+ if (pages.includes(address) || posts.includes(address) || RESERVED.includes(address)) return { text: t.app.addressTaken(address) };
265
+ const all = readAll(redirects);
266
+ const look = lookup(all);
267
+ if (look(address) === undefined) return null;
268
+ const { end } = endOf(address, look);
269
+ const via = entriesOf(all).filter((e) => e.from !== address && e.to.split('#')[0] === address).length;
270
+ const name = whatIsAt(end, relations);
271
+ return { text: t.app.addressRedirects(address, name), redirect: { end, name, via } };
272
+ }
273
+
274
+ // Screens that have moved, by their old address: a link or a bookmark to one opens it where it is now.
275
+ const MOVED: Record<string, string> = {
276
+ farver: 'udseende/farver',
277
+ 'virksomheden/logo': 'udseende/logo',
278
+ 'tema/blokvalg': 'blokvalg',
279
+ dele: 'udseende/dele',
280
+ tema: 'udseende',
281
+ 'tema/farver': 'udseende/farver',
282
+ 'tema/logo': 'udseende/logo',
283
+ 'tema/dele': 'udseende/dele',
284
+ 'tema/dele/editor': 'udseende/dele/editor',
285
+ 'virksomheden/bank': 'virksomheden/betaling/bank',
286
+ videresendelser: 'seo/videresendelser',
287
+ 'videresendelser/alle': 'seo/videresendelser/alle',
288
+ 'videresendelser/ikke-fundet': 'seo/videresendelser/ikke-fundet',
289
+ [STORE_BEFORE]: STORE_ROUTE,
290
+ [`${STORE_BEFORE}/temaer`]: STORE_THEMES,
291
+ };
292
+
293
+ // The editor's address after the "#" (nav.ts screenOf).
294
+ function routeOf(hash: string) {
295
+ const route = MOVED[screenOf(hash)] ?? screenOf(hash);
296
+ // An app's settings: on the screen with its category, when it has one ("Analytics", AppList.tsx settingsRoute), and so
297
+ // the pages of their fields (Field.page) after them.
298
+ const app = apps.find((a) => route === `apps/${a.id}/indstillinger` || route.startsWith(`apps/${a.id}/indstillinger/`));
299
+ const rest = app ? route.slice(`apps/${app.id}/indstillinger`.length) : '';
300
+ const moved = (app && settingsRoute(app) && `${settingsRoute(app)}${rest}`) || route;
301
+ if (moved !== screenOf(hash)) history.replaceState(history.state, '', `#${moved}`);
302
+ return moved;
303
+ }
304
+
305
+ // The editor; what it draws of the site is in the editor (InEditor: a link only drawn …), its own screens are its own
306
+ // (EditorOwn: the theme's UI parts on them are links that lead somewhere).
307
+ export default function App() {
308
+ return (
309
+ <InEditor>
310
+ <EditorOwn>
311
+ <Editor />
312
+ </EditorOwn>
313
+ </InEditor>
314
+ );
315
+ }
316
+
317
+ function Editor() {
318
+ // Whether someone is signed in (the Worker keeps the sign-in, packages/editor/worker/session.js): null until the Worker or this
319
+ // browser's copy of the files (cache.ts) has said.
320
+ const [signedIn, setSignedIn] = useState<boolean | null>(null);
321
+ // Whether the sidebar is open on a computer (sidebar.ts).
322
+ const sidebar = useSidebar();
323
+ // While it is closed: the mouse over the rail, which then shows the whole sidebar over the content.
324
+ const [peek, setPeek] = useState(false);
325
+ const peekTimer = useRef(0);
326
+ const [user, setUser] = useState<Viewer | null>(null);
327
+ const [files, setFiles] = useState<RepoFile[] | null>(null);
328
+ const [error, setError] = useState('');
329
+ const [route, setRoute] = useState(() => routeOf(location.hash));
330
+ // Counts the visits to an address: a form starts fresh on each, but not when a new entry that was
331
+ // just saved gets its own address.
332
+ const [visit, setVisit] = useState(0);
333
+ // The messages at the bottom stand in the middle of the content, next to the sidebar (editor.css).
334
+ useEffect(() => {
335
+ document.documentElement.dataset.sidebar = sidebar ? 'open' : 'closed';
336
+ }, [sidebar]);
337
+ // Stops waiting for the last save to be on the site.
338
+ const stopWaiting = useRef<() => void>(undefined);
339
+ // What the saves the editor waits for the website to have changed (save).
340
+ const waitingFor = useRef<string[]>([]);
341
+
342
+ useEffect(registerWorker, []);
343
+ useEffect(startRipples, []);
344
+ // Signed up for the notifications on the phone from the site's Worker, also when the editor is closed, while
345
+ // "På telefonen" is on (notifications.ts).
346
+ useEffect(() => {
347
+ if (signedIn) syncDevice();
348
+ }, [signedIn]);
349
+ // A tap on such a notification while the editor is open: to the screen it is about (packages/editor/pages/editor/sw.js).
350
+ useEffect(() => {
351
+ const onMessage = (e: MessageEvent) => typeof e.data?.to === 'string' && leaveTo(`#${e.data.to}`);
352
+ navigator.serviceWorker?.addEventListener('message', onMessage);
353
+ return () => navigator.serviceWorker?.removeEventListener('message', onMessage);
354
+ }, []);
355
+ // When the site was last updated (built on Cloudflare), for the start screen.
356
+ const [builtAt, setBuiltAt] = useState<Date | null>(null);
357
+ useEffect(() => keepCurrent((v) => v.builtAt && setBuiltAt(new Date(v.builtAt))), []);
358
+
359
+ // Where the editor is, to come back to when leaving is asked about.
360
+ const here = useRef(route);
361
+ here.current = route;
362
+ useEffect(() => {
363
+ const onHash = () => {
364
+ // The history's step (nav.ts): null when it undoes the one below.
365
+ const step = arrived(here.current);
366
+ if (!step) return;
367
+ // To or from a page of the open form's own (a field's, Field.page): the form stays, with what is in it, and
368
+ // scrolls itself (CompanyForm.tsx).
369
+ const next = routeOf(location.hash);
370
+ if (inForm(here.current, next)) return setRoute(next);
371
+ // While something is unsaved, another address (the phone's back gesture, the sidebar, a link)
372
+ // is first undone, and taken again only when the owner leaves without saving (leaveTo).
373
+ if (hasUnsaved()) {
374
+ step.undo();
375
+ return leaveTo(step.redo);
376
+ }
377
+ setRoute(next);
378
+ setVisit((v) => v + 1);
379
+ scrollTo(0, 0);
380
+ };
381
+ window.addEventListener('hashchange', onHash);
382
+ return () => window.removeEventListener('hashchange', onHash);
383
+ }, []);
384
+
385
+ // The commit the files are from. Not moved on by the owner's own saves, so the next check loads the
386
+ // files again with those changes and anything else that came in.
387
+ const commitOf = useRef('');
388
+ // Counts the owner's saves, so that files loaded while one was made (from before it) are not shown.
389
+ const saves = useRef(0);
390
+
391
+ // The files from this browser at once (cache.ts), then from GitHub only when the branch has moved
392
+ // on: checked on opening, and when the app is shown again, at most every half minute, and not
393
+ // while something is unsaved. Only the files whose text this browser does not have are loaded (github.ts). The open
394
+ // form keeps the file it opened with; saving checks it. Signed out (the Worker says so), the sign-in is shown.
395
+ const signedOut = signedIn === false;
396
+ useEffect(() => {
397
+ if (signedOut) return;
398
+ let stopped = false;
399
+ let checked = 0;
400
+ async function refresh() {
401
+ if (stopped || hasUnsaved() || Date.now() - checked < 30_000) return;
402
+ checked = Date.now();
403
+ try {
404
+ const [user, at] = await Promise.all([viewer(), head()]);
405
+ if (stopped) return;
406
+ setUser(user);
407
+ setSignedIn(true);
408
+ if (at.commit !== commitOf.current) {
409
+ const before = saves.current;
410
+ const loaded = await loadFolders(FOLDERS, 3, at);
411
+ if (stopped) return;
412
+ // Saved meanwhile: these files are older than that save; the next check loads them again.
413
+ if (saves.current !== before) {
414
+ checked = 0;
415
+ return;
416
+ }
417
+ commitOf.current = loaded.commit;
418
+ setFiles(Object.values(loaded.folders).flat());
419
+ }
420
+ } catch (e) {
421
+ if (e instanceof AuthError) signOut();
422
+ // With files from this browser, the editor goes on; the next check tries again.
423
+ else if (!commitOf.current) setError((e as Error).message);
424
+ checked = 0;
425
+ }
426
+ }
427
+ readCache().then((cached) => {
428
+ if (stopped) return;
429
+ if (cached) {
430
+ // Kept from other folders than these (an older editor): shown, and loaded again at once.
431
+ commitOf.current = cached.folders === FOLDERS_KEY ? cached.commit : '';
432
+ rememberTexts(cached.files);
433
+ setUser(cached.user);
434
+ setFiles(cached.files);
435
+ setSignedIn(true);
436
+ }
437
+ refresh();
438
+ });
439
+ const onShow = () => document.visibilityState === 'visible' && refresh();
440
+ document.addEventListener('visibilitychange', onShow);
441
+ return () => {
442
+ stopped = true;
443
+ document.removeEventListener('visibilitychange', onShow);
444
+ };
445
+ }, [signedOut]);
446
+
447
+ // Keeps what is shown, with the owner's saves, for the next time the editor opens.
448
+ useEffect(() => {
449
+ if (files && user && commitOf.current) writeCache({ commit: commitOf.current, files, user, folders: FOLDERS_KEY });
450
+ }, [files, user]);
451
+
452
+ const relations = useMemo(() => relationsOf(files ?? []), [files]);
453
+ // What a link in the text can go to (LinkSheet.tsx): the fixed pages (not those that are also a
454
+ // page), the other pages and the posts, by their addresses.
455
+ const linkGroups = useMemo(() => {
456
+ const fixed = relations.fixed.filter((f) => !relations.pages.some((o) => o.value === f.value));
457
+ return [
458
+ { title: t.forms.fixedPages, options: fixed },
459
+ { title: t.forms.pages, options: relations.pages },
460
+ { title: t.text.linkPosts, options: relations.blog.map((o) => ({ ...o, value: `/${o.value}/` })) },
461
+ ].filter((g) => g.options.length);
462
+ }, [relations]);
463
+ const listRows = useMemo(() => listRowsOf(files ?? []), [files]);
464
+ const testimonials = useMemo(() => testimonialsOf(files ?? []), [files]);
465
+
466
+ // The site's colours as loaded and saved ("Farver"), also before the site is built again with them.
467
+ const colorsFile = files?.find((f) => f.path === config.colors)?.text;
468
+ useEffect(() => {
469
+ if (colorsFile !== undefined) applyColors(colorsOf(files!));
470
+ }, [colorsFile]);
471
+ // How the site moves ("Bevægelse") as loaded and saved, also before the site is built again with it.
472
+ const motionFile = files?.find((f) => f.path === config.motion)?.text;
473
+ useEffect(() => {
474
+ if (files) applyMotion(motionOf(files));
475
+ }, [motionFile, files === null]);
476
+
477
+ const siteData = useMemo(() => siteOf(files ?? []), [files]);
478
+ const media = useMemo(() => mediaOf(files ?? []), [files]);
479
+
480
+ // A message at the bottom (snackbar in lib/snackbar.ts, the same as on the site), with a link to what it
481
+ // is about; one of a kind kept in the notifications too (notifications.ts).
482
+ function notify(next: Status, device = true) {
483
+ const edit = next?.edit;
484
+ if (next?.kind)
485
+ remember({ text: next.text, type: next.kind, error: next.error, link: edit && { text: edit.text, href: `#${edit.to}` } }, device);
486
+ snackbar(
487
+ next && {
488
+ text: next.text,
489
+ error: next.error,
490
+ links: [
491
+ ...(edit
492
+ ? [
493
+ {
494
+ text: edit.text,
495
+ href: `#${edit.to}`,
496
+ onClick: (e: MouseEvent) => {
497
+ e.preventDefault();
498
+ leaveTo(`#${edit.to}`);
499
+ },
500
+ },
501
+ ]
502
+ : []),
503
+ ],
504
+ },
505
+ );
506
+ }
507
+
508
+ async function save(message: string, changes: Change[], change: string) {
509
+ const saved = await commit(message, changes);
510
+ // One message for all saves: a later save is on the site together with or after the ones before. It says what
511
+ // each of them changed (each once).
512
+ stopWaiting.current?.();
513
+ if (!waitingFor.current.includes(change)) waitingFor.current.push(change);
514
+ const what = changesText(waitingFor.current);
515
+ // The Worker says it on the phone too, also when the editor is closed by then (notifications.ts).
516
+ const onServer = waitOnServer(saved.commit, t.app.siteUpdated(what), t.app.siteNotUpdated(what));
517
+ stopWaiting.current = waitForSite(saved.commit, async (online) => {
518
+ waitingFor.current = [];
519
+ const text = online ? t.app.siteUpdated(what) : t.app.siteNotUpdated(what);
520
+ // On screen the editor says it, and the Worker does not; else only the Worker sends it to the phone.
521
+ const byServer = document.visibilityState !== 'visible' && (await onServer);
522
+ if (!byServer) stopServerWait();
523
+ notify({ text, error: !online, kind: 'site' }, !byServer);
524
+ });
525
+ return saved.blobs;
526
+ }
527
+
528
+ function signOut() {
529
+ stopWaiting.current?.();
530
+ waitingFor.current = [];
531
+ // No notifications on this phone for someone signed out: signed off at the Worker while the sign-in is still there.
532
+ void signOff()
533
+ .catch(() => {})
534
+ .then(logout);
535
+ commitOf.current = '';
536
+ writeCache(null);
537
+ clearNotifications();
538
+ setSignedIn(false);
539
+ setUser(null);
540
+ setFiles(null);
541
+ }
542
+
543
+ const replace = (saved: RepoFile[], removed: string[] = []) => {
544
+ saves.current++;
545
+ setFiles((all) => [...(all ?? []).filter((f) => !saved.some((s) => s.path === f.path) && !removed.includes(f.path)), ...saved]);
546
+ };
547
+
548
+ const env: Env = {
549
+ relations,
550
+ lists: listRows,
551
+ testimonials,
552
+ site: siteData,
553
+ media,
554
+ files: files ?? [],
555
+ forget: (paths) => replace([], paths),
556
+ notify,
557
+ commit: save,
558
+ savedAt,
559
+ versions: versionsOf,
560
+ versionText,
561
+ deleted: deletedFiles,
562
+ load: (paths) => loadFiles(paths),
563
+ folder: (path) => loadFolders({ all: path }, 8).then((r) => r.folders.all),
564
+ store: {
565
+ catalog: readCatalog,
566
+ themes: readThemes,
567
+ files: readApp,
568
+ screenshots: readScreenshots,
569
+ themeScreenshots: readThemeScreenshots,
570
+ },
571
+ bytes: (oid, address) => loadBytes(oid, address),
572
+ notFound: { list: loadNotFound, forget: forgetNotFound },
573
+ // The sign-in's cookie goes with the request (packages/editor/worker/session.js).
574
+ appApi: (app, path = '', init) => fetch(`/api/${app}/${path}`, { cache: 'no-store', ...init }),
575
+ taken: (address, redirects) => taken(address, relations, redirects),
576
+ onSaved(file, more) {
577
+ replace([file, ...(more?.files ?? [])], more?.removed);
578
+ // A new entry, or a post that got a new address: from now on it is edited as the file it
579
+ // became. A form that saves another file than the one at its address (Virksomheden) stays where it is.
580
+ if (route.startsWith('ny/') || more?.removed.includes(route)) {
581
+ history.replaceState(history.state, '', `#${file.path}`);
582
+ setRoute(file.path);
583
+ }
584
+ },
585
+ async remove(collection, file, deleting) {
586
+ const reason = usedBy(collection, file, files ?? []);
587
+ if (reason) return notify(reason);
588
+ if (!(await ask(t.app.deleteEntry(collection.singular), t.common.delete))) return;
589
+ deleting(true);
590
+ notify({ text: t.common.deleting });
591
+ const change = t.app.changes.deleted(kindAndName(collection.singular, nameOf(collection, file)));
592
+ try {
593
+ await save(
594
+ siteT.app.commit.delete(asWritten(collection).singular, slugOf(file.path, collection.folder)),
595
+ [{ path: file.path, content: null, oid: file.oid }],
596
+ change,
597
+ );
598
+ replace([], [file.path]);
599
+ // Back to the list, as with the back arrow: the deleted entry is not left behind it in the history.
600
+ setUnsaved(false);
601
+ up(`#liste/${collection.id}`);
602
+ notify({ text: savedText(change), kind: 'saved' });
603
+ } catch (e) {
604
+ notify({ text: t.app.notDeleted((e as Error).message), error: true, kind: 'failed' });
605
+ deleting(false);
606
+ }
607
+ },
608
+ nextOrder(collection) {
609
+ const own = (files ?? []).filter((f) => f.path.startsWith(`${collection.folder}/`) && f.path.endsWith('.json'));
610
+ return Math.max(0, ...own.map((f) => Number(json(f).order) || 0)) + 1;
611
+ },
612
+ // Numbers the entries 1, 2, 3… in the new order and saves the files whose number changed.
613
+ async reorder(collection, ordered) {
614
+ const changes = ordered.flatMap((file, i) => {
615
+ const { order, ...rest } = json(file);
616
+ if (order === i + 1) return [];
617
+ const text = `${JSON.stringify({ order: i + 1, ...rest }, null, 2)}\n`;
618
+ return [{ path: file.path, content: text, oid: file.oid }];
619
+ });
620
+ const change = t.app.changes.order(lower(collection.label));
621
+ if (!changes.length) return change;
622
+ const oids = await save(siteT.app.commit.reorder(asWritten(collection).label.toLowerCase()), changes, change);
623
+ replace(changes.map((c) => ({ path: c.path, oid: oids[c.path], text: c.content })));
624
+ return change;
625
+ },
626
+ };
627
+
628
+ // The apps from the store updated on their own, once the files are there (autoUpdate in AppList.tsx).
629
+ const loaded = !!files;
630
+ useEffect(() => {
631
+ if (signedIn && loaded) autoUpdate(env, files!);
632
+ }, [signedIn, loaded]);
633
+
634
+ // What the installed apps have to say (AppNotifications in apps.ts): asked when the editor opens and every 15 minutes
635
+ // while it is open; each comes once, at the bottom and under "Notifikationer", while its type is on (notifications.ts).
636
+ const notifying = installedApps(files ?? [])
637
+ .filter((id) => apps.some((a) => a.id === id && a.notifications))
638
+ .join();
639
+ useEffect(() => {
640
+ if (!signedIn || !notifying) return;
641
+ const ask = () => {
642
+ for (const app of apps.filter((a) => notifying.split(',').includes(a.id))) {
643
+ const { types, check } = app.notifications!;
644
+ check({ api: (path, init) => env.appApi(app.id, path, init) })
645
+ .then((found) => {
646
+ for (const n of found) {
647
+ const type = appType(app.id, n.type);
648
+ if (!(n.type in types) || told(type, n.id) || !isOn(type)) continue;
649
+ const to = siteText(n.link ?? app.label);
650
+ const link = n.to ? { text: to, href: `#${n.to}` } : undefined;
651
+ remember({ text: n.text, type, error: n.error, link });
652
+ notify({ text: n.text, error: n.error, edit: n.to ? { to: n.to, text: to } : undefined });
653
+ }
654
+ })
655
+ .catch(() => {});
656
+ }
657
+ };
658
+ ask();
659
+ const timer = setInterval(ask, 15 * 60_000);
660
+ return () => clearInterval(timer);
661
+ }, [signedIn, notifying]);
662
+
663
+ // Signed out, or not yet known with nothing from this browser to show: the sign-in (while the Worker is asked, the
664
+ // icon and the name alone).
665
+ if (signedIn === false || (signedIn === null && !files)) {
666
+ return (
667
+ <main className="mx-auto flex min-h-dvh max-w-sm flex-col items-center justify-center gap-5 px-6 pb-[env(safe-area-inset-bottom)] text-center">
668
+ <img src="/editor/icon-192.png" alt="" className="size-20 rounded-[1.375rem] shadow-soft" />
669
+ <h1 className="text-2xl font-semibold">{siteName}</h1>
670
+ {signedIn === null && !error ? (
671
+ <EmptyNote busy>{t.app.loading}</EmptyNote>
672
+ ) : (
673
+ <Button wide onClick={login}>
674
+ {t.app.logIn}
675
+ </Button>
676
+ )}
677
+ {error && <ProblemNote>{error}</ProblemNote>}
678
+ </main>
679
+ );
680
+ }
681
+
682
+ // The part of the site that is open, for the sidebar: the menu under "Header", the lists and testimonials under
683
+ // "Lister", the front page under "Udseende".
684
+ const open = collectionOf(collections, route)?.collection;
685
+ const part = open?.id || route.replace(/^(liste|ny|slettet)\/([^/]+).*/, '$2');
686
+ const current = lists.includes(part)
687
+ ? 'lister'
688
+ : header.includes(part)
689
+ ? 'header'
690
+ : part.startsWith('virksomheden')
691
+ ? 'virksomheden'
692
+ : part.startsWith('blokke')
693
+ ? 'blokke'
694
+ : part.startsWith('udseende')
695
+ ? 'udseende'
696
+ : part.startsWith('seo')
697
+ ? 'seo'
698
+ : part.startsWith('apps')
699
+ ? 'apps'
700
+ : (partScreens.find((s) => part.startsWith(`${s.id}/`))?.id ?? part);
701
+
702
+ return (
703
+ <>
704
+ <div className="md:flex">
705
+ {/* On a computer: the parts of the site in the theme's navigation rail, expanded and collapsed with its menu
706
+ button; under it, while expanded, the logo and the name, which go to the start screen (the account is in the
707
+ top bar, Screen.tsx). Collapsed, the mouse over it shows it expanded over the content for a moment (a short wait
708
+ first, so passing over it does not), until the mouse leaves. On a phone, the start screen is the menu. */}
709
+ <aside
710
+ className="sticky top-0 z-30 hidden h-dvh shrink-0 pt-[env(safe-area-inset-top)] md:flex"
711
+ onPointerEnter={(e) => {
712
+ if (sidebar || e.pointerType !== 'mouse') return;
713
+ peekTimer.current = window.setTimeout(() => setPeek(true), 150);
714
+ }}
715
+ onPointerLeave={() => {
716
+ clearTimeout(peekTimer.current);
717
+ setPeek(false);
718
+ }}
719
+ >
720
+ <NavigationRail
721
+ label={t.library.sidebar}
722
+ menuLabel={sidebar ? t.library.closeSidebar : t.library.openSidebar}
723
+ expanded={sidebar}
724
+ peek={peek}
725
+ onExpandedChange={(open) => {
726
+ setPeek(false);
727
+ setSidebar(open);
728
+ }}
729
+ header={
730
+ <a href="#" className="flex min-w-0 items-center gap-3 font-semibold">
731
+ <img src="/editor/icon-192.png" alt="" className="size-7 shrink-0 rounded-lg" />
732
+ <span className="truncate">{siteName}</span>
733
+ </a>
734
+ }
735
+ >
736
+ {groups.map((g) => (
737
+ <NavigationRailSection key={g.title} label={g.title}>
738
+ {partsIn(g.ids, files ?? []).map(({ id, label, href, count }) => (
739
+ <NavigationRailItem
740
+ key={id}
741
+ href={href}
742
+ icon={icons[id]}
743
+ label={label}
744
+ count={files ? count : null}
745
+ selected={current === id}
746
+ />
747
+ ))}
748
+ </NavigationRailSection>
749
+ ))}
750
+ </NavigationRail>
751
+ </aside>
752
+
753
+ <div className="min-w-0 flex-1">
754
+ {files ? (
755
+ <>
756
+ {user && !user.canSave && (
757
+ <p className="bg-accent px-4 py-3 pt-[calc(0.75rem+env(safe-area-inset-top))] text-center text-sm text-on-accent">
758
+ {t.app.cannotSave(user.login)}
759
+ </p>
760
+ )}
761
+ <Page key={visit} route={route} files={files} env={env} user={user} builtAt={builtAt} onLogout={signOut} />
762
+ </>
763
+ ) : (
764
+ <Screen title={siteName}>
765
+ <EmptyNote busy={!error}>{error || t.app.loading}</EmptyNote>
766
+ </Screen>
767
+ )}
768
+ </div>
769
+
770
+ <LinkAsk groups={linkGroups} />
771
+ <Ask />
772
+ </div>
773
+ </>
774
+ );
775
+ }
776
+
777
+ /** The part whose form is at an address (CompanyForm.tsx): an app's settings on a screen of their own, one of
778
+ * "Virksomheden"'s (or of its parts', with the settings of the apps of their kinds), "Søgemaskineoptimering"'s, "Udseende"'s, or one on a screen of parts with
779
+ * the settings of the apps of its kinds ("Analytics"). */
780
+ function partAt(route: string, files: RepoFile[]): Part | null {
781
+ // The settings of an app installed without a screen with its kind, on a screen of their own.
782
+ const settingsOf = apps.find((a) => route === `apps/${a.id}/indstillinger`);
783
+ if (settingsOf) return (installedApps(files).includes(settingsOf.id) && appSettingsPart(settingsOf)) || null;
784
+ // virksomheden/<part>, or virksomheden/<part>/<its part> (Part.parts).
785
+ const [, companyId, ownId, ...more] = route.split('/');
786
+ const company = route.startsWith('virksomheden/') && !more.length && companyParts.find((p) => p.id === companyId);
787
+ return (
788
+ (company &&
789
+ (ownId ? [...(company.parts ?? []), ...appPartsIn(company, files)].find((p) => p.id === ownId) : company.file && company)) ||
790
+ (route.startsWith('blokke/') && blockParts.find((p) => p.id === route.slice(7))) ||
791
+ (route.startsWith('seo/') && seoParts.find((p) => p.id === route.slice(4))) ||
792
+ (route.startsWith('udseende/') && appearance.find((p) => p.id === route.slice(9))) ||
793
+ partScreens.flatMap((s) => [...s.parts, ...appPartsIn(s, files)].filter((p) => route === `${s.id}/${p.id}`))[0] ||
794
+ null
795
+ );
796
+ }
797
+
798
+ /** The form at an address: a part's (partAt, `at` its address), or the page of one of its fields with its choices on a
799
+ * page of their own (Field.page: the form's address with the field's name after it, `page`). */
800
+ function formAt(route: string, files: RepoFile[]): { part: Part; at: string; page?: string } | null {
801
+ const part = partAt(route, files);
802
+ if (part) return { part, at: route };
803
+ const cut = route.lastIndexOf('/');
804
+ const above = cut > 0 ? partAt(route.slice(0, cut), files) : null;
805
+ const page = route.slice(cut + 1);
806
+ return above?.fields?.some((f) => f.name === page && f.page) ? { part: above, at: route.slice(0, cut), page } : null;
807
+ }
808
+
809
+ type PageProps = { route: string; files: RepoFile[]; env: Env; user: Viewer | null; builtAt: Date | null; onLogout: () => void };
810
+
811
+ function Page({ route, files, env, user, builtAt, onLogout }: PageProps) {
812
+ if (!route) return <Start files={files} builtAt={builtAt} />;
813
+ if (route === 'konto') return <Account user={user} files={files} onLogout={onLogout} />;
814
+ if (route === 'notifikationer') return <Notifications />;
815
+ if (route === 'indstillinger') return <Settings files={files} />;
816
+ if (route === 'header') return <Header files={files} />;
817
+ if (route === 'lister') return <Lists files={files} />;
818
+ if (route === 'virksomheden') return <Company files={files} />;
819
+ if (route === 'blokke') return <Blocks files={files} />;
820
+ if (route === 'filer') return <Media files={files} env={env} />;
821
+ if (route === 'seo') return <Seo />;
822
+ if (route === REDIRECTS_ROUTE) return <Redirects env={env} />;
823
+ if (route === SEO_SCORES_ROUTE) return <SeoScores files={files} env={env} />;
824
+ if (route === TAB_ROUTES.redirects) return <Redirects env={env} tab="redirects" />;
825
+ if (route === TAB_ROUTES.missing) return <Redirects env={env} tab="missing" />;
826
+ const screen = partScreens.find((s) => s.id === route);
827
+ if (screen) return <PartsScreen screen={screen} files={files} />;
828
+ // A part of a screen of parts that is one of the editor's own screens (Part.opens), going back to that screen.
829
+ const opening = partScreens.flatMap((s) => s.parts.filter((p) => p.opens && route === `${s.id}/${p.id}`).map((p) => ({ s, p })))[0];
830
+ if (opening?.p.opens === 'consents') return <Consents env={env} back={{ href: `#${opening.s.id}`, label: opening.s.label }} />;
831
+ if (route === 'udseende') return <Theme files={files} />;
832
+ if (route === 'udseende/temaer') return <Themes env={env} files={files} />;
833
+ if (route === 'udseende/ikonpakker') return <IconPacks env={env} files={files} />;
834
+ if (route === 'udseende/bevaegelse') return <MotionSchemes env={env} files={files} />;
835
+ // A theme installed has its page; one from the store that is not (or not yet built), its page in the store.
836
+ const themeShown = route.startsWith('udseende/temaer/') && themes.find((x) => x.id === route.slice(16));
837
+ if (themeShown) return <ThemePage key={themeShown.id} of={themeShown} env={env} files={files} />;
838
+ if (/^udseende\/temaer\/[^/]+$/.test(route)) return <StoreThemePage key={route} id={route.slice(16)} env={env} />;
839
+ // The colours, or the site's own on a page of the same form while an app changes the pages' (FALLBACK_ROUTE).
840
+ if (route === 'udseende/farver' || route === FALLBACK_ROUTE)
841
+ return <Colors env={env} files={files} fallback={route === FALLBACK_ROUTE} />;
842
+ if (route === 'udseende/logo') return <BrandForm env={env} />;
843
+ if (route === 'blokvalg') return <BlockSettings env={env} files={files} />;
844
+ if (route === ADVANCED_ROUTE) return <Advanced env={env} />;
845
+ if (route === LICENSES_ROUTE) return <Licenses />;
846
+ if (route.startsWith(`${LICENSES_ROUTE}/`)) return <LicensePage key={route} id={route.slice(LICENSES_ROUTE.length + 1)} />;
847
+ if (route === 'apps') return <Apps env={env} files={files} />;
848
+ if (route === STORE_ROUTE) return <StorePage env={env} files={files} tab="apps" />;
849
+ if (route === STORE_THEMES) return <StorePage env={env} files={files} tab="themes" />;
850
+ if (route.startsWith(`${DEVELOPER_ROUTE}/`))
851
+ return <DeveloperPage key={route} slug={route.slice(DEVELOPER_ROUTE.length + 1)} env={env} files={files} />;
852
+ // An app in the site (its own, or one from the store while it is installed) has its page; one from the store that is
853
+ // not (or no longer) installed, its page in the store.
854
+ if (/^apps\/[^/]+$/.test(route)) {
855
+ const app = apps.find((a) => a.id === route.slice(5));
856
+ if (app && (!app.store || installedApps(files).includes(app.id))) return <AppPage app={app} env={env} files={files} />;
857
+ return <StoreAppPage key={route} id={route.slice(5)} env={env} files={files} />;
858
+ }
859
+ // A screen of an app's own under its page, while it is installed (Cookiescanner's "Blokeret adresse").
860
+ const [, screenApp, screenId, ...deeper] = route.split('/');
861
+ const ownScreen =
862
+ route.startsWith('apps/') && !deeper.length && apps.find((a) => a.id === screenApp && screenId && a.screens?.[screenId]);
863
+ if (ownScreen && installedApps(files).includes(ownScreen.id))
864
+ return <AppScreenPage key={route} app={ownScreen} id={screenId} env={env} />;
865
+ if (route === 'udseende/dele') return <PartsHome />;
866
+ if (route === 'udseende/dele/editor') return <Parts env={env} />;
867
+ if (route === 'udseende/dele/ui') return <ComponentParts />;
868
+ if (route === 'udseende/dele/blokke') return <BlocksParts />;
869
+ if (route.startsWith('udseende/dele/liste/')) return <KindParts key={route} id={route.slice(20)} />;
870
+ if (route === 'udseende/dele/vaerdier') return <Tokens />;
871
+ if (route.startsWith('slettet/')) return <Deleted id={route.slice(8)} files={files} env={env} />;
872
+ // virksomheden/<part>, or virksomheden/<part>/<its part> (Part.parts).
873
+ const [, companyId, ownId, ...more] = route.split('/');
874
+ const company = route.startsWith('virksomheden/') && !more.length && companyParts.find((p) => p.id === companyId);
875
+ if (company && company.parts && !ownId) return <Company part={company} files={files} />;
876
+ // A part's form, or the page of one of its fields (Field.page) with the form under it: the same form, so it keeps what
877
+ // is in it while the editor goes between them (useFormPages, inForm).
878
+ const form = formAt(route, files);
879
+ if (form) return <CompanyForm part={form.part} at={form.at} page={form.page} env={env} files={files} />;
880
+ // A list, or a tab an installed app adds to it (liste/<id>/<app>).
881
+ const [, listId, listTab, ...below] = route.split('/');
882
+ const listed = route.startsWith('liste/') && !below.length && collections.find((c) => c.id === listId);
883
+ if (listed) return <List key={listed.id} collection={listed} tab={listTab} files={files} env={env} />;
884
+
885
+ // ny/<list>, or ny/<list>/<kind> for one of its kinds (a pop-up: standard, mailchimp).
886
+ const [, newIn, newKind] = route.split('/');
887
+ const creating = route.startsWith('ny/') && collections.find((c) => c.id === newIn && c.create);
888
+ if (creating) {
889
+ if (creating.kind === 'markdown') return <EntryForm collection={creating} file={newMarkdown(creating, notOffered(files))} env={env} />;
890
+ if (creating.kind === 'folder')
891
+ return <DataForm key={route} collection={creating} kind={newKind} file={{ path: '', oid: '', text: '' }} env={env} />;
892
+ }
893
+
894
+ const found = collectionOf(collections, route);
895
+ const file = files.find((f) => f.path === route);
896
+ if (!found || !file)
897
+ return (
898
+ <Screen title={t.app.notFound} back={{ href: '#', label: siteName }}>
899
+ <EmptyNote>{t.app.nothingAt(route)}</EmptyNote>
900
+ </Screen>
901
+ );
902
+ if (found.collection.kind === 'markdown') return <EntryForm collection={found.collection} file={file} env={env} />;
903
+ return <DataForm collection={found.collection} dataFile={found.dataFile} file={file} env={env} />;
904
+ }