@morphemecms/cms 0.1.0 → 0.2.0-next-20261104150211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (908) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -21
  3. package/README.md +51 -51
  4. package/astro.mjs +162 -162
  5. package/bin/morpheme.mjs +38 -38
  6. package/client.d.ts +10 -10
  7. package/defaults/blockTexts.ts +33 -33
  8. package/defaults/collections.ts +774 -774
  9. package/defaults/colors.ts +125 -125
  10. package/defaults/company.ts +20 -20
  11. package/defaults/editor.ts +178 -178
  12. package/defaults/internalLinks.ts +30 -30
  13. package/defaults/opens.ts +18 -18
  14. package/defaults/popup.tsx +93 -93
  15. package/defaults/privacy.ts +79 -79
  16. package/defaults/rules.ts +450 -450
  17. package/defaults/schema.ts +16 -16
  18. package/defaults/sitedata.ts +129 -129
  19. package/defaults/standard.ts +106 -106
  20. package/defaults/standardPages.ts +87 -87
  21. package/defaults/texts.ts +481 -481
  22. package/demo/README.md +26 -26
  23. package/demo/src/content/blog/build-a-page-from-blocks.md +19 -19
  24. package/demo/src/content/blog/colors-everyone-can-read.md +19 -19
  25. package/demo/src/content/blog/edit-your-website-from-your-phone.md +21 -21
  26. package/demo/src/content/blog/every-save-is-a-version.md +21 -21
  27. package/demo/src/content/blog/schedule-a-post.md +17 -17
  28. package/demo/src/content/blog/secure-from-day-one.md +17 -17
  29. package/demo/src/content/blog/seo-without-the-guesswork.md +23 -23
  30. package/demo/src/content/blog/switch-themes-without-losing-anything.md +23 -23
  31. package/demo/src/content/blog/the-app-store.md +21 -21
  32. package/demo/src/content/pages/about.md +22 -22
  33. package/demo/src/content/pages/app-store.md +30 -30
  34. package/demo/src/content/pages/blocks.md +98 -98
  35. package/demo/src/content/pages/blog-page.md +11 -11
  36. package/demo/src/content/pages/contact.md +16 -16
  37. package/demo/src/content/pages/get-started.md +20 -20
  38. package/demo/src/content/pages/home.md +26 -26
  39. package/demo/src/content/pages/page-not-found.md +11 -11
  40. package/demo/src/content/pages/privacy-policy.md +24 -24
  41. package/demo/src/data/blocks.json +25 -25
  42. package/demo/src/data/booking.json +6 -6
  43. package/demo/src/data/categories/apps.json +6 -6
  44. package/demo/src/data/categories/design.json +6 -6
  45. package/demo/src/data/categories/editing.json +6 -6
  46. package/demo/src/data/categories/security.json +6 -6
  47. package/demo/src/data/categories/seo.json +6 -6
  48. package/demo/src/data/colors.json +75 -75
  49. package/demo/src/data/contact.json +8 -8
  50. package/demo/src/data/cookies.json +21 -21
  51. package/demo/src/data/footer.json +27 -27
  52. package/demo/src/data/menu.json +35 -35
  53. package/demo/src/data/mobile-menu.json +25 -25
  54. package/demo/src/data/popups/booking-bar.json +13 -13
  55. package/demo/src/data/popups/try-the-editor.json +16 -16
  56. package/demo/src/data/standard-pages.json +8 -8
  57. package/demo/src/data/testimonials/sample-bakery.json +10 -10
  58. package/demo/src/data/testimonials/sample-clinic.json +10 -10
  59. package/demo/src/data/testimonials/sample-studio.json +10 -10
  60. package/demo/src/site/colors.ts +11 -11
  61. package/demo/src/site/config.ts +19 -19
  62. package/icons/lucide/LICENSE +43 -43
  63. package/icons/lucide/address-book.svg +1 -1
  64. package/icons/lucide/app-window.svg +1 -1
  65. package/icons/lucide/arrow-counter-clockwise.svg +1 -1
  66. package/icons/lucide/arrow-down.svg +1 -1
  67. package/icons/lucide/arrow-left.svg +1 -1
  68. package/icons/lucide/arrow-right.svg +1 -1
  69. package/icons/lucide/arrow-square-out.svg +1 -1
  70. package/icons/lucide/arrow-u-up-left.svg +1 -1
  71. package/icons/lucide/arrow-u-up-right.svg +1 -1
  72. package/icons/lucide/arrow-up-right.svg +1 -1
  73. package/icons/lucide/arrow-up.svg +1 -1
  74. package/icons/lucide/arrows-down-up.svg +1 -1
  75. package/icons/lucide/arrows-in-line-horizontal.svg +1 -1
  76. package/icons/lucide/arrows-out-line-horizontal.svg +1 -1
  77. package/icons/lucide/article.svg +1 -1
  78. package/icons/lucide/bank.svg +1 -1
  79. package/icons/lucide/bell.svg +1 -1
  80. package/icons/lucide/bicycle.svg +1 -1
  81. package/icons/lucide/binoculars.svg +1 -1
  82. package/icons/lucide/book-open-text.svg +1 -1
  83. package/icons/lucide/book-open.svg +1 -1
  84. package/icons/lucide/browser.svg +1 -1
  85. package/icons/lucide/buildings.svg +1 -1
  86. package/icons/lucide/bus.svg +1 -1
  87. package/icons/lucide/calendar-blank.svg +1 -1
  88. package/icons/lucide/calendar-check.svg +1 -1
  89. package/icons/lucide/car.svg +1 -1
  90. package/icons/lucide/cards.svg +1 -1
  91. package/icons/lucide/caret-down.svg +1 -1
  92. package/icons/lucide/caret-left.svg +1 -1
  93. package/icons/lucide/caret-right.svg +1 -1
  94. package/icons/lucide/caret-up.svg +1 -1
  95. package/icons/lucide/certificate.svg +1 -1
  96. package/icons/lucide/chart-line-up.svg +1 -1
  97. package/icons/lucide/chats.svg +1 -1
  98. package/icons/lucide/check-circle.svg +1 -1
  99. package/icons/lucide/check.svg +1 -1
  100. package/icons/lucide/circle-half.svg +1 -1
  101. package/icons/lucide/circle.svg +1 -1
  102. package/icons/lucide/clock-counter-clockwise.svg +1 -1
  103. package/icons/lucide/clock.svg +1 -1
  104. package/icons/lucide/code.svg +1 -1
  105. package/icons/lucide/coffee.svg +1 -1
  106. package/icons/lucide/columns.svg +1 -1
  107. package/icons/lucide/cookie.svg +1 -1
  108. package/icons/lucide/copy.svg +1 -1
  109. package/icons/lucide/credit-card.svg +1 -1
  110. package/icons/lucide/currency-circle-dollar.svg +1 -1
  111. package/icons/lucide/cursor-click.svg +1 -1
  112. package/icons/lucide/database.svg +1 -1
  113. package/icons/lucide/desktop.svg +1 -1
  114. package/icons/lucide/device-mobile.svg +1 -1
  115. package/icons/lucide/door-open.svg +1 -1
  116. package/icons/lucide/download-simple.svg +1 -1
  117. package/icons/lucide/envelope-simple.svg +1 -1
  118. package/icons/lucide/eye-slash.svg +1 -1
  119. package/icons/lucide/eye.svg +1 -1
  120. package/icons/lucide/file-pdf.svg +1 -1
  121. package/icons/lucide/file-text.svg +1 -1
  122. package/icons/lucide/file-video.svg +1 -1
  123. package/icons/lucide/file-zip.svg +1 -1
  124. package/icons/lucide/flower-lotus.svg +1 -1
  125. package/icons/lucide/folder-simple.svg +1 -1
  126. package/icons/lucide/funnel.svg +1 -1
  127. package/icons/lucide/gauge.svg +1 -1
  128. package/icons/lucide/gear.svg +1 -1
  129. package/icons/lucide/github-logo.svg +1 -1
  130. package/icons/lucide/globe.svg +1 -1
  131. package/icons/lucide/google-logo.svg +1 -1
  132. package/icons/lucide/graduation-cap.svg +1 -1
  133. package/icons/lucide/hammer.svg +1 -1
  134. package/icons/lucide/hand-heart.svg +1 -1
  135. package/icons/lucide/handshake.svg +1 -1
  136. package/icons/lucide/hard-drives.svg +1 -1
  137. package/icons/lucide/heart.svg +1 -1
  138. package/icons/lucide/house.svg +1 -1
  139. package/icons/lucide/image-square.svg +1 -1
  140. package/icons/lucide/image.svg +1 -1
  141. package/icons/lucide/info.svg +1 -1
  142. package/icons/lucide/keyboard.svg +1 -1
  143. package/icons/lucide/leaf.svg +1 -1
  144. package/icons/lucide/lifebuoy.svg +1 -1
  145. package/icons/lucide/lightbulb.svg +1 -1
  146. package/icons/lucide/link-break.svg +1 -1
  147. package/icons/lucide/link-simple.svg +1 -1
  148. package/icons/lucide/list-bullets.svg +1 -1
  149. package/icons/lucide/list-checks.svg +1 -1
  150. package/icons/lucide/list-numbers.svg +1 -1
  151. package/icons/lucide/list.svg +1 -1
  152. package/icons/lucide/lock-simple-open.svg +1 -1
  153. package/icons/lucide/lock-simple.svg +1 -1
  154. package/icons/lucide/magnifying-glass.svg +1 -1
  155. package/icons/lucide/map-pin.svg +1 -1
  156. package/icons/lucide/map-trifold.svg +1 -1
  157. package/icons/lucide/medal.svg +1 -1
  158. package/icons/lucide/megaphone.svg +1 -1
  159. package/icons/lucide/minus.svg +1 -1
  160. package/icons/lucide/moon.svg +1 -1
  161. package/icons/lucide/newspaper.svg +1 -1
  162. package/icons/lucide/pack.json +23 -23
  163. package/icons/lucide/paint-brush-broad.svg +1 -1
  164. package/icons/lucide/palette.svg +1 -1
  165. package/icons/lucide/paper-plane-tilt.svg +1 -1
  166. package/icons/lucide/pencil-simple.svg +1 -1
  167. package/icons/lucide/phone.svg +1 -1
  168. package/icons/lucide/play-fill.svg +1 -1
  169. package/icons/lucide/plus.svg +1 -1
  170. package/icons/lucide/prohibit.svg +1 -1
  171. package/icons/lucide/puzzle-piece.svg +1 -1
  172. package/icons/lucide/question.svg +1 -1
  173. package/icons/lucide/quotes.svg +1 -1
  174. package/icons/lucide/rows.svg +1 -1
  175. package/icons/lucide/ruler.svg +1 -1
  176. package/icons/lucide/scan.svg +1 -1
  177. package/icons/lucide/shapes.svg +1 -1
  178. package/icons/lucide/share-network.svg +1 -1
  179. package/icons/lucide/shield-check.svg +1 -1
  180. package/icons/lucide/sidebar-simple.svg +1 -1
  181. package/icons/lucide/sign-out.svg +1 -1
  182. package/icons/lucide/signpost.svg +1 -1
  183. package/icons/lucide/sliders-horizontal.svg +1 -1
  184. package/icons/lucide/smiley.svg +1 -1
  185. package/icons/lucide/sparkle.svg +1 -1
  186. package/icons/lucide/square-half-bottom.svg +1 -1
  187. package/icons/lucide/square.svg +1 -1
  188. package/icons/lucide/squares-four.svg +1 -1
  189. package/icons/lucide/sticker.svg +1 -1
  190. package/icons/lucide/storefront.svg +1 -1
  191. package/icons/lucide/sun.svg +1 -1
  192. package/icons/lucide/swatches.svg +1 -1
  193. package/icons/lucide/tag.svg +1 -1
  194. package/icons/lucide/text-aa.svg +1 -1
  195. package/icons/lucide/text-align-left.svg +1 -1
  196. package/icons/lucide/text-b.svg +1 -1
  197. package/icons/lucide/text-h-one.svg +1 -1
  198. package/icons/lucide/text-h-three.svg +1 -1
  199. package/icons/lucide/text-h-two.svg +1 -1
  200. package/icons/lucide/text-italic.svg +1 -1
  201. package/icons/lucide/text-t.svg +1 -1
  202. package/icons/lucide/ticket.svg +1 -1
  203. package/icons/lucide/toggle-right.svg +1 -1
  204. package/icons/lucide/train.svg +1 -1
  205. package/icons/lucide/trash.svg +1 -1
  206. package/icons/lucide/upload-simple.svg +1 -1
  207. package/icons/lucide/user-circle.svg +1 -1
  208. package/icons/lucide/user.svg +1 -1
  209. package/icons/lucide/users-three.svg +1 -1
  210. package/icons/lucide/users.svg +1 -1
  211. package/icons/lucide/video-camera.svg +1 -1
  212. package/icons/lucide/warning-circle.svg +1 -1
  213. package/icons/lucide/warning.svg +1 -1
  214. package/icons/lucide/wave-sine.svg +1 -1
  215. package/icons/lucide/wheelchair.svg +1 -1
  216. package/icons/lucide/wrench.svg +1 -1
  217. package/icons/lucide/x.svg +1 -1
  218. package/icons/material-symbols/LICENSE +201 -201
  219. package/icons/material-symbols/address-book.svg +1 -1
  220. package/icons/material-symbols/app-window.svg +1 -1
  221. package/icons/material-symbols/arrow-counter-clockwise.svg +1 -1
  222. package/icons/material-symbols/arrow-down.svg +1 -1
  223. package/icons/material-symbols/arrow-left.svg +1 -1
  224. package/icons/material-symbols/arrow-right.svg +1 -1
  225. package/icons/material-symbols/arrow-square-out.svg +1 -1
  226. package/icons/material-symbols/arrow-u-up-left.svg +1 -1
  227. package/icons/material-symbols/arrow-u-up-right.svg +1 -1
  228. package/icons/material-symbols/arrow-up-right.svg +1 -1
  229. package/icons/material-symbols/arrow-up.svg +1 -1
  230. package/icons/material-symbols/arrows-down-up.svg +1 -1
  231. package/icons/material-symbols/arrows-in-line-horizontal.svg +1 -1
  232. package/icons/material-symbols/arrows-out-line-horizontal.svg +1 -1
  233. package/icons/material-symbols/article.svg +1 -1
  234. package/icons/material-symbols/bank.svg +1 -1
  235. package/icons/material-symbols/bell.svg +1 -1
  236. package/icons/material-symbols/bicycle.svg +1 -1
  237. package/icons/material-symbols/binoculars.svg +1 -1
  238. package/icons/material-symbols/book-open-text.svg +1 -1
  239. package/icons/material-symbols/book-open.svg +1 -1
  240. package/icons/material-symbols/browser.svg +1 -1
  241. package/icons/material-symbols/buildings.svg +1 -1
  242. package/icons/material-symbols/bus.svg +1 -1
  243. package/icons/material-symbols/calendar-blank.svg +1 -1
  244. package/icons/material-symbols/calendar-check.svg +1 -1
  245. package/icons/material-symbols/car.svg +1 -1
  246. package/icons/material-symbols/cards.svg +1 -1
  247. package/icons/material-symbols/caret-down.svg +1 -1
  248. package/icons/material-symbols/caret-left.svg +1 -1
  249. package/icons/material-symbols/caret-right.svg +1 -1
  250. package/icons/material-symbols/caret-up.svg +1 -1
  251. package/icons/material-symbols/certificate.svg +1 -1
  252. package/icons/material-symbols/chart-line-up.svg +1 -1
  253. package/icons/material-symbols/chats.svg +1 -1
  254. package/icons/material-symbols/check-circle.svg +1 -1
  255. package/icons/material-symbols/check.svg +1 -1
  256. package/icons/material-symbols/circle-half.svg +1 -1
  257. package/icons/material-symbols/circle.svg +1 -1
  258. package/icons/material-symbols/clock-counter-clockwise.svg +1 -1
  259. package/icons/material-symbols/clock.svg +1 -1
  260. package/icons/material-symbols/code.svg +1 -1
  261. package/icons/material-symbols/coffee.svg +1 -1
  262. package/icons/material-symbols/columns.svg +1 -1
  263. package/icons/material-symbols/cookie.svg +1 -1
  264. package/icons/material-symbols/copy.svg +1 -1
  265. package/icons/material-symbols/credit-card.svg +1 -1
  266. package/icons/material-symbols/currency-circle-dollar.svg +1 -1
  267. package/icons/material-symbols/cursor-click.svg +1 -1
  268. package/icons/material-symbols/database.svg +1 -1
  269. package/icons/material-symbols/desktop.svg +1 -1
  270. package/icons/material-symbols/device-mobile.svg +1 -1
  271. package/icons/material-symbols/door-open.svg +1 -1
  272. package/icons/material-symbols/download-simple.svg +1 -1
  273. package/icons/material-symbols/envelope-simple.svg +1 -1
  274. package/icons/material-symbols/eye-slash.svg +1 -1
  275. package/icons/material-symbols/eye.svg +1 -1
  276. package/icons/material-symbols/file-pdf.svg +1 -1
  277. package/icons/material-symbols/file-text.svg +1 -1
  278. package/icons/material-symbols/file-video.svg +1 -1
  279. package/icons/material-symbols/file-zip.svg +1 -1
  280. package/icons/material-symbols/flower-lotus.svg +1 -1
  281. package/icons/material-symbols/folder-simple.svg +1 -1
  282. package/icons/material-symbols/funnel.svg +1 -1
  283. package/icons/material-symbols/gauge.svg +1 -1
  284. package/icons/material-symbols/gear.svg +1 -1
  285. package/icons/material-symbols/github-logo.svg +1 -1
  286. package/icons/material-symbols/globe.svg +1 -1
  287. package/icons/material-symbols/google-logo.svg +1 -1
  288. package/icons/material-symbols/graduation-cap.svg +1 -1
  289. package/icons/material-symbols/hammer.svg +1 -1
  290. package/icons/material-symbols/hand-heart.svg +1 -1
  291. package/icons/material-symbols/handshake.svg +1 -1
  292. package/icons/material-symbols/hard-drives.svg +1 -1
  293. package/icons/material-symbols/heart.svg +1 -1
  294. package/icons/material-symbols/house.svg +1 -1
  295. package/icons/material-symbols/image-square.svg +1 -1
  296. package/icons/material-symbols/image.svg +1 -1
  297. package/icons/material-symbols/info.svg +1 -1
  298. package/icons/material-symbols/keyboard.svg +1 -1
  299. package/icons/material-symbols/leaf.svg +1 -1
  300. package/icons/material-symbols/lifebuoy.svg +1 -1
  301. package/icons/material-symbols/lightbulb.svg +1 -1
  302. package/icons/material-symbols/link-break.svg +1 -1
  303. package/icons/material-symbols/link-simple.svg +1 -1
  304. package/icons/material-symbols/list-bullets.svg +1 -1
  305. package/icons/material-symbols/list-checks.svg +1 -1
  306. package/icons/material-symbols/list-numbers.svg +1 -1
  307. package/icons/material-symbols/list.svg +1 -1
  308. package/icons/material-symbols/lock-simple-open.svg +1 -1
  309. package/icons/material-symbols/lock-simple.svg +1 -1
  310. package/icons/material-symbols/magnifying-glass.svg +1 -1
  311. package/icons/material-symbols/map-pin.svg +1 -1
  312. package/icons/material-symbols/map-trifold.svg +1 -1
  313. package/icons/material-symbols/medal.svg +1 -1
  314. package/icons/material-symbols/megaphone.svg +1 -1
  315. package/icons/material-symbols/minus.svg +1 -1
  316. package/icons/material-symbols/moon.svg +1 -1
  317. package/icons/material-symbols/newspaper.svg +1 -1
  318. package/icons/material-symbols/pack.json +29 -29
  319. package/icons/material-symbols/paint-brush-broad.svg +1 -1
  320. package/icons/material-symbols/palette.svg +1 -1
  321. package/icons/material-symbols/paper-plane-tilt.svg +1 -1
  322. package/icons/material-symbols/pencil-simple.svg +1 -1
  323. package/icons/material-symbols/phone.svg +1 -1
  324. package/icons/material-symbols/play-fill.svg +1 -1
  325. package/icons/material-symbols/plus.svg +1 -1
  326. package/icons/material-symbols/prohibit.svg +1 -1
  327. package/icons/material-symbols/puzzle-piece.svg +1 -1
  328. package/icons/material-symbols/question.svg +1 -1
  329. package/icons/material-symbols/quotes.svg +1 -1
  330. package/icons/material-symbols/rows.svg +1 -1
  331. package/icons/material-symbols/ruler.svg +1 -1
  332. package/icons/material-symbols/scan.svg +1 -1
  333. package/icons/material-symbols/shapes.svg +1 -1
  334. package/icons/material-symbols/share-network.svg +1 -1
  335. package/icons/material-symbols/shield-check.svg +1 -1
  336. package/icons/material-symbols/sidebar-simple.svg +1 -1
  337. package/icons/material-symbols/sign-out.svg +1 -1
  338. package/icons/material-symbols/signpost.svg +1 -1
  339. package/icons/material-symbols/sliders-horizontal.svg +1 -1
  340. package/icons/material-symbols/smiley.svg +1 -1
  341. package/icons/material-symbols/sparkle.svg +1 -1
  342. package/icons/material-symbols/square-half-bottom.svg +1 -1
  343. package/icons/material-symbols/square.svg +1 -1
  344. package/icons/material-symbols/squares-four.svg +1 -1
  345. package/icons/material-symbols/sticker.svg +1 -1
  346. package/icons/material-symbols/storefront.svg +1 -1
  347. package/icons/material-symbols/sun.svg +1 -1
  348. package/icons/material-symbols/swatches.svg +1 -1
  349. package/icons/material-symbols/tag.svg +1 -1
  350. package/icons/material-symbols/text-aa.svg +1 -1
  351. package/icons/material-symbols/text-align-left.svg +1 -1
  352. package/icons/material-symbols/text-b.svg +1 -1
  353. package/icons/material-symbols/text-h-one.svg +1 -1
  354. package/icons/material-symbols/text-h-three.svg +1 -1
  355. package/icons/material-symbols/text-h-two.svg +1 -1
  356. package/icons/material-symbols/text-italic.svg +1 -1
  357. package/icons/material-symbols/text-t.svg +1 -1
  358. package/icons/material-symbols/ticket.svg +1 -1
  359. package/icons/material-symbols/toggle-right.svg +1 -1
  360. package/icons/material-symbols/train.svg +1 -1
  361. package/icons/material-symbols/trash.svg +1 -1
  362. package/icons/material-symbols/upload-simple.svg +1 -1
  363. package/icons/material-symbols/user-circle.svg +1 -1
  364. package/icons/material-symbols/user.svg +1 -1
  365. package/icons/material-symbols/users-three.svg +1 -1
  366. package/icons/material-symbols/users.svg +1 -1
  367. package/icons/material-symbols/video-camera.svg +1 -1
  368. package/icons/material-symbols/warning-circle.svg +1 -1
  369. package/icons/material-symbols/warning.svg +1 -1
  370. package/icons/material-symbols/wave-sine.svg +1 -1
  371. package/icons/material-symbols/wheelchair.svg +1 -1
  372. package/icons/material-symbols/wrench.svg +1 -1
  373. package/icons/material-symbols/x.svg +1 -1
  374. package/icons/phosphor/pack.json +8 -8
  375. package/lib/apps.mjs +142 -139
  376. package/lib/apps.test.d.mts +1 -0
  377. package/lib/apps.test.mjs +50 -0
  378. package/lib/blocks.mjs +90 -90
  379. package/lib/caseCollisions.d.mts +4 -0
  380. package/lib/caseCollisions.mjs +62 -0
  381. package/lib/caseCollisions.test.d.mts +1 -0
  382. package/lib/caseCollisions.test.mjs +74 -0
  383. package/lib/colors.ts +86 -86
  384. package/lib/contactBar.ts +18 -18
  385. package/lib/contracts.d.mts +15 -0
  386. package/lib/contracts.mjs +17 -0
  387. package/lib/cookieCategories.mjs +28 -28
  388. package/lib/crop.mjs +83 -83
  389. package/lib/csp.mjs +130 -130
  390. package/lib/cspSources.mjs +60 -60
  391. package/lib/dynamic-colors.ts +426 -426
  392. package/lib/git.mjs +64 -64
  393. package/lib/hooks.ts +51 -51
  394. package/lib/inline.ts +20 -20
  395. package/lib/license-texts/Apache-2.0.txt +190 -190
  396. package/lib/license-texts/BSD-2-Clause.txt +24 -24
  397. package/lib/license-texts/BSD-3-Clause.txt +28 -28
  398. package/lib/license-texts/ISC.txt +15 -15
  399. package/lib/license-texts/MIT.txt +21 -21
  400. package/lib/licenses.d.mts +1 -1
  401. package/lib/licenses.mjs +683 -683
  402. package/lib/markdown.ts +42 -42
  403. package/lib/motion-schemes.mjs +5 -5
  404. package/lib/motion.ts +131 -131
  405. package/lib/notice.mjs +34 -34
  406. package/lib/scheduled.mjs +37 -37
  407. package/lib/snackbar.ts +149 -149
  408. package/lib/status.mjs +18 -18
  409. package/lib/storeEntries.d.mts +18 -0
  410. package/lib/storeEntries.mjs +64 -0
  411. package/lib/storeEntries.test.d.mts +1 -0
  412. package/lib/storeEntries.test.mjs +71 -0
  413. package/lib/svg.mjs +231 -231
  414. package/lib/text-colors.mjs +43 -43
  415. package/lib/video-id.mjs +72 -72
  416. package/lib/video.mjs +91 -91
  417. package/package.json +136 -131
  418. package/pages/404.astro +10 -10
  419. package/pages/[...slug].astro +51 -51
  420. package/pages/_headers.txt +31 -31
  421. package/pages/blog/index.astro +10 -10
  422. package/pages/blog/kategori/[category].astro +18 -18
  423. package/pages/editor/code-images.json.js +25 -25
  424. package/pages/editor/index.astro +75 -75
  425. package/pages/editor/manifest.webmanifest.js +23 -23
  426. package/pages/editor/sw.js +33 -33
  427. package/pages/editor/sw.js.js +4 -4
  428. package/pages/editor/thumbnails.json.js +21 -21
  429. package/pages/editor/version.json.js +22 -22
  430. package/pages/headers.js +4 -4
  431. package/pages/index.astro +10 -10
  432. package/pages/ny.astro +6 -6
  433. package/pages/robots.txt.js +3 -3
  434. package/pages/rss.xml.js +20 -20
  435. package/scripts/app-screenshots.mjs +164 -164
  436. package/scripts/app-workers.mjs +6 -6
  437. package/scripts/bin.mjs +12 -12
  438. package/scripts/built-site.mjs +134 -99
  439. package/scripts/check-consent.mjs +113 -113
  440. package/scripts/check-content.mjs +151 -149
  441. package/scripts/check-demo.mjs +24 -24
  442. package/scripts/check-store.mjs +160 -0
  443. package/scripts/check-themes.mjs +45 -45
  444. package/scripts/check-worker.mjs +569 -569
  445. package/scripts/compare-html.mjs +81 -81
  446. package/scripts/declarations.mjs +10 -0
  447. package/scripts/declarations.test.mjs +32 -0
  448. package/scripts/demo-site.mjs +42 -42
  449. package/scripts/editor-icons.mjs +56 -56
  450. package/scripts/editor-roundtrip.mjs +134 -134
  451. package/scripts/editor-screens.mjs +101 -101
  452. package/scripts/git-env.mjs +9 -9
  453. package/scripts/icon-packs.mjs +151 -151
  454. package/scripts/link-counts.mjs +22 -22
  455. package/scripts/page-colors.mjs +42 -42
  456. package/scripts/releases.mjs +80 -80
  457. package/scripts/responsive-images.mjs +94 -94
  458. package/scripts/scheduled-list.mjs +15 -15
  459. package/scripts/theme-screenshots.mjs +88 -88
  460. package/scripts/types.mjs +17 -21
  461. package/scripts/video-thumbnails.mjs +89 -89
  462. package/scripts/worker-modules.mjs +34 -34
  463. package/site/appCookies.mjs +74 -74
  464. package/site/apps.ts +21 -21
  465. package/site/autolinks.mjs +200 -200
  466. package/site/blockScripts.ts +31 -31
  467. package/site/categories.ts +16 -16
  468. package/site/consent.ts +26 -26
  469. package/site/content.config.ts +84 -84
  470. package/site/content.ts +104 -104
  471. package/site/cookieList.ts +17 -17
  472. package/site/defineSite.d.mts +2 -14
  473. package/site/defineSite.mjs +131 -136
  474. package/site/directives/interact.mjs +31 -31
  475. package/site/editor-version.mjs +26 -26
  476. package/site/fill.ts +13 -13
  477. package/site/format.ts +11 -11
  478. package/site/home.ts +66 -66
  479. package/site/iconPacks.mjs +43 -43
  480. package/site/icons.mjs +605 -605
  481. package/site/images.mjs +157 -157
  482. package/site/motion.ts +13 -13
  483. package/site/noindex.mjs +21 -21
  484. package/site/pageColors.ts +110 -110
  485. package/site/popups.ts +98 -98
  486. package/site/schema.ts +189 -189
  487. package/site/sections.ts +3 -3
  488. package/site/seo.ts +28 -28
  489. package/site/site.ts +76 -76
  490. package/site/siteConfig.mjs +8 -8
  491. package/site/siteCsp.mjs +75 -75
  492. package/site/siteModules.mjs +27 -27
  493. package/site/standardPages.ts +59 -59
  494. package/site/storeTheme.d.mts +6 -0
  495. package/site/storeTheme.mjs +29 -0
  496. package/site/storeTheme.test.d.mts +1 -0
  497. package/site/storeTheme.test.mjs +53 -0
  498. package/site/testimonials.ts +48 -48
  499. package/site/text.ts +43 -43
  500. package/site/theme.mjs +483 -472
  501. package/site/tracking.client.ts +225 -225
  502. package/site/tracking.ts +9 -9
  503. package/src/ActionParts.tsx +205 -205
  504. package/src/Advanced.tsx +152 -152
  505. package/src/App.tsx +901 -904
  506. package/src/AppList.tsx +1362 -1345
  507. package/src/BlockDialog.tsx +360 -360
  508. package/src/BlockParts.tsx +652 -652
  509. package/src/BlockSettings.tsx +533 -533
  510. package/src/BrandForm.tsx +164 -164
  511. package/src/Colors.tsx +476 -476
  512. package/src/CommunicationParts.tsx +199 -199
  513. package/src/CompanyForm.tsx +209 -209
  514. package/src/Consents.tsx +175 -175
  515. package/src/ContainmentParts.tsx +185 -185
  516. package/src/Crop.tsx +152 -152
  517. package/src/DataForm.tsx +434 -434
  518. package/src/Deleted.tsx +177 -177
  519. package/src/EntryForm.tsx +740 -740
  520. package/src/FileView.tsx +30 -30
  521. package/src/FormFrame.tsx +237 -237
  522. package/src/Keywords.tsx +225 -225
  523. package/src/Library.tsx +1563 -1563
  524. package/src/Licenses.tsx +336 -336
  525. package/src/LinkSheet.tsx +138 -138
  526. package/src/MarkdownEditor.tsx +1075 -1075
  527. package/src/Media.tsx +677 -677
  528. package/src/NavigationParts.tsx +350 -350
  529. package/src/NavigationRestParts.tsx +247 -247
  530. package/src/Notifications.tsx +59 -59
  531. package/src/PagePicker.tsx +136 -136
  532. package/src/Parts.tsx +991 -991
  533. package/src/PartsShown.tsx +168 -168
  534. package/src/PickerParts.tsx +183 -183
  535. package/src/Redirects.tsx +450 -450
  536. package/src/Screen.tsx +184 -184
  537. package/src/SectionsField.tsx +65 -65
  538. package/src/SelectionParts.tsx +134 -134
  539. package/src/SeoScores.tsx +184 -184
  540. package/src/Sheet.tsx +35 -35
  541. package/src/ThemeStore.tsx +376 -376
  542. package/src/Tokens.tsx +346 -346
  543. package/src/Versions.tsx +214 -214
  544. package/src/addresses.ts +75 -75
  545. package/src/allIcons.ts +7 -7
  546. package/src/appFiles.ts +29 -29
  547. package/src/apps.ts +281 -281
  548. package/src/ask.tsx +123 -123
  549. package/src/blocks.ts +251 -251
  550. package/src/brand.ts +153 -153
  551. package/src/cache.ts +42 -42
  552. package/src/check.ts +285 -285
  553. package/src/checkSite.ts +256 -256
  554. package/src/{consents.ts → consentData.ts} +26 -26
  555. package/src/cookieList.ts +70 -70
  556. package/src/countries.ts +59 -59
  557. package/src/deploy.ts +155 -155
  558. package/src/editor.css +602 -602
  559. package/src/examples.ts +85 -85
  560. package/src/fields.tsx +942 -942
  561. package/src/frontmatter.mjs +42 -42
  562. package/src/github.ts +376 -376
  563. package/src/i18n/da-DK/advanced.ts +28 -28
  564. package/src/i18n/da-DK/app.ts +71 -71
  565. package/src/i18n/da-DK/apps.ts +90 -90
  566. package/src/i18n/da-DK/blockSettings.ts +38 -38
  567. package/src/i18n/da-DK/blocks.ts +408 -408
  568. package/src/i18n/da-DK/check.ts +25 -25
  569. package/src/i18n/da-DK/colors.ts +64 -64
  570. package/src/i18n/da-DK/common.ts +118 -118
  571. package/src/i18n/da-DK/company.ts +24 -24
  572. package/src/i18n/da-DK/consents.ts +23 -23
  573. package/src/i18n/da-DK/forms.ts +116 -116
  574. package/src/i18n/da-DK/history.ts +35 -35
  575. package/src/i18n/da-DK/index.ts +49 -49
  576. package/src/i18n/da-DK/keywords.ts +19 -19
  577. package/src/i18n/da-DK/library.ts +135 -135
  578. package/src/i18n/da-DK/licenses.ts +25 -25
  579. package/src/i18n/da-DK/media.ts +79 -79
  580. package/src/i18n/da-DK/parts.ts +696 -696
  581. package/src/i18n/da-DK/preview.ts +13 -13
  582. package/src/i18n/da-DK/redirects.ts +51 -51
  583. package/src/i18n/da-DK/seo.ts +20 -20
  584. package/src/i18n/da-DK/text.ts +35 -35
  585. package/src/i18n/da-DK/theme.ts +41 -41
  586. package/src/i18n/en-US/advanced.ts +32 -32
  587. package/src/i18n/en-US/app.ts +84 -84
  588. package/src/i18n/en-US/apps.ts +134 -134
  589. package/src/i18n/en-US/blockSettings.ts +48 -48
  590. package/src/i18n/en-US/blocks.ts +441 -441
  591. package/src/i18n/en-US/check.ts +31 -31
  592. package/src/i18n/en-US/colors.ts +82 -82
  593. package/src/i18n/en-US/common.ts +144 -144
  594. package/src/i18n/en-US/company.ts +25 -25
  595. package/src/i18n/en-US/consents.ts +25 -25
  596. package/src/i18n/en-US/forms.ts +145 -145
  597. package/src/i18n/en-US/history.ts +43 -43
  598. package/src/i18n/en-US/index.ts +51 -51
  599. package/src/i18n/en-US/keywords.ts +24 -24
  600. package/src/i18n/en-US/library.ts +176 -176
  601. package/src/i18n/en-US/licenses.ts +36 -36
  602. package/src/i18n/en-US/media.ts +80 -80
  603. package/src/i18n/en-US/parts.ts +718 -718
  604. package/src/i18n/en-US/preview.ts +15 -15
  605. package/src/i18n/en-US/redirects.ts +66 -66
  606. package/src/i18n/en-US/seo.ts +26 -26
  607. package/src/i18n/en-US/text.ts +41 -41
  608. package/src/i18n/en-US/theme.ts +41 -41
  609. package/src/i18n/index.ts +124 -124
  610. package/src/icon.tsx +160 -160
  611. package/src/iconPacks.ts +89 -89
  612. package/src/images.ts +163 -163
  613. package/src/{licenses.ts → licenseData.ts} +79 -79
  614. package/src/markdown.mjs +204 -204
  615. package/src/nav.ts +68 -68
  616. package/src/notFound.ts +28 -28
  617. package/src/{notifications.ts → notificationData.ts} +223 -223
  618. package/src/pagePreview.tsx +204 -204
  619. package/src/r2Caching.ts +55 -55
  620. package/src/{redirects.ts → redirectData.ts} +149 -149
  621. package/src/registry.tsx +188 -188
  622. package/src/schema.ts +417 -417
  623. package/src/sections.mjs +303 -303
  624. package/src/seo.ts +333 -333
  625. package/src/shared.ts +44 -44
  626. package/src/sidebar.ts +31 -31
  627. package/src/site.ts +317 -317
  628. package/src/standard/Block.astro +19 -19
  629. package/src/standard/BlockSite.astro +18 -18
  630. package/src/standard/accordion/index.tsx +47 -47
  631. package/src/standard/accordion/load.ts +23 -23
  632. package/src/standard/accordion/shared.ts +15 -15
  633. package/src/standard/accordion/view.ts +32 -32
  634. package/src/standard/buttons/fields.ts +98 -98
  635. package/src/standard/buttons/index.tsx +23 -23
  636. package/src/standard/buttons/load.ts +34 -34
  637. package/src/standard/buttons/shared.ts +12 -12
  638. package/src/standard/buttons/view.ts +30 -30
  639. package/src/standard/cards/index.tsx +71 -71
  640. package/src/standard/cards/load.ts +17 -17
  641. package/src/standard/cards/shared.ts +27 -27
  642. package/src/standard/cards/view.ts +29 -29
  643. package/src/standard/checklist/index.tsx +27 -27
  644. package/src/standard/checklist/load.ts +5 -5
  645. package/src/standard/checklist/view.ts +13 -13
  646. package/src/standard/cookies/index.tsx +17 -17
  647. package/src/standard/cookies/list.ts +56 -56
  648. package/src/standard/cookies/load.ts +11 -11
  649. package/src/standard/cookies/shared.ts +20 -20
  650. package/src/standard/cookies/view.ts +28 -28
  651. package/src/standard/faq/index.tsx +29 -29
  652. package/src/standard/faq/load.ts +14 -14
  653. package/src/standard/faq/view.ts +21 -21
  654. package/src/standard/head.ts +23 -23
  655. package/src/standard/hero/fields.ts +48 -48
  656. package/src/standard/hero/index.tsx +57 -57
  657. package/src/standard/hero/load.ts +23 -23
  658. package/src/standard/hero/view.ts +38 -38
  659. package/src/standard/links/index.tsx +41 -41
  660. package/src/standard/links/load.ts +17 -17
  661. package/src/standard/links/view.ts +25 -25
  662. package/src/standard/list/index.tsx +50 -50
  663. package/src/standard/list/load.ts +5 -5
  664. package/src/standard/list/view.ts +18 -18
  665. package/src/standard/load.ts +7 -7
  666. package/src/standard/notice/index.tsx +30 -30
  667. package/src/standard/notice/load.ts +16 -16
  668. package/src/standard/notice/view.ts +10 -10
  669. package/src/standard/points/index.tsx +42 -42
  670. package/src/standard/points/load.ts +5 -5
  671. package/src/standard/points/view.ts +12 -12
  672. package/src/standard/posts/index.tsx +132 -132
  673. package/src/standard/posts/load.ts +36 -36
  674. package/src/standard/posts/shared.ts +21 -21
  675. package/src/standard/posts/view.ts +63 -63
  676. package/src/standard/pricecards/index.tsx +57 -57
  677. package/src/standard/pricecards/load.ts +11 -11
  678. package/src/standard/pricecards/view.ts +50 -50
  679. package/src/standard/sharedTexts.ts +6 -6
  680. package/src/standard/site.ts +124 -124
  681. package/src/standard/siteLooks.ts +19 -19
  682. package/src/standard/sites.ts +13 -13
  683. package/src/standard/steps/index.tsx +28 -28
  684. package/src/standard/steps/load.ts +5 -5
  685. package/src/standard/steps/view.ts +11 -11
  686. package/src/standard/testimonials/index.tsx +87 -87
  687. package/src/standard/testimonials/list.ts +43 -43
  688. package/src/standard/testimonials/load.ts +58 -58
  689. package/src/standard/testimonials/shared.ts +13 -13
  690. package/src/standard/testimonials/view.ts +76 -76
  691. package/src/standard/themeBlocks.ts +13 -13
  692. package/src/standard/video/index.tsx +23 -23
  693. package/src/standard/video/load.ts +10 -10
  694. package/src/standard/video/view.ts +42 -42
  695. package/src/standard/view.tsx +105 -105
  696. package/src/standard/views.ts +37 -37
  697. package/src/store.ts +479 -499
  698. package/src/theme.ts +90 -90
  699. package/src/themeUi.ts +97 -97
  700. package/src/themes.ts +91 -91
  701. package/src/ui.tsx +3402 -3402
  702. package/src/virtual.d.ts +5 -5
  703. package/src/when.ts +22 -22
  704. package/starter/.githooks/pre-push +4 -4
  705. package/starter/.nvmrc +1 -1
  706. package/starter/.prettierignore +11 -11
  707. package/starter/.prettierrc.json +7 -7
  708. package/starter/README.md +27 -26
  709. package/starter/astro.config.mjs +5 -5
  710. package/starter/package.json +67 -67
  711. package/starter/public/_redirects +1 -1
  712. package/starter/public/favicon.svg +1 -1
  713. package/starter/public/logo.svg +1 -1
  714. package/starter/src/content/pages/blog.md +8 -8
  715. package/starter/src/content/pages/forside.md +9 -9
  716. package/starter/src/content/pages/kontakt.md +7 -7
  717. package/starter/src/content/pages/privatlivspolitik.md +9 -9
  718. package/starter/src/content/pages/siden-findes-ikke.md +7 -7
  719. package/starter/src/content.config.ts +2 -2
  720. package/starter/src/data/apps.json +1 -1
  721. package/starter/src/data/block-settings.json +3 -3
  722. package/starter/src/data/blocks.json +1 -1
  723. package/starter/src/data/booking.json +6 -6
  724. package/starter/src/data/colors.json +70 -70
  725. package/starter/src/data/contact.json +8 -8
  726. package/starter/src/data/cookies.json +21 -21
  727. package/starter/src/data/csp.json +1 -1
  728. package/starter/src/data/footer.json +15 -15
  729. package/starter/src/data/internal-links.json +4 -4
  730. package/starter/src/data/menu.json +4 -4
  731. package/starter/src/data/mobile-menu.json +4 -4
  732. package/starter/src/data/old-addresses.json +1 -1
  733. package/starter/src/data/redirects.json +1 -1
  734. package/starter/src/data/standard-pages.json +8 -8
  735. package/starter/src/data/theme.json +1 -1
  736. package/starter/src/env.d.ts +2 -2
  737. package/starter/src/site/config.ts +13 -13
  738. package/starter/src/site/worker.js +14 -14
  739. package/starter/tsconfig.json +17 -17
  740. package/starter/worker/apps.js +6 -6
  741. package/starter/wrangler.jsonc +39 -39
  742. package/theme/LICENSE +21 -21
  743. package/theme/blocks/ArrowLink.tsx +14 -14
  744. package/theme/blocks/BlockHead.tsx +26 -26
  745. package/theme/blocks/Panel.tsx +32 -32
  746. package/theme/blocks/Person.tsx +26 -26
  747. package/theme/blocks/accordion/PageRow.tsx +25 -25
  748. package/theme/blocks/accordion/View.tsx +17 -17
  749. package/theme/blocks/buttons/Buttons.tsx +28 -28
  750. package/theme/blocks/buttons/View.tsx +12 -12
  751. package/theme/blocks/cards/LinkCards.tsx +72 -72
  752. package/theme/blocks/cards/View.tsx +20 -20
  753. package/theme/blocks/checklist/Checks.tsx +15 -15
  754. package/theme/blocks/checklist/View.tsx +14 -14
  755. package/theme/blocks/cookies/View.tsx +36 -36
  756. package/theme/blocks/faq/Accordion.tsx +34 -34
  757. package/theme/blocks/faq/View.tsx +16 -16
  758. package/theme/blocks/faq/script.ts +56 -56
  759. package/theme/blocks/hero/View.tsx +44 -44
  760. package/theme/blocks/links/LinkGroups.tsx +24 -24
  761. package/theme/blocks/links/View.tsx +17 -17
  762. package/theme/blocks/list/Rows.tsx +24 -24
  763. package/theme/blocks/list/View.tsx +14 -14
  764. package/theme/blocks/notice/View.tsx +13 -13
  765. package/theme/blocks/points/PointList.tsx +34 -34
  766. package/theme/blocks/points/View.tsx +14 -14
  767. package/theme/blocks/posts/View.tsx +26 -26
  768. package/theme/blocks/pricecards/View.tsx +40 -40
  769. package/theme/blocks/steps/StepList.tsx +23 -23
  770. package/theme/blocks/steps/View.tsx +14 -14
  771. package/theme/blocks/testimonials/List.tsx +46 -46
  772. package/theme/blocks/testimonials/Slider.tsx +44 -44
  773. package/theme/blocks/testimonials/Testimonial.tsx +124 -124
  774. package/theme/blocks/testimonials/View.tsx +21 -21
  775. package/theme/blocks/testimonials/script.ts +52 -52
  776. package/theme/blocks/video/View.tsx +41 -41
  777. package/theme/components/ActionBar.astro +63 -63
  778. package/theme/components/AppSlot.astro +21 -21
  779. package/theme/components/ArrowLink.tsx +21 -21
  780. package/theme/components/BlogIndex.astro +48 -48
  781. package/theme/components/BlogPosts.astro +18 -18
  782. package/theme/components/BlogPosts.tsx +45 -45
  783. package/theme/components/BookButton.tsx +22 -22
  784. package/theme/components/Container.astro +11 -11
  785. package/theme/components/CookieConsent.astro +121 -121
  786. package/theme/components/EndBlock.astro +21 -21
  787. package/theme/components/FontPreload.astro +6 -6
  788. package/theme/components/Footer.astro +85 -85
  789. package/theme/components/Header.astro +186 -186
  790. package/theme/components/Icon.astro +16 -16
  791. package/theme/components/Img.astro +29 -29
  792. package/theme/components/Img.tsx +33 -33
  793. package/theme/components/Inline.astro +11 -11
  794. package/theme/components/Logo.astro +23 -23
  795. package/theme/components/LogoMark.astro +10 -10
  796. package/theme/components/Markdown.astro +17 -17
  797. package/theme/components/MobileMenu.tsx +224 -224
  798. package/theme/components/MobileNav.astro +33 -33
  799. package/theme/components/PageHeader.astro +22 -22
  800. package/theme/components/PageSection.astro +24 -24
  801. package/theme/components/PhoneButton.tsx +11 -11
  802. package/theme/components/Popup.astro +145 -145
  803. package/theme/components/PopupButton.tsx +24 -24
  804. package/theme/components/PopupView.tsx +48 -48
  805. package/theme/components/PostList.tsx +58 -58
  806. package/theme/components/PostMeta.tsx +17 -17
  807. package/theme/components/RichText.astro +28 -28
  808. package/theme/components/Section.astro +29 -29
  809. package/theme/components/SectionHeading.tsx +8 -8
  810. package/theme/components/TextSection.astro +16 -16
  811. package/theme/components/home/PageBlock.astro +19 -19
  812. package/theme/components/home/Text.astro +13 -13
  813. package/theme/components/loadPopup.ts +22 -22
  814. package/theme/components/loadPosts.ts +16 -16
  815. package/theme/components/posts.ts +15 -15
  816. package/theme/editor/looks.mjs +7 -7
  817. package/theme/editor/previews.tsx +56 -56
  818. package/theme/layouts/Base.astro +273 -273
  819. package/theme/layouts/PageLayout.astro +126 -126
  820. package/theme/layouts/PostLayout.astro +137 -137
  821. package/theme/scripts/motion.ts +29 -29
  822. package/theme/styles/base.css +320 -320
  823. package/theme/styles/blocks.css +1808 -1808
  824. package/theme/styles/components.css +206 -206
  825. package/theme/styles/global.css +1302 -1302
  826. package/theme/styles/ui.css +6544 -6544
  827. package/theme/templates/Blog.astro +38 -38
  828. package/theme/templates/Category.astro +19 -19
  829. package/theme/templates/Home.astro +36 -36
  830. package/theme/templates/New.astro +68 -68
  831. package/theme/templates/NotFound.astro +117 -117
  832. package/theme/theme.json +28 -28
  833. package/theme/ui/Badge.tsx +45 -45
  834. package/theme/ui/BottomSheet.tsx +148 -148
  835. package/theme/ui/Button.tsx +79 -79
  836. package/theme/ui/ButtonGroup.tsx +31 -31
  837. package/theme/ui/Card.tsx +45 -45
  838. package/theme/ui/Carousel.tsx +124 -124
  839. package/theme/ui/Checkbox.tsx +59 -59
  840. package/theme/ui/Chip.tsx +101 -101
  841. package/theme/ui/ChipSet.tsx +27 -27
  842. package/theme/ui/DatePicker.tsx +149 -149
  843. package/theme/ui/DatePickerContainer.tsx +316 -316
  844. package/theme/ui/Dialog.tsx +100 -100
  845. package/theme/ui/Divider.tsx +27 -27
  846. package/theme/ui/ExposedDropdownMenu.tsx +77 -77
  847. package/theme/ui/Fab.tsx +88 -88
  848. package/theme/ui/FabMenu.tsx +67 -67
  849. package/theme/ui/IconButton.tsx +78 -78
  850. package/theme/ui/List.tsx +287 -287
  851. package/theme/ui/LoadingIndicator.tsx +29 -29
  852. package/theme/ui/Menu.tsx +278 -278
  853. package/theme/ui/NavigationBar.tsx +62 -62
  854. package/theme/ui/NavigationDrawer.tsx +80 -80
  855. package/theme/ui/NavigationRail.tsx +154 -154
  856. package/theme/ui/ProgressIndicator.tsx +162 -162
  857. package/theme/ui/RadioButton.tsx +50 -50
  858. package/theme/ui/Search.tsx +129 -129
  859. package/theme/ui/SegmentedButton.tsx +49 -49
  860. package/theme/ui/SideSheet.tsx +54 -54
  861. package/theme/ui/Slider.tsx +155 -155
  862. package/theme/ui/Snackbar.tsx +67 -67
  863. package/theme/ui/SplitButton.tsx +67 -67
  864. package/theme/ui/Switch.tsx +60 -60
  865. package/theme/ui/Tabs.tsx +114 -114
  866. package/theme/ui/TextField.tsx +278 -278
  867. package/theme/ui/TimePicker.tsx +128 -128
  868. package/theme/ui/TimePickerContainer.tsx +234 -234
  869. package/theme/ui/Toolbar.tsx +67 -67
  870. package/theme/ui/Tooltip.tsx +58 -58
  871. package/theme/ui/TopAppBar.tsx +77 -77
  872. package/theme/ui/bottomSheet.script.ts +8 -8
  873. package/theme/ui/{bottomSheet.ts → bottomSheetControl.ts} +211 -211
  874. package/theme/ui/carousel.script.ts +28 -28
  875. package/theme/ui/carouselLayout.ts +229 -229
  876. package/theme/ui/checkbox.script.ts +8 -8
  877. package/theme/ui/datePicker.script.ts +302 -302
  878. package/theme/ui/dates.ts +185 -185
  879. package/theme/ui/{dialog.ts → dialogControl.ts} +48 -48
  880. package/theme/ui/dropdownMenu.script.ts +166 -166
  881. package/theme/ui/fabMenu.script.ts +57 -57
  882. package/theme/ui/navigationRail.script.ts +47 -47
  883. package/theme/ui/ripple.script.ts +159 -159
  884. package/theme/ui/search.script.ts +92 -92
  885. package/theme/ui/{sideSheet.ts → sideSheetControl.ts} +48 -48
  886. package/theme/ui/slider.script.ts +12 -12
  887. package/theme/ui/sliderUpdate.ts +52 -52
  888. package/theme/ui/snackbar.script.ts +16 -16
  889. package/theme/ui/splitButton.script.ts +63 -63
  890. package/theme/ui/tabs.script.ts +48 -48
  891. package/theme/ui/textField.script.ts +17 -17
  892. package/theme/ui/textFieldUpdate.ts +44 -44
  893. package/theme/ui/timePicker.script.ts +248 -248
  894. package/theme/ui/toolbar.script.ts +43 -43
  895. package/theme/ui/tooltip.script.ts +68 -68
  896. package/worker/consent.js +141 -141
  897. package/worker/editor-github.js +167 -167
  898. package/worker/editor-host.js +74 -74
  899. package/worker/files.js +401 -401
  900. package/worker/github.js +36 -36
  901. package/worker/index.js +188 -184
  902. package/worker/not-found.js +97 -97
  903. package/worker/oauth.js +95 -95
  904. package/worker/publish.js +52 -52
  905. package/worker/push.js +270 -270
  906. package/worker/r2-status.js +50 -50
  907. package/worker/session.js +58 -58
  908. package/worker/store.js +46 -0
package/site/theme.mjs CHANGED
@@ -1,472 +1,483 @@
1
- // The site's theme: how it looks (src/themes/<theme>/, with its theme.json). Which one is in use is data,
2
- // src/data/theme.json (`{ "theme": "<folder>" }`), changed with the swap button on "Tema" in the editor.
3
- // Every theme builds on the standard theme (packages/editor/theme/): what a theme does not have itself, a file of the
4
- // theme or the look of a block, is the standard theme's, styled by the theme in use's CSS. The site and the editor
5
- // reach the theme as @theme (themePlugin: the theme in use's file, else the standard theme's), and the site's own
6
- // parts (src/site/) as @site (aliases): for packages/editor/astro.mjs and packages/editor/scripts/check-content.mjs. Stops with what is
7
- // wrong, when the theme named is not installed.
8
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
9
- import { relative } from 'node:path';
10
- import { fileURLToPath, pathToFileURL } from 'node:url';
11
- import { svgParts } from '@morphemecms/cms/lib/svg.mjs';
12
- import { declarationProblems, ownerId } from '@morphemecms/cms/lib/licenses.mjs';
13
- import { installedPacks, isIconName, packFolder, packProblems, phosphorFile, svgProblems, usedIcons } from './icons.mjs';
14
- import { ICON_PACK_FILE, ICON_PACK_LICENSE, SYSTEM_PACKS, THEME_PACKS_FOLDER, packInUse, packTheme, themePackId } from './iconPacks.mjs';
15
- import { MOTION_SCHEMES } from '@morphemecms/cms/lib/motion-schemes.mjs';
16
-
17
- /** The site's choice of icon pack, `{ "pack": "<id>" }` (config.icons in src/site/config.ts). */
18
- const ICONS_FILE = 'src/data/icons.json';
19
-
20
- // The site's root (the working directory) and this package's.
21
- const root = pathToFileURL(`${process.cwd()}/`);
22
- const pkg = new URL('../', import.meta.url);
23
-
24
- /** The theme every theme builds on, Material (this package's theme/, id 'saturn'): it has all of THEME_FILES and the look of every
25
- * standard block. */
26
- export const STANDARD_THEME = 'saturn';
27
-
28
- /** A theme's folder, from the site's root: the standard theme's is this package's theme/, another's src/themes/<theme>. */
29
- export const themeFolder = (theme) =>
30
- theme === STANDARD_THEME ? relative(process.cwd(), fileURLToPath(new URL('theme', pkg))) : `src/themes/${theme}`;
31
-
32
- // What the standard theme has, as its folder in src/themes/: what the site's pages (packages/editor/pages/) and the editor
33
- // (src/site/) take from it as @theme. Besides these, the look of its blocks (blocks/<type>/) and what they import.
34
- // Another theme needs only theme.json and styles/global.css (OWN_FILES); the rest it may have, else it is the
35
- // standard theme's.
36
- export const THEME_FILES = [
37
- 'theme.json',
38
- 'styles/global.css',
39
- 'layouts/PageLayout.astro',
40
- 'layouts/PostLayout.astro',
41
- ...['Home', 'Blog', 'Category', 'New', 'NotFound'].map((t) => `templates/${t}.astro`),
42
- 'editor/previews.tsx',
43
- 'editor/looks.mjs',
44
- ];
45
-
46
- /** The themes installed: the standard theme and the folders in src/themes/ with a theme.json. */
47
- export const installedThemes = () => [
48
- STANDARD_THEME,
49
- ...(existsSync(new URL('src/themes/', root)) ? readdirSync(new URL('src/themes/', root), { withFileTypes: true }) : [])
50
- .filter((e) => e.isDirectory() && existsSync(new URL(`src/themes/${e.name}/theme.json`, root)))
51
- .map((e) => e.name),
52
- ];
53
-
54
- /** What every theme has itself. */
55
- export const OWN_FILES = ['theme.json', 'styles/global.css'];
56
-
57
- // A block's contract (its type, fields, rules, shared texts, list and what it shows), which is the editor's or an
58
- // app's, never a theme's: a theme has only the look of the standard blocks (blocks/<type>/: View.tsx, components).
59
- const CONTRACT_FILES = ['index.tsx', 'shared.ts', 'texts.ts', 'list.ts', 'fields.ts', 'view.ts', 'load.ts'];
60
-
61
- // How a block looked before it was drawn from its view, once on the site and once in the editor: now View.tsx.
62
- const OLD_LOOK = ['Site.astro', 'draw.tsx'];
63
-
64
- // An import of the UI components (ui.css) or of the standard theme's CSS that has them (base.css, global.css); the
65
- // standard theme's own global.css imports ./base.css.
66
- const UI_IMPORT = /@import\s+['"](?:@morphemecms\/cms\/theme\/styles\/|\.\/)(?:ui|base|global)\.css['"]/;
67
-
68
- const folders = (url) =>
69
- existsSync(url)
70
- ? readdirSync(url, { withFileTypes: true })
71
- .filter((e) => e.isDirectory())
72
- .map((e) => e.name)
73
- : [];
74
- const has = (path) => existsSync(new URL(path, root));
75
-
76
- /** The standard blocks' types: their contracts' folders in the editor (packages/editor/src/standard/<type>/). */
77
- const standardTypes = () => folders(new URL('src/standard/', pkg)).filter((t) => existsSync(new URL(`src/standard/${t}/index.tsx`, pkg)));
78
-
79
- /** What is wrong with a theme's own icons (icons/<name>.svg, drawn by Svg in packages/editor/src/icon.tsx, in place of
80
- * its icon pack's): each is one <svg> with a viewBox, with only what draws in it (svgProblems), by a name the site
81
- * draws. */
82
- function iconProblems(theme) {
83
- const dir = `${themeFolder(theme)}/icons`;
84
- if (!has(dir)) return [];
85
- const problems = [];
86
- for (const entry of readdirSync(new URL(dir, root), { withFileTypes: true })) {
87
- const file = `${dir}/${entry.name}`;
88
- const name = entry.name.replace(/\.svg$/, '');
89
- if (!entry.isFile() || !entry.name.endsWith('.svg')) {
90
- problems.push(`${file}: et temas ikon er en SVG-fil, ${dir}/<navn>.svg.`);
91
- continue;
92
- }
93
- if (!isIconName(name))
94
- problems.push(`${file}: sitet tegner intet ikon, der hedder "${name}". Et ikon hedder som i Phosphor (fx map-pin, play-fill).`);
95
- problems.push(...svgProblems(file, readFileSync(new URL(file, root), 'utf8')));
96
- }
97
- return problems;
98
- }
99
-
100
- const manifestOf = (theme) => JSON.parse(readFileSync(new URL(`${themeFolder(theme)}/theme.json`, root), 'utf8'));
101
-
102
- /** The icon packs a theme brings (src/themes/<theme>/icon-packs/<pack>/, with its pack.json), by their folders. */
103
- const ownPacks = (theme) =>
104
- folders(new URL(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/`, root)).filter((p) =>
105
- has(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/${p}/${ICON_PACK_FILE}`),
106
- );
107
-
108
- /** The choice of icon pack made under "Indstillinger" › "Udseende" › "Ikonpakker" (src/data/icons.json, `{ "pack": "<id>"
109
- * }`), or undefined while none is made (then the theme's is used). */
110
- export function chosenPack() {
111
- const file = new URL(ICONS_FILE, root);
112
- if (!existsSync(file)) return undefined;
113
- const { pack } = JSON.parse(readFileSync(file, 'utf8'));
114
- return pack ?? undefined;
115
- }
116
-
117
- /** The icon pack the site and the editor draw their icons with, by its id (packInUse in packages/editor/site/iconPacks.mjs): the one
118
- * chosen, else the theme's ("icons" in its theme.json), else the standard theme's (Material Symbols). Stops with what is
119
- * wrong when it is not installed. */
120
- export function iconPack(theme = activeTheme()) {
121
- const installed = installedPacks();
122
- const chosen = chosenPack();
123
- const id = packInUse({ chosen, theme, icons: manifestOf(theme).icons, standardIcons: manifestOf(STANDARD_THEME).icons, installed });
124
- if (!installed.includes(id))
125
- throw new Error(
126
- chosen === id
127
- ? `${ICONS_FILE}: ikonpakken ${JSON.stringify(id)} er ikke installeret (${packFolder(id)}/${ICON_PACK_FILE}).`
128
- : `${themeFolder(theme)}/theme.json: "icons" er ikonpakken ${JSON.stringify(id)}, som ikke er installeret.`,
129
- );
130
- return id;
131
- }
132
-
133
- /** The icon packs there are with their licences, from their manifests, for "Licenser" under "Indstillinger" (IconPack
134
- * in @morphemecms/cms/lib/licenses.mjs, licensesIntegration in packages/editor/astro.mjs): each pack's id, name, version, who made it,
135
- * its licence's name and file (LICENSE in its folder), the theme it came with, whether the site draws with it, and each
136
- * set it takes icons from besides its own (`includes`, with its `licenseFile`). packProblems checks the manifests. */
137
- export function iconPackLicenses() {
138
- const inUse = iconPack();
139
- return installedPacks().map((id) => {
140
- const dir = packFolder(id);
141
- const manifest = JSON.parse(readFileSync(new URL(`${dir}/${ICON_PACK_FILE}`, root), 'utf8'));
142
- const set = (x, file) => ({ name: x.name, version: x.version ?? null, author: x.author, license: x.license, file: `${dir}/${file}` });
143
- return {
144
- id,
145
- ...set(manifest, ICON_PACK_LICENSE),
146
- ...(packTheme(id) ? { theme: packTheme(id) } : {}),
147
- inUse: id === inUse,
148
- includes: (manifest.includes ?? []).map((x) => set(x, x.licenseFile)),
149
- };
150
- });
151
- }
152
-
153
- /** The folders a theme's icons come from, first the one that wins: its own icons/, then the standard theme's (it has
154
- * none), then the icon pack in use's (iconPack). */
155
- const iconFolders = (theme) =>
156
- [...new Set([theme, STANDARD_THEME])].map((t) => `${themeFolder(t)}/icons/`).concat(`${packFolder(iconPack(theme))}/`);
157
-
158
- /** Whether an icon's file is Phosphor's own (as its package has it, as Phosphor's pack has it): then it needs no
159
- * putting in Phosphor's place. */
160
- const isPhosphors = (name, svg) => svg.trim() === phosphorFile(name)?.trim();
161
-
162
- /** The theme in use's icons as their files' text, by name: its own (icons/<name>.svg), else its icon pack's (iconPack);
163
- * not those that are Phosphor's own, which the site and the editor draw as they are (all, with Phosphor's pack). */
164
- function themeIcons() {
165
- const icons = {};
166
- for (const dir of iconFolders(activeTheme()).reverse())
167
- if (has(dir))
168
- for (const f of readdirSync(new URL(dir, root)))
169
- if (f.endsWith('.svg')) icons[f.slice(0, -'.svg'.length)] = readFileSync(new URL(dir + f, root), 'utf8');
170
- for (const [name, svg] of Object.entries(icons)) if (isPhosphors(name, svg)) delete icons[name];
171
- return icons;
172
- }
173
-
174
- /** An icon of a theme (the one in use, if none is given) as its file's text: its own (icons/<name>.svg), else its icon
175
- * pack's; null when it is Phosphor's own, or neither has it (then it is Phosphor's). For what is drawn outside Vite: the
176
- * play button of a video in the text (rehypeVideo, packages/editor/astro.mjs). In Vite, themePlugin puts them in Phosphor's place. */
177
- export function themeIcon(name, theme = activeTheme()) {
178
- for (const dir of iconFolders(theme))
179
- if (has(`${dir}${name}.svg`)) {
180
- const svg = readFileSync(new URL(`${dir}${name}.svg`, root), 'utf8');
181
- return isPhosphors(name, svg) ? null : svg;
182
- }
183
- return null;
184
- }
185
-
186
- /** The site's choice of motion scheme, `{ "scheme": "<scheme>" }` (config.motion in src/site/config.ts). */
187
- const MOTION_FILE = 'src/data/motion.json';
188
-
189
- /** What is wrong with the choice of motion scheme made under "Udseende" › "Bevægelse" (src/data/motion.json): one that
190
- * is not a scheme (lib/motion.ts). */
191
- export function motionProblems() {
192
- const file = new URL(MOTION_FILE, root);
193
- if (!existsSync(file)) return [];
194
- try {
195
- const { scheme } = JSON.parse(readFileSync(file, 'utf8'));
196
- if (MOTION_SCHEMES.includes(scheme)) return [];
197
- return [`${MOTION_FILE}: bevægelsen ${JSON.stringify(scheme ?? null)} findes ikke. Vælg en under Udseende › Bevægelse.`];
198
- } catch (e) {
199
- return [`${MOTION_FILE}: ${e.message}`];
200
- }
201
- }
202
-
203
- /** What is wrong with the system's icon packs (packProblems; a theme's own are its themeProblems) and the choice of one:
204
- * src/data/icons.json naming one that is not there. */
205
- export function iconPackProblems() {
206
- const used = usedIcons();
207
- const installed = installedPacks();
208
- const problems = SYSTEM_PACKS.filter((id) => installed.includes(id)).flatMap((id) => packProblems(id, used));
209
- for (const id of SYSTEM_PACKS)
210
- if (!installed.includes(id)) problems.push(`${packFolder(id)}/${ICON_PACK_FILE} mangler: kør npm run icons.`);
211
- try {
212
- const chosen = chosenPack();
213
- if (chosen !== undefined && (typeof chosen !== 'string' || !installed.includes(chosen)))
214
- problems.push(`${ICONS_FILE}: ikonpakken ${JSON.stringify(chosen)} er ikke installeret. Vælg en anden under Udseende › Ikonpakker.`);
215
- } catch (e) {
216
- problems.push(`${ICONS_FILE}: ${e.message}`);
217
- }
218
- return problems;
219
- }
220
-
221
- // Phosphor, as the site and the editor import it: its React icons (`import { CaretRight } from '@phosphor-icons/react'`)
222
- // and its SVG files as text (`@phosphor-icons/core/assets/regular/caret-right.svg?raw`, `…/fill/play-fill.svg?raw`, also
223
- // in the import.meta.glob of Svg, packages/editor/src/icon.tsx). themePlugin puts the theme's icons in their place.
224
- const PHOSPHOR_REACT = '@phosphor-icons/react';
225
- const PHOSPHOR_FILE = /@phosphor-icons\/core\/assets\/(?:regular|fill)\/([a-z0-9-]+)\.svg\?raw$/;
226
- const REACT_ICONS = '\0site-theme-icons';
227
- const FILE_ICON = '\0site-theme-icon:';
228
-
229
- /** The React component's name of a Phosphor icon by its file's name (`caret-right` → CaretRight), when Phosphor has it. */
230
- const reactName = (name) => {
231
- const pascal = name.replace(/(^|-)([a-z0-9])/g, (_, __, c) => c.toUpperCase());
232
- return has(`node_modules/@phosphor-icons/react/dist/csr/${pascal}.es.js`) ? pascal : null;
233
- };
234
-
235
- /** `@phosphor-icons/react` with the theme's icons in place of Phosphor's, the rest Phosphor's: each one the theme has
236
- * draws its SVG file in the file's viewBox, with the props of a Phosphor icon (size, color, weight, mirrored, alt, the
237
- * svg's own; from IconContext as Phosphor's), through Phosphor's IconBase; `weight="fill"` its `<name>-fill`, if the
238
- * theme has one, else the same as the others. */
239
- function reactIcons(icons) {
240
- const lines = [
241
- "import { createElement, forwardRef, useContext } from 'react';",
242
- `import { IconBase, IconContext } from '${PHOSPHOR_REACT}';`,
243
- `export * from '${PHOSPHOR_REACT}';`,
244
- 'const draw = (regular, fill) =>',
245
- ' forwardRef((props, ref) => {',
246
- ' const context = useContext(IconContext);',
247
- " const weight = props.weight ?? context.weight ?? 'regular';",
248
- ' const color = props.color ?? context.color;',
249
- " const [viewBox, inner] = weight === 'fill' && fill ? fill : regular;",
250
- // A colour of its own also for lines (a theme's own icon drawn with stroke="currentColor"), not only for what is filled.
251
- " const drawn = createElement('g', { color: color === 'currentColor' ? undefined : color, dangerouslySetInnerHTML: { __html: inner } });",
252
- ' return createElement(IconBase, { ...props, ref, viewBox, weights: new Map([[weight, drawn]]) });',
253
- ' });',
254
- ];
255
- const parts = (name) => {
256
- const svg = svgParts(icons[name]);
257
- return svg && JSON.stringify([svg.viewBox, svg.inner]);
258
- };
259
- for (const name of Object.keys(icons)) {
260
- const component = reactName(name);
261
- if (!component || !parts(name)) continue;
262
- lines.push(
263
- `export const ${component}Icon = /* @__PURE__ */ draw(${parts(name)}, ${parts(`${name}-fill`) ?? 'null'});`,
264
- `export { ${component}Icon as ${component} };`,
265
- );
266
- }
267
- return lines.join('\n');
268
- }
269
-
270
- /** An icon's SVG file as Phosphor's are (in the text's colour, `fill="currentColor"`), for an import of Phosphor's. */
271
- const fileIcon = (svg) => {
272
- const { viewBox, inner } = svgParts(svg);
273
- const box = viewBox ? ` viewBox="${viewBox}"` : '';
274
- return `export default ${JSON.stringify(`<svg xmlns="http://www.w3.org/2000/svg"${box} fill="currentColor">${inner}</svg>`)};`;
275
- };
276
-
277
- /** What a theme is missing (the standard theme: of THEME_FILES and the look of every standard block, another: of
278
- * OWN_FILES), what is wrong in its theme.json (name, version, author, icon), a block of its own (a folder in its
279
- * blocks/ that is not the look of a standard block, or with a block's contract in it), a part nothing uses (in a
280
- * folder without its View.tsx, a file the standard theme does not have there), and what is wrong with its own icons
281
- * (iconProblems). */
282
- export function themeProblems(theme) {
283
- const needed = theme === STANDARD_THEME ? THEME_FILES : OWN_FILES;
284
- const problems = needed.filter((f) => !has(`${themeFolder(theme)}/${f}`)).map((f) => `${themeFolder(theme)}/${f} mangler.`);
285
- const standard = new Set(standardTypes());
286
- if (theme === STANDARD_THEME)
287
- for (const type of standard)
288
- if (!has(`${themeFolder(theme)}/blocks/${type}/View.tsx`)) problems.push(`${themeFolder(theme)}/blocks/${type}/View.tsx mangler.`);
289
- // Every theme draws the UI components (styles/ui.css: the buttons and icon buttons): it imports them, itself or
290
- // through the standard theme's base.css or global.css.
291
- const css = `${themeFolder(theme)}/styles/global.css`;
292
- if (has(css) && !UI_IMPORT.test(readFileSync(new URL(css, root), 'utf8')))
293
- problems.push(
294
- `${css}: importerer ikke temaets UI-dele (knapperne): @import '@morphemecms/cms/theme/styles/ui.css', eller standardtemaets base.css eller global.css, der gør det.`,
295
- );
296
- for (const type of folders(new URL(`${themeFolder(theme)}/blocks/`, root))) {
297
- const dir = `${themeFolder(theme)}/blocks/${type}`;
298
- if (!standard.has(type)) problems.push(`${dir}: et tema kan kun have udseendet af en standardblok, og "${type}" er ikke en.`);
299
- for (const f of CONTRACT_FILES)
300
- if (has(`${dir}/${f}`)) problems.push(`${dir}/${f}: en bloks kontrakt hører til editoren eller en app, ikke temaet.`);
301
- // Without a View.tsx of its own, the standard theme's draws the block, and each file replaces one of its parts
302
- // (faq/Accordion.tsx), which it imports as @theme/…; else nothing would use it. The standard theme draws them all.
303
- if (theme === STANDARD_THEME || has(`${dir}/View.tsx`)) {
304
- if (!has(`${dir}/View.tsx`)) problems.push(`${dir}/View.tsx mangler: den tegner blokken på sitet og i editoren.`);
305
- } else
306
- for (const entry of readdirSync(new URL(dir, root), { withFileTypes: true }))
307
- if (
308
- entry.name !== 'script.ts' &&
309
- !CONTRACT_FILES.includes(entry.name) &&
310
- !has(`${themeFolder(STANDARD_THEME)}/blocks/${type}/${entry.name}`)
311
- )
312
- problems.push(
313
- `${dir}/${entry.name}: standardtemaet har ingen del med det navn, så intet bruger den. Giv den samme navn som den del, den erstatter, eller tegn blokken med en View.tsx.`,
314
- );
315
- for (const f of OLD_LOOK)
316
- if (has(`${dir}/${f}`)) problems.push(`${dir}/${f}: blokken tegnes af View.tsx, både på sitet og i editoren.`);
317
- }
318
- try {
319
- const manifest = JSON.parse(readFileSync(new URL(`${themeFolder(theme)}/theme.json`, root), 'utf8'));
320
- for (const key of ['name', 'version', 'author', 'icon'])
321
- if (typeof manifest[key] !== 'string' || !manifest[key]) problems.push(`${themeFolder(theme)}/theme.json: "${key}" mangler.`);
322
- const { description, highlights, releaseNotes } = manifest;
323
- const byLanguage = (x, ok) => x && typeof x === 'object' && !Array.isArray(x) && Object.values(x).length && Object.values(x).every(ok);
324
- if (description !== undefined && !byLanguage(description, (x) => typeof x === 'string' && x))
325
- problems.push(`${themeFolder(theme)}/theme.json: "description" skal være teksten på hvert sprog, fx { "da-DK": "…" }.`);
326
- if (
327
- highlights !== undefined &&
328
- !byLanguage(highlights, (x) => Array.isArray(x) && x.length && x.every((y) => typeof y === 'string' && y))
329
- )
330
- problems.push(`${themeFolder(theme)}/theme.json: "highlights" skal være punkterne på hvert sprog, fx { "da-DK": ["…"] }.`);
331
- if (releaseNotes !== undefined && !byLanguage(releaseNotes, (x) => byLanguage(x, (y) => typeof y === 'string' && y)))
332
- problems.push(
333
- `${themeFolder(theme)}/theme.json: "releaseNotes" skal være det nye i hver version på hvert sprog, fx { "da-DK": { "1.1.0": "…" } }.`,
334
- );
335
- if ('store' in manifest && (manifest.store !== true || theme === STANDARD_THEME))
336
- problems.push(
337
- `${themeFolder(theme)}/theme.json: "store" kan kun være true (temaet står i Appstore, npm run store:release), og standardtemaet har det ikke.`,
338
- );
339
- // The icon pack it uses by default: a system pack, or one of its own by its folder; the standard theme's a system pack.
340
- const own = ownPacks(theme);
341
- if (
342
- (manifest.icons !== undefined || theme === STANDARD_THEME) &&
343
- !(SYSTEM_PACKS.includes(manifest.icons) || (theme !== STANDARD_THEME && own.includes(manifest.icons)))
344
- )
345
- problems.push(
346
- `${themeFolder(theme)}/theme.json: "icons" er den ikonpakke, temaet bruger, når ingen er valgt: ${[...SYSTEM_PACKS, ...own].map((p) => `"${p}"`).join(', ')}.`,
347
- );
348
- // How it moves while the owner has chosen none (lib/motion.ts); without it, the standard theme's.
349
- if (manifest.motion !== undefined && !MOTION_SCHEMES.includes(manifest.motion))
350
- problems.push(
351
- `${themeFolder(theme)}/theme.json: "motion" er den bevægelse, temaet bruger, når ingen er valgt: ${MOTION_SCHEMES.map((s) => `"${s}"`).join(', ')}.`,
352
- );
353
- // Its own licence and the licences of what it brings (fonts, code, pictures), for "Licenser" and the store.
354
- problems.push(
355
- ...declarationProblems({
356
- license: manifest.license,
357
- thirdParty: manifest.thirdParty,
358
- folder: fileURLToPath(new URL(`${themeFolder(theme)}/`, root)),
359
- where: `${themeFolder(theme)}/theme.json`,
360
- what: `temaet ${manifest.name || theme}`,
361
- owner: ownerId('theme', theme),
362
- root: fileURLToPath(root),
363
- }),
364
- );
365
- } catch (e) {
366
- problems.push(`${themeFolder(theme)}/theme.json: ${e.message}`);
367
- }
368
- // The icon packs it brings: each as every pack must be, and not by a system pack's name.
369
- const packs = ownPacks(theme);
370
- for (const p of packs)
371
- if (SYSTEM_PACKS.includes(p))
372
- problems.push(
373
- `${themeFolder(theme)}/${THEME_PACKS_FOLDER}/${p}: en ikonpakke i et tema hedder ikke som systemets (${SYSTEM_PACKS.join(', ')}).`,
374
- );
375
- if (theme === STANDARD_THEME && packs.length)
376
- problems.push(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}: standardtemaets ikonpakker er systemets, i CMS'ets icons/.`);
377
- for (const entry of has(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}`)
378
- ? readdirSync(new URL(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/`, root))
379
- : [])
380
- if (!packs.includes(entry))
381
- problems.push(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/${entry}: en ikonpakke er en mappe med sin ${ICON_PACK_FILE}.`);
382
- const used = usedIcons();
383
- return [...problems, ...iconProblems(theme), ...packs.flatMap((p) => packProblems(themePackId(theme, p), used))];
384
- }
385
-
386
- // A site's own parts (src/site/<file>), else the package's standard ones (defaults/<file>): `@site/<file>`.
387
- const SITE_DIRS = [fileURLToPath(new URL('src/site/', root)), fileURLToPath(new URL('defaults/', pkg))];
388
-
389
- /** The file `@site/<file>` is: the site's src/site/<file>, else the package's defaults/<file>, with or without its
390
- * extension (`@site/editor`, as TypeScript writes it), or null. */
391
- export function siteFile(file) {
392
- for (const dir of SITE_DIRS)
393
- for (const ext of ['', '.ts', '.tsx', '.mjs', '.js'])
394
- if (statSync(dir + file + ext, { throwIfNoEntry: false })?.isFile()) return dir + file + ext;
395
- return null;
396
- }
397
-
398
- /** The folder of the theme in use, under src/themes/. */
399
- export function activeTheme() {
400
- const { theme } = JSON.parse(readFileSync(new URL('src/data/theme.json', root), 'utf8'));
401
- if (typeof theme !== 'string' || !/^[a-z0-9-]+$/.test(theme) || !existsSync(new URL(`${themeFolder(theme)}/theme.json`, root)))
402
- throw new Error(`src/data/theme.json: temaet ${JSON.stringify(theme)} er ikke installeret (src/themes/<tema>/theme.json).`);
403
- return theme;
404
- }
405
-
406
- /** @data (the site's data, src/data/), @saturn (the standard theme, this package's theme/) and @icons (the system's icon
407
- * packs), as Vite's resolve.alias. @site is themePlugin's (siteFile). */
408
- export const aliases = () => ({
409
- '@data': fileURLToPath(new URL('src/data', root)),
410
- '@saturn': fileURLToPath(new URL('theme', pkg)),
411
- '@icons': fileURLToPath(new URL('icons', pkg)),
412
- });
413
-
414
- /** @theme and @site as a Vite plugin: `@site/<file>` is the site's own part, else the package's standard one
415
- * (siteFile), and `@theme/<file>` is the theme in use's file, else the standard theme's. A glob
416
- * (`import.meta.glob('@theme/blocks/*\/View.tsx')`) is the theme in use's; whoever globs puts the standard
417
- * theme's under it (packages/editor/src/standard/siteLooks.ts, packages/editor/src/standard/themeBlocks.ts).
418
- * And the theme's icons (themeIcons: its own, else the icon pack in use's, iconPack) in Phosphor's place, on the site
419
- * and in the editor alike: an import of `@phosphor-icons/react` is Phosphor's with the theme's icons in place of its own
420
- * (reactIcons), and an SVG file of Phosphor's as text (`?raw`) the theme's, when it has one. An icon neither has is
421
- * Phosphor's (check:content stops at a pack without one the site or the editor draws: packProblems). With Phosphor's
422
- * pack and no icons of its own nothing is changed. The pack is read when the site is built, so a new choice
423
- * (src/data/icons.json) or theme is in the site and the editor after the next build. */
424
- export function themePlugin() {
425
- const themes = [activeTheme(), STANDARD_THEME].map((t) => fileURLToPath(new URL(`${themeFolder(t)}/`, root)));
426
- const icons = themeIcons();
427
- const own = Object.keys(icons).length > 0;
428
- return {
429
- name: 'site-theme',
430
- enforce: 'pre',
431
- // Phosphor's package bundled for the browser, also when the first import of it is the theme's icons'.
432
- config: () => (own ? { optimizeDeps: { include: [PHOSPHOR_REACT] } } : {}),
433
- resolveId(source, importer) {
434
- // The theme's icons import Phosphor's own.
435
- if (source === PHOSPHOR_REACT) return own && importer !== REACT_ICONS ? REACT_ICONS : null;
436
- const icon = source.match(PHOSPHOR_FILE)?.[1];
437
- if (icon) return svgParts(icons[icon]) ? FILE_ICON + icon : null;
438
- if (source.startsWith('@site/')) return siteFile(source.slice('@site/'.length));
439
- if (!source.startsWith('@theme/')) return null;
440
- const file = source.slice('@theme/'.length);
441
- if (file.includes('*')) return themes[0] + file;
442
- // A file without its extension (`@theme/editor/previews`), as TypeScript writes it.
443
- for (const dir of themes)
444
- for (const ext of ['', '.ts', '.tsx', '.mjs', '.js'])
445
- if (statSync(dir + file + ext, { throwIfNoEntry: false })?.isFile()) return dir + file + ext;
446
- return null;
447
- },
448
- load(id) {
449
- if (id === REACT_ICONS) return reactIcons(icons);
450
- if (id.startsWith(FILE_ICON)) return fileIcon(icons[id.slice(FILE_ICON.length)]);
451
- return null;
452
- },
453
- };
454
- }
455
-
456
- /** What a block is missing to be drawn from its view (packages/editor/src/standard/view.tsx): a standard block's
457
- * contract its view.ts and load.ts, an app's block (src/apps/<app>/blocks/<type>/) those and its View.tsx; and the
458
- * files of how a block looked before (Site.astro, draw.tsx), which nothing uses now. */
459
- export function blockProblems() {
460
- const problems = [];
461
- for (const type of standardTypes())
462
- for (const f of ['view.ts', 'load.ts'])
463
- if (!existsSync(new URL(`src/standard/${type}/${f}`, pkg))) problems.push(`packages/editor/src/standard/${type}/${f} mangler.`);
464
- for (const app of folders(new URL('src/apps/', root)))
465
- for (const type of folders(new URL(`src/apps/${app}/blocks/`, root))) {
466
- const dir = `src/apps/${app}/blocks/${type}`;
467
- for (const f of ['index.tsx', 'view.ts', 'load.ts', 'View.tsx']) if (!has(`${dir}/${f}`)) problems.push(`${dir}/${f} mangler.`);
468
- for (const f of OLD_LOOK)
469
- if (has(`${dir}/${f}`)) problems.push(`${dir}/${f}: blokken tegnes af View.tsx, både på sitet og i editoren.`);
470
- }
471
- return problems;
472
- }
1
+ // The site's theme: how it looks (src/themes/<theme>/, with its theme.json). Which one is in use is data,
2
+ // src/data/theme.json (`{ "theme": "<folder>" }`), changed with the swap button on "Tema" in the editor.
3
+ // Every theme builds on the standard theme (packages/editor/theme/): what a theme does not have itself, a file of the
4
+ // theme or the look of a block, is the standard theme's, styled by the theme in use's CSS. The site and the editor
5
+ // reach the theme as @theme (themePlugin: the theme in use's file, else the standard theme's), and the site's own
6
+ // parts (src/site/) as @site (aliases): for packages/editor/astro.mjs and packages/editor/scripts/check-content.mjs. Stops with what is
7
+ // wrong, when the theme named is not installed.
8
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
9
+ import { relative } from 'node:path';
10
+ import { fileURLToPath, pathToFileURL } from 'node:url';
11
+ import { svgParts } from '@morphemecms/cms/lib/svg.mjs';
12
+ import { declarationProblems, ownerId } from '@morphemecms/cms/lib/licenses.mjs';
13
+ import { installedPacks, isIconName, packFolder, packProblems, phosphorFile, svgProblems, usedIcons } from './icons.mjs';
14
+ import { ICON_PACK_FILE, ICON_PACK_LICENSE, SYSTEM_PACKS, THEME_PACKS_FOLDER, packInUse, packTheme, themePackId } from './iconPacks.mjs';
15
+ import { MOTION_SCHEMES } from '@morphemecms/cms/lib/motion-schemes.mjs';
16
+ import { storeImportProblems } from './storeTheme.mjs';
17
+
18
+ /** The site's choice of icon pack, `{ "pack": "<id>" }` (config.icons in src/site/config.ts). */
19
+ const ICONS_FILE = 'src/data/icons.json';
20
+
21
+ // The site's root (the working directory) and this package's.
22
+ const root = pathToFileURL(`${process.cwd()}/`);
23
+ const pkg = new URL('../', import.meta.url);
24
+
25
+ /** The theme every theme builds on, Material (this package's theme/, id 'saturn'): it has all of THEME_FILES and the look of every
26
+ * standard block. */
27
+ export const STANDARD_THEME = 'saturn';
28
+
29
+ /** A theme's folder, from the site's root: the standard theme's is this package's theme/, another's src/themes/<theme>. */
30
+ export const themeFolder = (theme) =>
31
+ theme === STANDARD_THEME ? relative(process.cwd(), fileURLToPath(new URL('theme', pkg))) : `src/themes/${theme}`;
32
+
33
+ // What the standard theme has, as its folder in src/themes/: what the site's pages (packages/editor/pages/) and the editor
34
+ // (src/site/) take from it as @theme. Besides these, the look of its blocks (blocks/<type>/) and what they import.
35
+ // Another theme needs only theme.json and styles/global.css (OWN_FILES); the rest it may have, else it is the
36
+ // standard theme's.
37
+ export const THEME_FILES = [
38
+ 'theme.json',
39
+ 'styles/global.css',
40
+ 'layouts/PageLayout.astro',
41
+ 'layouts/PostLayout.astro',
42
+ ...['Home', 'Blog', 'Category', 'New', 'NotFound'].map((t) => `templates/${t}.astro`),
43
+ 'editor/previews.tsx',
44
+ 'editor/looks.mjs',
45
+ ];
46
+
47
+ /** The themes installed: the standard theme and the folders in src/themes/ with a theme.json. */
48
+ export const installedThemes = () => [
49
+ STANDARD_THEME,
50
+ ...(existsSync(new URL('src/themes/', root)) ? readdirSync(new URL('src/themes/', root), { withFileTypes: true }) : [])
51
+ .filter((e) => e.isDirectory() && existsSync(new URL(`src/themes/${e.name}/theme.json`, root)))
52
+ .map((e) => e.name),
53
+ ];
54
+
55
+ /** What every theme has itself. */
56
+ export const OWN_FILES = ['theme.json', 'styles/global.css'];
57
+
58
+ // A block's contract (its type, fields, rules, shared texts, list and what it shows), which is the editor's or an
59
+ // app's, never a theme's: a theme has only the look of the standard blocks (blocks/<type>/: View.tsx, components).
60
+ const CONTRACT_FILES = ['index.tsx', 'shared.ts', 'texts.ts', 'list.ts', 'fields.ts', 'view.ts', 'load.ts'];
61
+
62
+ // How a block looked before it was drawn from its view, once on the site and once in the editor: now View.tsx.
63
+ const OLD_LOOK = ['Site.astro', 'draw.tsx'];
64
+
65
+ // An import of the UI components (ui.css) or of the standard theme's CSS that has them (base.css, global.css); the
66
+ // standard theme's own global.css imports ./base.css.
67
+ const UI_IMPORT = /@import\s+['"](?:@morphemecms\/cms\/theme\/styles\/|\.\/)(?:ui|base|global)\.css['"]/;
68
+
69
+ const folders = (url) =>
70
+ existsSync(url)
71
+ ? readdirSync(url, { withFileTypes: true })
72
+ .filter((e) => e.isDirectory())
73
+ .map((e) => e.name)
74
+ : [];
75
+ const has = (path) => existsSync(new URL(path, root));
76
+
77
+ /** The standard blocks' types: their contracts' folders in the editor (packages/editor/src/standard/<type>/). */
78
+ const standardTypes = () => folders(new URL('src/standard/', pkg)).filter((t) => existsSync(new URL(`src/standard/${t}/index.tsx`, pkg)));
79
+
80
+ /** What is wrong with a theme's own icons (icons/<name>.svg, drawn by Svg in packages/editor/src/icon.tsx, in place of
81
+ * its icon pack's): each is one <svg> with a viewBox, with only what draws in it (svgProblems), by a name the site
82
+ * draws. */
83
+ function iconProblems(theme) {
84
+ const dir = `${themeFolder(theme)}/icons`;
85
+ if (!has(dir)) return [];
86
+ const problems = [];
87
+ for (const entry of readdirSync(new URL(dir, root), { withFileTypes: true })) {
88
+ const file = `${dir}/${entry.name}`;
89
+ const name = entry.name.replace(/\.svg$/, '');
90
+ if (!entry.isFile() || !entry.name.endsWith('.svg')) {
91
+ problems.push(`${file}: et temas ikon er en SVG-fil, ${dir}/<navn>.svg.`);
92
+ continue;
93
+ }
94
+ if (!isIconName(name))
95
+ problems.push(`${file}: sitet tegner intet ikon, der hedder "${name}". Et ikon hedder som i Phosphor (fx map-pin, play-fill).`);
96
+ problems.push(...svgProblems(file, readFileSync(new URL(file, root), 'utf8')));
97
+ }
98
+ return problems;
99
+ }
100
+
101
+ const manifestOf = (theme) => JSON.parse(readFileSync(new URL(`${themeFolder(theme)}/theme.json`, root), 'utf8'));
102
+
103
+ /** The icon packs a theme brings (src/themes/<theme>/icon-packs/<pack>/, with its pack.json), by their folders. */
104
+ const ownPacks = (theme) =>
105
+ folders(new URL(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/`, root)).filter((p) =>
106
+ has(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/${p}/${ICON_PACK_FILE}`),
107
+ );
108
+
109
+ /** The choice of icon pack made under "Indstillinger" › "Udseende" › "Ikonpakker" (src/data/icons.json, `{ "pack": "<id>"
110
+ * }`), or undefined while none is made (then the theme's is used). */
111
+ export function chosenPack() {
112
+ const file = new URL(ICONS_FILE, root);
113
+ if (!existsSync(file)) return undefined;
114
+ const { pack } = JSON.parse(readFileSync(file, 'utf8'));
115
+ return pack ?? undefined;
116
+ }
117
+
118
+ /** The icon pack the site and the editor draw their icons with, by its id (packInUse in packages/editor/site/iconPacks.mjs): the one
119
+ * chosen, else the theme's ("icons" in its theme.json), else the standard theme's (Material Symbols). Stops with what is
120
+ * wrong when it is not installed. */
121
+ export function iconPack(theme = activeTheme()) {
122
+ const installed = installedPacks();
123
+ const chosen = chosenPack();
124
+ const id = packInUse({ chosen, theme, icons: manifestOf(theme).icons, standardIcons: manifestOf(STANDARD_THEME).icons, installed });
125
+ if (!installed.includes(id))
126
+ throw new Error(
127
+ chosen === id
128
+ ? `${ICONS_FILE}: ikonpakken ${JSON.stringify(id)} er ikke installeret (${packFolder(id)}/${ICON_PACK_FILE}).`
129
+ : `${themeFolder(theme)}/theme.json: "icons" er ikonpakken ${JSON.stringify(id)}, som ikke er installeret.`,
130
+ );
131
+ return id;
132
+ }
133
+
134
+ /** The icon packs there are with their licences, from their manifests, for "Licenser" under "Indstillinger" (IconPack
135
+ * in @morphemecms/cms/lib/licenses.mjs, licensesIntegration in packages/editor/astro.mjs): each pack's id, name, version, who made it,
136
+ * its licence's name and file (LICENSE in its folder), the theme it came with, whether the site draws with it, and each
137
+ * set it takes icons from besides its own (`includes`, with its `licenseFile`). packProblems checks the manifests. */
138
+ export function iconPackLicenses() {
139
+ const inUse = iconPack();
140
+ return installedPacks().map((id) => {
141
+ const dir = packFolder(id);
142
+ const manifest = JSON.parse(readFileSync(new URL(`${dir}/${ICON_PACK_FILE}`, root), 'utf8'));
143
+ const set = (x, file) => ({ name: x.name, version: x.version ?? null, author: x.author, license: x.license, file: `${dir}/${file}` });
144
+ return {
145
+ id,
146
+ ...set(manifest, ICON_PACK_LICENSE),
147
+ ...(packTheme(id) ? { theme: packTheme(id) } : {}),
148
+ inUse: id === inUse,
149
+ includes: (manifest.includes ?? []).map((x) => set(x, x.licenseFile)),
150
+ };
151
+ });
152
+ }
153
+
154
+ /** The folders a theme's icons come from, first the one that wins: its own icons/, then the standard theme's (it has
155
+ * none), then the icon pack in use's (iconPack). */
156
+ const iconFolders = (theme) =>
157
+ [...new Set([theme, STANDARD_THEME])].map((t) => `${themeFolder(t)}/icons/`).concat(`${packFolder(iconPack(theme))}/`);
158
+
159
+ /** Whether an icon's file is Phosphor's own (as its package has it, as Phosphor's pack has it): then it needs no
160
+ * putting in Phosphor's place. */
161
+ const isPhosphors = (name, svg) => svg.trim() === phosphorFile(name)?.trim();
162
+
163
+ /** The theme in use's icons as their files' text, by name: its own (icons/<name>.svg), else its icon pack's (iconPack);
164
+ * not those that are Phosphor's own, which the site and the editor draw as they are (all, with Phosphor's pack). */
165
+ function themeIcons() {
166
+ const icons = {};
167
+ for (const dir of iconFolders(activeTheme()).reverse())
168
+ if (has(dir))
169
+ for (const f of readdirSync(new URL(dir, root)))
170
+ if (f.endsWith('.svg')) icons[f.slice(0, -'.svg'.length)] = readFileSync(new URL(dir + f, root), 'utf8');
171
+ for (const [name, svg] of Object.entries(icons)) if (isPhosphors(name, svg)) delete icons[name];
172
+ return icons;
173
+ }
174
+
175
+ /** An icon of a theme (the one in use, if none is given) as its file's text: its own (icons/<name>.svg), else its icon
176
+ * pack's; null when it is Phosphor's own, or neither has it (then it is Phosphor's). For what is drawn outside Vite: the
177
+ * play button of a video in the text (rehypeVideo, packages/editor/astro.mjs). In Vite, themePlugin puts them in Phosphor's place. */
178
+ export function themeIcon(name, theme = activeTheme()) {
179
+ for (const dir of iconFolders(theme))
180
+ if (has(`${dir}${name}.svg`)) {
181
+ const svg = readFileSync(new URL(`${dir}${name}.svg`, root), 'utf8');
182
+ return isPhosphors(name, svg) ? null : svg;
183
+ }
184
+ return null;
185
+ }
186
+
187
+ /** The site's choice of motion scheme, `{ "scheme": "<scheme>" }` (config.motion in src/site/config.ts). */
188
+ const MOTION_FILE = 'src/data/motion.json';
189
+
190
+ /** What is wrong with the choice of motion scheme made under "Udseende" › "Bevægelse" (src/data/motion.json): one that
191
+ * is not a scheme (lib/motion.ts). */
192
+ export function motionProblems() {
193
+ const file = new URL(MOTION_FILE, root);
194
+ if (!existsSync(file)) return [];
195
+ try {
196
+ const { scheme } = JSON.parse(readFileSync(file, 'utf8'));
197
+ if (MOTION_SCHEMES.includes(scheme)) return [];
198
+ return [`${MOTION_FILE}: bevægelsen ${JSON.stringify(scheme ?? null)} findes ikke. Vælg en under Udseende › Bevægelse.`];
199
+ } catch (e) {
200
+ return [`${MOTION_FILE}: ${e.message}`];
201
+ }
202
+ }
203
+
204
+ /** What is wrong with the system's icon packs (packProblems; a theme's own are its themeProblems) and the choice of one:
205
+ * src/data/icons.json naming one that is not there. */
206
+ export function iconPackProblems() {
207
+ const used = usedIcons();
208
+ const installed = installedPacks();
209
+ const problems = SYSTEM_PACKS.filter((id) => installed.includes(id)).flatMap((id) => packProblems(id, used));
210
+ for (const id of SYSTEM_PACKS)
211
+ if (!installed.includes(id)) problems.push(`${packFolder(id)}/${ICON_PACK_FILE} mangler: kør npm run icons.`);
212
+ try {
213
+ const chosen = chosenPack();
214
+ if (chosen !== undefined && (typeof chosen !== 'string' || !installed.includes(chosen)))
215
+ problems.push(`${ICONS_FILE}: ikonpakken ${JSON.stringify(chosen)} er ikke installeret. Vælg en anden under Udseende › Ikonpakker.`);
216
+ } catch (e) {
217
+ problems.push(`${ICONS_FILE}: ${e.message}`);
218
+ }
219
+ return problems;
220
+ }
221
+
222
+ // Phosphor, as the site and the editor import it: its React icons (`import { CaretRight } from '@phosphor-icons/react'`)
223
+ // and its SVG files as text (`@phosphor-icons/core/assets/regular/caret-right.svg?raw`, `…/fill/play-fill.svg?raw`, also
224
+ // in the import.meta.glob of Svg, packages/editor/src/icon.tsx). themePlugin puts the theme's icons in their place.
225
+ const PHOSPHOR_REACT = '@phosphor-icons/react';
226
+ const PHOSPHOR_FILE = /@phosphor-icons\/core\/assets\/(?:regular|fill)\/([a-z0-9-]+)\.svg\?raw$/;
227
+ const REACT_ICONS = '\0site-theme-icons';
228
+ const FILE_ICON = '\0site-theme-icon:';
229
+
230
+ /** The React component's name of a Phosphor icon by its file's name (`caret-right` → CaretRight), when Phosphor has it. */
231
+ const reactName = (name) => {
232
+ const pascal = name.replace(/(^|-)([a-z0-9])/g, (_, __, c) => c.toUpperCase());
233
+ return has(`node_modules/@phosphor-icons/react/dist/csr/${pascal}.es.js`) ? pascal : null;
234
+ };
235
+
236
+ /** `@phosphor-icons/react` with the theme's icons in place of Phosphor's, the rest Phosphor's: each one the theme has
237
+ * draws its SVG file in the file's viewBox, with the props of a Phosphor icon (size, color, weight, mirrored, alt, the
238
+ * svg's own; from IconContext as Phosphor's), through Phosphor's IconBase; `weight="fill"` its `<name>-fill`, if the
239
+ * theme has one, else the same as the others. */
240
+ function reactIcons(icons) {
241
+ const lines = [
242
+ "import { createElement, forwardRef, useContext } from 'react';",
243
+ `import { IconBase, IconContext } from '${PHOSPHOR_REACT}';`,
244
+ `export * from '${PHOSPHOR_REACT}';`,
245
+ 'const draw = (regular, fill) =>',
246
+ ' forwardRef((props, ref) => {',
247
+ ' const context = useContext(IconContext);',
248
+ " const weight = props.weight ?? context.weight ?? 'regular';",
249
+ ' const color = props.color ?? context.color;',
250
+ " const [viewBox, inner] = weight === 'fill' && fill ? fill : regular;",
251
+ // A colour of its own also for lines (a theme's own icon drawn with stroke="currentColor"), not only for what is filled.
252
+ " const drawn = createElement('g', { color: color === 'currentColor' ? undefined : color, dangerouslySetInnerHTML: { __html: inner } });",
253
+ ' return createElement(IconBase, { ...props, ref, viewBox, weights: new Map([[weight, drawn]]) });',
254
+ ' });',
255
+ ];
256
+ const parts = (name) => {
257
+ const svg = svgParts(icons[name]);
258
+ return svg && JSON.stringify([svg.viewBox, svg.inner]);
259
+ };
260
+ for (const name of Object.keys(icons)) {
261
+ const component = reactName(name);
262
+ if (!component || !parts(name)) continue;
263
+ lines.push(
264
+ `export const ${component}Icon = /* @__PURE__ */ draw(${parts(name)}, ${parts(`${name}-fill`) ?? 'null'});`,
265
+ `export { ${component}Icon as ${component} };`,
266
+ );
267
+ }
268
+ return lines.join('\n');
269
+ }
270
+
271
+ /** An icon's SVG file as Phosphor's are (in the text's colour, `fill="currentColor"`), for an import of Phosphor's. */
272
+ const fileIcon = (svg) => {
273
+ const { viewBox, inner } = svgParts(svg);
274
+ const box = viewBox ? ` viewBox="${viewBox}"` : '';
275
+ return `export default ${JSON.stringify(`<svg xmlns="http://www.w3.org/2000/svg"${box} fill="currentColor">${inner}</svg>`)};`;
276
+ };
277
+
278
+ /** What a theme is missing (the standard theme: of THEME_FILES and the look of every standard block, another: of
279
+ * OWN_FILES), what is wrong in its theme.json (name, version, author, icon), a block of its own (a folder in its
280
+ * blocks/ that is not the look of a standard block, or with a block's contract in it), a part nothing uses (in a
281
+ * folder without its View.tsx, a file the standard theme does not have there), and what is wrong with its own icons
282
+ * (iconProblems). */
283
+ export function themeProblems(theme) {
284
+ const needed = theme === STANDARD_THEME ? THEME_FILES : OWN_FILES;
285
+ const problems = needed.filter((f) => !has(`${themeFolder(theme)}/${f}`)).map((f) => `${themeFolder(theme)}/${f} mangler.`);
286
+ const standard = new Set(standardTypes());
287
+ if (theme === STANDARD_THEME)
288
+ for (const type of standard)
289
+ if (!has(`${themeFolder(theme)}/blocks/${type}/View.tsx`)) problems.push(`${themeFolder(theme)}/blocks/${type}/View.tsx mangler.`);
290
+ // Every theme draws the UI components (styles/ui.css: the buttons and icon buttons): it imports them, itself or
291
+ // through the standard theme's base.css or global.css.
292
+ const css = `${themeFolder(theme)}/styles/global.css`;
293
+ if (has(css) && !UI_IMPORT.test(readFileSync(new URL(css, root), 'utf8')))
294
+ problems.push(
295
+ `${css}: importerer ikke temaets UI-dele (knapperne): @import '@morphemecms/cms/theme/styles/ui.css', eller standardtemaets base.css eller global.css, der gør det.`,
296
+ );
297
+ for (const type of folders(new URL(`${themeFolder(theme)}/blocks/`, root))) {
298
+ const dir = `${themeFolder(theme)}/blocks/${type}`;
299
+ if (!standard.has(type)) problems.push(`${dir}: et tema kan kun have udseendet af en standardblok, og "${type}" er ikke en.`);
300
+ for (const f of CONTRACT_FILES)
301
+ if (has(`${dir}/${f}`)) problems.push(`${dir}/${f}: en bloks kontrakt hører til editoren eller en app, ikke temaet.`);
302
+ // Without a View.tsx of its own, the standard theme's draws the block, and each file replaces one of its parts
303
+ // (faq/Accordion.tsx), which it imports as @theme/…; else nothing would use it. The standard theme draws them all.
304
+ if (theme === STANDARD_THEME || has(`${dir}/View.tsx`)) {
305
+ if (!has(`${dir}/View.tsx`)) problems.push(`${dir}/View.tsx mangler: den tegner blokken på sitet og i editoren.`);
306
+ } else
307
+ for (const entry of readdirSync(new URL(dir, root), { withFileTypes: true }))
308
+ if (
309
+ entry.name !== 'script.ts' &&
310
+ !CONTRACT_FILES.includes(entry.name) &&
311
+ !has(`${themeFolder(STANDARD_THEME)}/blocks/${type}/${entry.name}`)
312
+ )
313
+ problems.push(
314
+ `${dir}/${entry.name}: standardtemaet har ingen del med det navn, så intet bruger den. Giv den samme navn som den del, den erstatter, eller tegn blokken med en View.tsx.`,
315
+ );
316
+ for (const f of OLD_LOOK)
317
+ if (has(`${dir}/${f}`)) problems.push(`${dir}/${f}: blokken tegnes af View.tsx, både på sitet og i editoren.`);
318
+ }
319
+ try {
320
+ const manifest = JSON.parse(readFileSync(new URL(`${themeFolder(theme)}/theme.json`, root), 'utf8'));
321
+ for (const key of ['name', 'version', 'author', 'icon'])
322
+ if (typeof manifest[key] !== 'string' || !manifest[key]) problems.push(`${themeFolder(theme)}/theme.json: "${key}" mangler.`);
323
+ const { description, highlights, releaseNotes } = manifest;
324
+ const byLanguage = (x, ok) => x && typeof x === 'object' && !Array.isArray(x) && Object.values(x).length && Object.values(x).every(ok);
325
+ if (description !== undefined && !byLanguage(description, (x) => typeof x === 'string' && x))
326
+ problems.push(`${themeFolder(theme)}/theme.json: "description" skal være teksten på hvert sprog, fx { "da-DK": "…" }.`);
327
+ if (
328
+ highlights !== undefined &&
329
+ !byLanguage(highlights, (x) => Array.isArray(x) && x.length && x.every((y) => typeof y === 'string' && y))
330
+ )
331
+ problems.push(`${themeFolder(theme)}/theme.json: "highlights" skal være punkterne på hvert sprog, fx { "da-DK": ["…"] }.`);
332
+ if (releaseNotes !== undefined && !byLanguage(releaseNotes, (x) => byLanguage(x, (y) => typeof y === 'string' && y)))
333
+ problems.push(
334
+ `${themeFolder(theme)}/theme.json: "releaseNotes" skal være det nye i hver version på hvert sprog, fx { "da-DK": { "1.1.0": "…" } }.`,
335
+ );
336
+ if ('store' in manifest && (manifest.store !== true || theme === STANDARD_THEME))
337
+ problems.push(
338
+ `${themeFolder(theme)}/theme.json: "store" kan kun være true (temaet står i Appstore, npm run store:release), og standardtemaet har det ikke.`,
339
+ );
340
+ // A theme from the store is copied in as its folder only: its CSS imports nothing from outside it but the CMS.
341
+ if (manifest.store === true) {
342
+ const styles = `${themeFolder(theme)}/styles`;
343
+ for (const f of has(styles) ? readdirSync(new URL(`${styles}/`, root), { recursive: true }) : [])
344
+ if (String(f).endsWith('.css')) {
345
+ const inTheme = `styles/${String(f).replaceAll('\\', '/')}`;
346
+ for (const p of storeImportProblems(readFileSync(new URL(`${themeFolder(theme)}/${inTheme}`, root), 'utf8'), inTheme))
347
+ problems.push(`${themeFolder(theme)}/${p}`);
348
+ }
349
+ }
350
+ // The icon pack it uses by default: a system pack, or one of its own by its folder; the standard theme's a system pack.
351
+ const own = ownPacks(theme);
352
+ if (
353
+ (manifest.icons !== undefined || theme === STANDARD_THEME) &&
354
+ !(SYSTEM_PACKS.includes(manifest.icons) || (theme !== STANDARD_THEME && own.includes(manifest.icons)))
355
+ )
356
+ problems.push(
357
+ `${themeFolder(theme)}/theme.json: "icons" er den ikonpakke, temaet bruger, når ingen er valgt: ${[...SYSTEM_PACKS, ...own].map((p) => `"${p}"`).join(', ')}.`,
358
+ );
359
+ // How it moves while the owner has chosen none (lib/motion.ts); without it, the standard theme's.
360
+ if (manifest.motion !== undefined && !MOTION_SCHEMES.includes(manifest.motion))
361
+ problems.push(
362
+ `${themeFolder(theme)}/theme.json: "motion" er den bevægelse, temaet bruger, når ingen er valgt: ${MOTION_SCHEMES.map((s) => `"${s}"`).join(', ')}.`,
363
+ );
364
+ // Its own licence and the licences of what it brings (fonts, code, pictures), for "Licenser" and the store.
365
+ problems.push(
366
+ ...declarationProblems({
367
+ license: manifest.license,
368
+ thirdParty: manifest.thirdParty,
369
+ folder: fileURLToPath(new URL(`${themeFolder(theme)}/`, root)),
370
+ where: `${themeFolder(theme)}/theme.json`,
371
+ what: `temaet ${manifest.name || theme}`,
372
+ owner: ownerId('theme', theme),
373
+ root: fileURLToPath(root),
374
+ }),
375
+ );
376
+ } catch (e) {
377
+ problems.push(`${themeFolder(theme)}/theme.json: ${e.message}`);
378
+ }
379
+ // The icon packs it brings: each as every pack must be, and not by a system pack's name.
380
+ const packs = ownPacks(theme);
381
+ for (const p of packs)
382
+ if (SYSTEM_PACKS.includes(p))
383
+ problems.push(
384
+ `${themeFolder(theme)}/${THEME_PACKS_FOLDER}/${p}: en ikonpakke i et tema hedder ikke som systemets (${SYSTEM_PACKS.join(', ')}).`,
385
+ );
386
+ if (theme === STANDARD_THEME && packs.length)
387
+ problems.push(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}: standardtemaets ikonpakker er systemets, i CMS'ets icons/.`);
388
+ for (const entry of has(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}`)
389
+ ? readdirSync(new URL(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/`, root))
390
+ : [])
391
+ if (!packs.includes(entry))
392
+ problems.push(`${themeFolder(theme)}/${THEME_PACKS_FOLDER}/${entry}: en ikonpakke er en mappe med sin ${ICON_PACK_FILE}.`);
393
+ const used = usedIcons();
394
+ return [...problems, ...iconProblems(theme), ...packs.flatMap((p) => packProblems(themePackId(theme, p), used))];
395
+ }
396
+
397
+ // A site's own parts (src/site/<file>), else the package's standard ones (defaults/<file>): `@site/<file>`. With `/` only, as Vite's ids (Windows).
398
+ const SITE_DIRS = [fileURLToPath(new URL('src/site/', root)), fileURLToPath(new URL('defaults/', pkg))].map((d) => d.replaceAll('\\', '/'));
399
+
400
+ /** The file `@site/<file>` is: the site's src/site/<file>, else the package's defaults/<file>, with or without its
401
+ * extension (`@site/editor`, as TypeScript writes it), or null. */
402
+ export function siteFile(file) {
403
+ for (const dir of SITE_DIRS)
404
+ for (const ext of ['', '.ts', '.tsx', '.mjs', '.js'])
405
+ if (statSync(dir + file + ext, { throwIfNoEntry: false })?.isFile()) return dir + file + ext;
406
+ return null;
407
+ }
408
+
409
+ /** The folder of the theme in use, under src/themes/. */
410
+ export function activeTheme() {
411
+ const { theme } = JSON.parse(readFileSync(new URL('src/data/theme.json', root), 'utf8'));
412
+ if (typeof theme !== 'string' || !/^[a-z0-9-]+$/.test(theme) || !existsSync(new URL(`${themeFolder(theme)}/theme.json`, root)))
413
+ throw new Error(`src/data/theme.json: temaet ${JSON.stringify(theme)} er ikke installeret (src/themes/<tema>/theme.json).`);
414
+ return theme;
415
+ }
416
+
417
+ /** @data (the site's data, src/data/), @saturn (the standard theme, this package's theme/) and @icons (the system's icon
418
+ * packs), as Vite's resolve.alias. @site is themePlugin's (siteFile). */
419
+ export const aliases = () => ({
420
+ '@data': fileURLToPath(new URL('src/data', root)),
421
+ '@saturn': fileURLToPath(new URL('theme', pkg)),
422
+ '@icons': fileURLToPath(new URL('icons', pkg)),
423
+ });
424
+
425
+ /** @theme and @site as a Vite plugin: `@site/<file>` is the site's own part, else the package's standard one
426
+ * (siteFile), and `@theme/<file>` is the theme in use's file, else the standard theme's. A glob
427
+ * (`import.meta.glob('@theme/blocks/*\/View.tsx')`) is the theme in use's; whoever globs puts the standard
428
+ * theme's under it (packages/editor/src/standard/siteLooks.ts, packages/editor/src/standard/themeBlocks.ts).
429
+ * And the theme's icons (themeIcons: its own, else the icon pack in use's, iconPack) in Phosphor's place, on the site
430
+ * and in the editor alike: an import of `@phosphor-icons/react` is Phosphor's with the theme's icons in place of its own
431
+ * (reactIcons), and an SVG file of Phosphor's as text (`?raw`) the theme's, when it has one. An icon neither has is
432
+ * Phosphor's (check:content stops at a pack without one the site or the editor draws: packProblems). With Phosphor's
433
+ * pack and no icons of its own nothing is changed. The pack is read when the site is built, so a new choice
434
+ * (src/data/icons.json) or theme is in the site and the editor after the next build. */
435
+ export function themePlugin() {
436
+ const themes = [activeTheme(), STANDARD_THEME].map((t) => fileURLToPath(new URL(`${themeFolder(t)}/`, root)).replaceAll('\\', '/'));
437
+ const icons = themeIcons();
438
+ const own = Object.keys(icons).length > 0;
439
+ return {
440
+ name: 'site-theme',
441
+ enforce: 'pre',
442
+ // Phosphor's package bundled for the browser, also when the first import of it is the theme's icons'.
443
+ config: () => (own ? { optimizeDeps: { include: [PHOSPHOR_REACT] } } : {}),
444
+ resolveId(source, importer) {
445
+ // The theme's icons import Phosphor's own.
446
+ if (source === PHOSPHOR_REACT) return own && importer !== REACT_ICONS ? REACT_ICONS : null;
447
+ const icon = source.match(PHOSPHOR_FILE)?.[1];
448
+ if (icon) return svgParts(icons[icon]) ? FILE_ICON + icon : null;
449
+ if (source.startsWith('@site/')) return siteFile(source.slice('@site/'.length));
450
+ if (!source.startsWith('@theme/')) return null;
451
+ const file = source.slice('@theme/'.length);
452
+ if (file.includes('*')) return themes[0] + file;
453
+ // A file without its extension (`@theme/editor/previews`), as TypeScript writes it.
454
+ for (const dir of themes)
455
+ for (const ext of ['', '.ts', '.tsx', '.mjs', '.js'])
456
+ if (statSync(dir + file + ext, { throwIfNoEntry: false })?.isFile()) return dir + file + ext;
457
+ return null;
458
+ },
459
+ load(id) {
460
+ if (id === REACT_ICONS) return reactIcons(icons);
461
+ if (id.startsWith(FILE_ICON)) return fileIcon(icons[id.slice(FILE_ICON.length)]);
462
+ return null;
463
+ },
464
+ };
465
+ }
466
+
467
+ /** What a block is missing to be drawn from its view (packages/editor/src/standard/view.tsx): a standard block's
468
+ * contract its view.ts and load.ts, an app's block (src/apps/<app>/blocks/<type>/) those and its View.tsx; and the
469
+ * files of how a block looked before (Site.astro, draw.tsx), which nothing uses now. */
470
+ export function blockProblems() {
471
+ const problems = [];
472
+ for (const type of standardTypes())
473
+ for (const f of ['view.ts', 'load.ts'])
474
+ if (!existsSync(new URL(`src/standard/${type}/${f}`, pkg))) problems.push(`packages/editor/src/standard/${type}/${f} mangler.`);
475
+ for (const app of folders(new URL('src/apps/', root)))
476
+ for (const type of folders(new URL(`src/apps/${app}/blocks/`, root))) {
477
+ const dir = `src/apps/${app}/blocks/${type}`;
478
+ for (const f of ['index.tsx', 'view.ts', 'load.ts', 'View.tsx']) if (!has(`${dir}/${f}`)) problems.push(`${dir}/${f} mangler.`);
479
+ for (const f of OLD_LOOK)
480
+ if (has(`${dir}/${f}`)) problems.push(`${dir}/${f}: blokken tegnes af View.tsx, både på sitet og i editoren.`);
481
+ }
482
+ return problems;
483
+ }