@morphemecms/cms 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/astro.mjs +3 -0
  3. package/bin/morpheme.mjs +3 -1
  4. package/defaults/blockTexts.ts +2 -2
  5. package/defaults/collections.ts +10 -0
  6. package/defaults/editor.ts +57 -53
  7. package/defaults/privacy.ts +4 -1
  8. package/defaults/rules.ts +36 -38
  9. package/defaults/standardPages.ts +0 -3
  10. package/defaults/texts.ts +11 -2
  11. package/demo/src/content/pages/about.md +2 -0
  12. package/icons/lucide/browser-fill.svg +1 -0
  13. package/icons/lucide/buildings-fill.svg +1 -0
  14. package/icons/lucide/certificate-fill.svg +1 -0
  15. package/icons/lucide/chart-line-up-fill.svg +1 -0
  16. package/icons/lucide/chats-fill.svg +1 -0
  17. package/icons/lucide/envelope-simple-fill.svg +1 -0
  18. package/icons/lucide/file-text-fill.svg +1 -0
  19. package/icons/lucide/folder-simple-fill.svg +1 -0
  20. package/icons/lucide/gauge.svg +1 -1
  21. package/icons/lucide/gear-six.svg +1 -0
  22. package/icons/lucide/globe-simple.svg +1 -0
  23. package/icons/lucide/heart-fill.svg +1 -0
  24. package/icons/lucide/layout.svg +1 -0
  25. package/icons/lucide/list-checks-fill.svg +1 -0
  26. package/icons/lucide/magnifying-glass-fill.svg +1 -0
  27. package/icons/lucide/megaphone-fill.svg +1 -0
  28. package/icons/lucide/newspaper-fill.svg +1 -0
  29. package/icons/lucide/paint-brush-broad-fill.svg +1 -0
  30. package/icons/lucide/puzzle-piece-fill.svg +1 -0
  31. package/icons/lucide/shield-check-fill.svg +1 -0
  32. package/icons/lucide/sidebar-simple.svg +1 -1
  33. package/icons/lucide/sidebar.svg +1 -0
  34. package/icons/lucide/square-half-bottom-fill.svg +1 -0
  35. package/icons/lucide/square-half.svg +1 -0
  36. package/icons/lucide/squares-four-fill.svg +1 -0
  37. package/icons/lucide/storefront-fill.svg +1 -0
  38. package/icons/lucide/tag-fill.svg +1 -0
  39. package/icons/lucide/toggle-right-fill.svg +1 -0
  40. package/icons/lucide/trash-fill.svg +1 -0
  41. package/icons/lucide/user-fill.svg +1 -0
  42. package/icons/lucide/wrench-fill.svg +1 -0
  43. package/icons/material-symbols/browser-fill.svg +1 -0
  44. package/icons/material-symbols/buildings-fill.svg +1 -0
  45. package/icons/material-symbols/certificate-fill.svg +1 -0
  46. package/icons/material-symbols/chart-line-up-fill.svg +1 -0
  47. package/icons/material-symbols/chats-fill.svg +1 -0
  48. package/icons/material-symbols/envelope-simple-fill.svg +1 -0
  49. package/icons/material-symbols/file-text-fill.svg +1 -0
  50. package/icons/material-symbols/folder-simple-fill.svg +1 -0
  51. package/icons/material-symbols/gauge.svg +1 -1
  52. package/icons/material-symbols/gear-six.svg +1 -0
  53. package/icons/material-symbols/globe-simple.svg +1 -0
  54. package/icons/material-symbols/heart-fill.svg +1 -0
  55. package/icons/material-symbols/layout.svg +1 -0
  56. package/icons/material-symbols/list-checks-fill.svg +1 -0
  57. package/icons/material-symbols/magnifying-glass-fill.svg +1 -0
  58. package/icons/material-symbols/megaphone-fill.svg +1 -0
  59. package/icons/material-symbols/newspaper-fill.svg +1 -0
  60. package/icons/material-symbols/paint-brush-broad-fill.svg +1 -0
  61. package/icons/material-symbols/puzzle-piece-fill.svg +1 -0
  62. package/icons/material-symbols/shield-check-fill.svg +1 -0
  63. package/icons/material-symbols/sidebar-simple.svg +1 -1
  64. package/icons/material-symbols/sidebar.svg +1 -0
  65. package/icons/material-symbols/square-half-bottom-fill.svg +1 -0
  66. package/icons/material-symbols/square-half.svg +1 -0
  67. package/icons/material-symbols/squares-four-fill.svg +1 -0
  68. package/icons/material-symbols/storefront-fill.svg +1 -0
  69. package/icons/material-symbols/tag-fill.svg +1 -0
  70. package/icons/material-symbols/toggle-right-fill.svg +1 -0
  71. package/icons/material-symbols/trash-fill.svg +1 -0
  72. package/icons/material-symbols/user-fill.svg +1 -0
  73. package/icons/material-symbols/wrench-fill.svg +1 -0
  74. package/icons/phosphor/browser-fill.svg +1 -0
  75. package/icons/phosphor/buildings-fill.svg +1 -0
  76. package/icons/phosphor/certificate-fill.svg +1 -0
  77. package/icons/phosphor/chart-line-up-fill.svg +1 -0
  78. package/icons/phosphor/chats-fill.svg +1 -0
  79. package/icons/phosphor/envelope-simple-fill.svg +1 -0
  80. package/icons/phosphor/file-text-fill.svg +1 -0
  81. package/icons/phosphor/folder-simple-fill.svg +1 -0
  82. package/icons/phosphor/gear-six.svg +1 -0
  83. package/icons/phosphor/globe-simple.svg +1 -0
  84. package/icons/phosphor/heart-fill.svg +1 -0
  85. package/icons/phosphor/layout.svg +1 -0
  86. package/icons/phosphor/list-checks-fill.svg +1 -0
  87. package/icons/phosphor/magnifying-glass-fill.svg +1 -0
  88. package/icons/phosphor/megaphone-fill.svg +1 -0
  89. package/icons/phosphor/newspaper-fill.svg +1 -0
  90. package/icons/phosphor/paint-brush-broad-fill.svg +1 -0
  91. package/icons/phosphor/puzzle-piece-fill.svg +1 -0
  92. package/icons/phosphor/shield-check-fill.svg +1 -0
  93. package/icons/phosphor/sidebar.svg +1 -0
  94. package/icons/phosphor/square-half-bottom-fill.svg +1 -0
  95. package/icons/phosphor/square-half.svg +1 -0
  96. package/icons/phosphor/squares-four-fill.svg +1 -0
  97. package/icons/phosphor/storefront-fill.svg +1 -0
  98. package/icons/phosphor/tag-fill.svg +1 -0
  99. package/icons/phosphor/toggle-right-fill.svg +1 -0
  100. package/icons/phosphor/trash-fill.svg +1 -0
  101. package/icons/phosphor/user-fill.svg +1 -0
  102. package/icons/phosphor/wrench-fill.svg +1 -0
  103. package/lib/breakpoints.d.mts +13 -0
  104. package/lib/breakpoints.mjs +20 -0
  105. package/lib/breakpoints.test.mjs +39 -0
  106. package/lib/build-lane.d.mts +15 -0
  107. package/lib/build-lane.mjs +23 -0
  108. package/lib/build-lane.test.mjs +74 -0
  109. package/lib/build-settings.d.mts +16 -0
  110. package/lib/build-settings.mjs +39 -0
  111. package/lib/build-settings.test.mjs +41 -0
  112. package/lib/contracts.d.mts +3 -2
  113. package/lib/contracts.mjs +3 -2
  114. package/lib/drawer-swipe.d.mts +10 -0
  115. package/lib/drawer-swipe.mjs +17 -0
  116. package/lib/drawer-swipe.test.mjs +33 -0
  117. package/lib/drawerKeys.d.mts +9 -0
  118. package/lib/drawerKeys.mjs +26 -0
  119. package/lib/drawerKeys.test.mjs +21 -0
  120. package/lib/eyebrow.d.mts +6 -0
  121. package/lib/eyebrow.mjs +24 -0
  122. package/lib/eyebrow.test.mjs +20 -0
  123. package/lib/front-sections.test.mjs +82 -0
  124. package/lib/heading-levels.d.mts +7 -0
  125. package/lib/heading-levels.mjs +16 -0
  126. package/lib/heading-levels.test.mjs +37 -0
  127. package/lib/inline.ts +3 -2
  128. package/lib/markdown.ts +2 -0
  129. package/lib/materialIcon.d.mts +21 -0
  130. package/lib/materialIcon.mjs +29 -0
  131. package/lib/materialIcon.test.mjs +45 -0
  132. package/lib/menu-layout.d.mts +11 -0
  133. package/lib/menu-layout.mjs +14 -0
  134. package/lib/menu-layout.test.mjs +23 -0
  135. package/lib/menu.d.mts +59 -0
  136. package/lib/menu.mjs +129 -0
  137. package/lib/menu.test.mjs +291 -0
  138. package/lib/rehype-emphasize.d.mts +4 -0
  139. package/lib/rehype-emphasize.mjs +20 -0
  140. package/lib/rehype-emphasize.test.mjs +39 -0
  141. package/lib/section-in-view.d.mts +8 -0
  142. package/lib/section-in-view.mjs +25 -0
  143. package/lib/section-in-view.test.mjs +35 -0
  144. package/lib/section-marker.d.mts +5 -0
  145. package/lib/section-marker.mjs +18 -0
  146. package/lib/section-marker.test.mjs +22 -0
  147. package/lib/section-ranges.d.mts +12 -0
  148. package/lib/section-ranges.mjs +43 -0
  149. package/lib/section-ranges.test.mjs +61 -0
  150. package/lib/sheet-stops.d.mts +68 -0
  151. package/lib/sheet-stops.mjs +198 -0
  152. package/lib/sheet-stops.test.mjs +155 -0
  153. package/lib/still-timer.d.mts +5 -0
  154. package/lib/still-timer.mjs +17 -0
  155. package/lib/still-timer.test.mjs +42 -0
  156. package/lib/text-sections.d.mts +10 -0
  157. package/lib/text-sections.mjs +45 -0
  158. package/lib/text-sections.test.mjs +78 -0
  159. package/lib/video.mjs +2 -1
  160. package/lib/without-h1.d.mts +1 -0
  161. package/lib/without-h1.mjs +7 -0
  162. package/lib/without-h1.test.mjs +26 -0
  163. package/lib/word-at.d.mts +6 -0
  164. package/lib/word-at.mjs +20 -0
  165. package/lib/word-at.test.mjs +19 -0
  166. package/package.json +3 -1
  167. package/pages/editor/version.json.js +22 -2
  168. package/scripts/already-checked.mjs +11 -0
  169. package/scripts/already-checked.test.mjs +18 -0
  170. package/scripts/build-settings-problems.mjs +27 -0
  171. package/scripts/build-settings-problems.test.mjs +35 -0
  172. package/scripts/check-content.mjs +42 -4
  173. package/scripts/check-worker.mjs +96 -4
  174. package/scripts/ci-build.mjs +85 -0
  175. package/scripts/ci-build.test.mjs +137 -0
  176. package/scripts/editor-roundtrip.mjs +62 -3
  177. package/scripts/editor-screens.mjs +21 -7
  178. package/scripts/git-env.mjs +6 -0
  179. package/scripts/step-command.mjs +34 -0
  180. package/scripts/step-command.test.mjs +50 -0
  181. package/scripts/text-problems.mjs +83 -0
  182. package/scripts/text-problems.test.mjs +74 -0
  183. package/scripts/worker-modules.mjs +11 -1
  184. package/site/breakpoints.d.mts +6 -0
  185. package/site/breakpoints.mjs +52 -0
  186. package/site/breakpoints.test.mjs +36 -0
  187. package/site/casing.d.mts +3 -0
  188. package/site/casing.mjs +26 -0
  189. package/site/content.config.ts +2 -0
  190. package/site/content.ts +2 -4
  191. package/site/defineSite.d.mts +1 -0
  192. package/site/defineSite.mjs +3 -0
  193. package/site/home.ts +3 -2
  194. package/site/icons.d.mts +116 -4
  195. package/site/icons.mjs +62 -6
  196. package/site/images.mjs +1 -1
  197. package/site/live-diff.d.mts +22 -0
  198. package/site/live-diff.mjs +98 -0
  199. package/site/live-diff.test.mjs +235 -0
  200. package/site/materialSymbols.d.mts +5 -0
  201. package/site/materialSymbols.mjs +19 -0
  202. package/site/sections.ts +1 -1
  203. package/site/theme.d.mts +1 -1
  204. package/site/theme.mjs +7 -1
  205. package/src/ActionParts.tsx +1 -1
  206. package/src/Advanced.tsx +101 -20
  207. package/src/App.tsx +47 -121
  208. package/src/AppList.tsx +1 -1
  209. package/src/BlockParts.tsx +3 -3
  210. package/src/Colors.tsx +1 -1
  211. package/src/CommunicationParts.tsx +1 -1
  212. package/src/ContainmentParts.tsx +80 -28
  213. package/src/Deleted.tsx +18 -4
  214. package/src/EditorFooter.tsx +14 -0
  215. package/src/EntryForm.tsx +75 -39
  216. package/src/FormFrame.tsx +3 -8
  217. package/src/Library.tsx +46 -51
  218. package/src/Licenses.tsx +27 -11
  219. package/src/MainDrawerParts.tsx +60 -0
  220. package/src/MainMenu.tsx +136 -0
  221. package/src/MarkdownEditor.tsx +175 -32
  222. package/src/Media.tsx +69 -16
  223. package/src/MediaTags.tsx +91 -0
  224. package/src/ModalSheetSample.tsx +47 -0
  225. package/src/NavigationParts.tsx +67 -21
  226. package/src/NavigationRestParts.tsx +1 -1
  227. package/src/Parts.tsx +71 -0
  228. package/src/PartsShown.tsx +1 -1
  229. package/src/PickerParts.tsx +2 -2
  230. package/src/Redirects.tsx +3 -2
  231. package/src/Screen.tsx +93 -90
  232. package/src/SelectionParts.tsx +22 -6
  233. package/src/SiteLogoLink.tsx +14 -0
  234. package/src/StartFab.tsx +22 -0
  235. package/src/StartStatus.tsx +1 -1
  236. package/src/autoTags.d.mts +28 -0
  237. package/src/autoTags.mjs +68 -0
  238. package/src/autoTags.test.mjs +109 -0
  239. package/src/buildSettings.test.mjs +20 -0
  240. package/src/buildSettings.ts +20 -0
  241. package/src/checkSite.ts +14 -1
  242. package/src/deploy.ts +7 -9
  243. package/src/editor.css +150 -17
  244. package/src/eyebrow.d.mts +5 -0
  245. package/src/eyebrow.mjs +53 -0
  246. package/src/eyebrowState.d.mts +21 -0
  247. package/src/eyebrowState.mjs +62 -0
  248. package/src/eyebrowState.test.mjs +78 -0
  249. package/src/fileRecords.d.mts +115 -0
  250. package/src/fileRecords.mjs +224 -0
  251. package/src/fileRecords.test.mjs +224 -0
  252. package/src/fileTagFilter.d.mts +30 -0
  253. package/src/fileTagFilter.mjs +44 -0
  254. package/src/fileTagFilter.test.mjs +57 -0
  255. package/src/fileTags.d.mts +56 -0
  256. package/src/fileTags.mjs +100 -0
  257. package/src/fileTags.test.mjs +127 -0
  258. package/src/firstHeading.d.mts +6 -0
  259. package/src/firstHeading.mjs +37 -0
  260. package/src/i18n/da-DK/advanced.ts +27 -12
  261. package/src/i18n/da-DK/app.ts +9 -7
  262. package/src/i18n/da-DK/apps.ts +16 -16
  263. package/src/i18n/da-DK/blockSettings.ts +9 -9
  264. package/src/i18n/da-DK/blocks.ts +2 -2
  265. package/src/i18n/da-DK/check.ts +5 -2
  266. package/src/i18n/da-DK/cms.ts +14 -12
  267. package/src/i18n/da-DK/colors.ts +5 -5
  268. package/src/i18n/da-DK/common.ts +2 -2
  269. package/src/i18n/da-DK/company.ts +3 -3
  270. package/src/i18n/da-DK/consents.ts +6 -6
  271. package/src/i18n/da-DK/forms.ts +6 -6
  272. package/src/i18n/da-DK/history.ts +6 -6
  273. package/src/i18n/da-DK/keywords.ts +2 -2
  274. package/src/i18n/da-DK/library.ts +17 -15
  275. package/src/i18n/da-DK/licenses.ts +4 -4
  276. package/src/i18n/da-DK/media.ts +10 -4
  277. package/src/i18n/da-DK/parts.ts +62 -25
  278. package/src/i18n/da-DK/preview.ts +5 -6
  279. package/src/i18n/da-DK/redirects.ts +7 -7
  280. package/src/i18n/da-DK/seo.ts +1 -1
  281. package/src/i18n/da-DK/text.ts +2 -0
  282. package/src/i18n/da-DK/theme.ts +5 -5
  283. package/src/i18n/en-US/advanced.ts +34 -18
  284. package/src/i18n/en-US/app.ts +7 -5
  285. package/src/i18n/en-US/apps.ts +9 -9
  286. package/src/i18n/en-US/blockSettings.ts +4 -4
  287. package/src/i18n/en-US/blocks.ts +6 -6
  288. package/src/i18n/en-US/check.ts +6 -3
  289. package/src/i18n/en-US/cms.ts +15 -14
  290. package/src/i18n/en-US/colors.ts +18 -18
  291. package/src/i18n/en-US/common.ts +3 -3
  292. package/src/i18n/en-US/consents.ts +4 -4
  293. package/src/i18n/en-US/forms.ts +5 -5
  294. package/src/i18n/en-US/history.ts +2 -2
  295. package/src/i18n/en-US/library.ts +21 -15
  296. package/src/i18n/en-US/licenses.ts +6 -6
  297. package/src/i18n/en-US/media.ts +7 -1
  298. package/src/i18n/en-US/parts.ts +60 -21
  299. package/src/i18n/en-US/preview.ts +5 -6
  300. package/src/i18n/en-US/redirects.ts +3 -3
  301. package/src/i18n/en-US/seo.ts +1 -1
  302. package/src/i18n/en-US/text.ts +3 -0
  303. package/src/i18n/en-US/theme.ts +6 -6
  304. package/src/icon.tsx +90 -27
  305. package/src/licenseData.test.mjs +28 -0
  306. package/src/licenseData.ts +12 -1
  307. package/src/markdown.d.mts +2 -0
  308. package/src/markdown.mjs +73 -5
  309. package/src/markdown.test.mjs +74 -0
  310. package/src/materialIcons.ts +8 -0
  311. package/src/menuChildren.ts +36 -0
  312. package/src/menuOpen.ts +34 -0
  313. package/src/pagePreview.tsx +23 -19
  314. package/src/r2Caching.ts +14 -0
  315. package/src/sectionFrame.ts +39 -0
  316. package/src/sectionState.d.mts +19 -0
  317. package/src/sectionState.mjs +31 -0
  318. package/src/sectionState.test.mjs +59 -0
  319. package/src/sections.d.mts +9 -7
  320. package/src/sections.mjs +74 -27
  321. package/src/sections.test.mjs +59 -0
  322. package/src/sidebar.ts +2 -2
  323. package/src/site.ts +27 -17
  324. package/src/stillCursor.d.mts +8 -0
  325. package/src/stillCursor.mjs +19 -0
  326. package/src/stillCursor.test.mjs +26 -0
  327. package/src/tagUses.d.mts +47 -0
  328. package/src/tagUses.mjs +121 -0
  329. package/src/tagUses.test.mjs +141 -0
  330. package/src/themeUi.ts +10 -0
  331. package/src/topBarSlot.tsx +37 -0
  332. package/src/ui.tsx +654 -150
  333. package/src/virtual.d.ts +5 -0
  334. package/starter/README.md +2 -1
  335. package/starter/package.json +2 -1
  336. package/starter/wrangler.jsonc +3 -2
  337. package/theme/blocks/cards/LinkCards.tsx +2 -1
  338. package/theme/blocks/hero/View.tsx +2 -1
  339. package/theme/blocks/video/View.tsx +2 -1
  340. package/theme/components/BlogIndex.astro +2 -2
  341. package/theme/components/BlogPosts.astro +1 -1
  342. package/theme/components/BlogPosts.tsx +9 -6
  343. package/theme/components/Container.astro +1 -1
  344. package/theme/components/CookieConsent.astro +1 -1
  345. package/theme/components/EndBlock.astro +1 -1
  346. package/theme/components/Footer.astro +5 -5
  347. package/theme/components/Header.astro +3 -3
  348. package/theme/components/Icon.astro +2 -2
  349. package/theme/components/MobileMenu.tsx +72 -61
  350. package/theme/components/MobileNav.astro +2 -2
  351. package/theme/components/PageHeader.astro +2 -2
  352. package/theme/components/PageSection.astro +1 -1
  353. package/theme/components/PopupButton.tsx +2 -2
  354. package/theme/components/PopupView.tsx +2 -2
  355. package/theme/components/PostList.tsx +7 -3
  356. package/theme/components/RichText.astro +22 -7
  357. package/theme/components/Section.astro +17 -10
  358. package/theme/components/SectionHeading.tsx +1 -1
  359. package/theme/components/home/PageBlock.astro +4 -4
  360. package/theme/components/home/Text.astro +3 -3
  361. package/theme/layouts/PageLayout.astro +3 -3
  362. package/theme/layouts/PostLayout.astro +9 -8
  363. package/theme/styles/base.css +13 -3
  364. package/theme/styles/blocks.css +97 -10
  365. package/theme/styles/components.css +9 -9
  366. package/theme/styles/elevation.test.mjs +168 -0
  367. package/theme/styles/global.css +65 -59
  368. package/theme/styles/ui.css +460 -131
  369. package/theme/templates/Home.astro +1 -1
  370. package/theme/templates/New.astro +5 -5
  371. package/theme/templates/NotFound.astro +2 -2
  372. package/theme/ui/BottomSheet.tsx +118 -21
  373. package/theme/ui/BottomSheetResizeIcon.tsx +13 -0
  374. package/theme/ui/Button.tsx +4 -3
  375. package/theme/ui/Dialog.tsx +1 -1
  376. package/theme/ui/FabMenu.tsx +13 -12
  377. package/theme/ui/LoadingIndicator.tsx +1 -1
  378. package/theme/ui/NavigationDrawer.tsx +116 -15
  379. package/theme/ui/NavigationRail.tsx +22 -13
  380. package/theme/ui/Search.tsx +1 -1
  381. package/theme/ui/SettingRow.tsx +31 -0
  382. package/theme/ui/Switch.tsx +11 -11
  383. package/theme/ui/TextField.tsx +17 -6
  384. package/theme/ui/bottomSheet.script.ts +3 -2
  385. package/theme/ui/bottomSheetControl.ts +131 -46
  386. package/theme/ui/fabMenu.script.ts +69 -53
  387. package/theme/ui/modalSheetSize.ts +214 -0
  388. package/theme/ui/navigationDrawerControl.ts +35 -0
  389. package/theme/ui/search.script.ts +4 -2
  390. package/worker/index.js +3 -0
  391. package/worker/plan-status.js +46 -0
  392. package/worker/push.js +7 -5
  393. package/worker/r2-status.js +2 -1
  394. package/icons/lucide/desktop.svg +0 -1
  395. package/icons/material-symbols/desktop.svg +0 -1
  396. package/icons/phosphor/desktop.svg +0 -1
package/src/Parts.tsx CHANGED
@@ -7,6 +7,9 @@
7
7
  import { useMemo, useRef, useState } from 'react';
8
8
  import {
9
9
  AppWindow,
10
+ Article,
11
+ Eye,
12
+ SlidersHorizontal,
10
13
  Gear,
11
14
  ArrowDown,
12
15
  ArrowsDownUp,
@@ -29,6 +32,8 @@ import {
29
32
  Trash,
30
33
  } from '@phosphor-icons/react';
31
34
  import Screen from './Screen';
35
+ import EditorFooter from './EditorFooter';
36
+ import { MainDrawerSample } from './MainDrawerParts';
32
37
  import { Part, PartGroup, PartsShown, useGrouped, useSearched } from './PartsShown';
33
38
  import {
34
39
  EmptyNote,
@@ -89,6 +94,11 @@ import {
89
94
  ChoiceRow,
90
95
  IconSwatches,
91
96
  ListItem,
97
+ MenuRow,
98
+ SectionList,
99
+ SectionPane,
100
+ SectionSheet,
101
+ SectionsButton,
92
102
  } from './ui';
93
103
  import { FieldList, Fields, useWrongField, WrongField, type FieldContext } from './fields';
94
104
  import { newValue, problemAt } from './check';
@@ -100,6 +110,7 @@ import type { Field } from './schema';
100
110
  import { defaultColors } from '@site/editor';
101
111
  import { BASE_COLORS, canSetBaseColor, wheelColor, wheelColors, wheelDots, withBaseColor, type Dynamic } from '../lib/dynamic-colors';
102
112
  import { iconPacks } from './iconPacks';
113
+ import { jurisdictionName } from './r2Caching';
103
114
  import type { Env } from './App';
104
115
  import { t } from './i18n';
105
116
 
@@ -125,6 +136,7 @@ export default function Parts({ env }: { env: Env }) {
125
136
  const [phone, setPhone] = useState({ code: '+45', number: examples.contact.phone });
126
137
  const [tile, setTile] = useState(0);
127
138
  const [placement, setPlacement] = useState('top');
139
+ const [menuPick, setMenuPick] = useState('auto');
128
140
  const [unfolded, setUnfolded] = useState(false);
129
141
  const [front, setFront] = useState(true);
130
142
  // An e-mail address without its domain: the field says what is wrong.
@@ -134,6 +146,13 @@ export default function Parts({ env }: { env: Env }) {
134
146
  const [picture, setPicture] = useState(examplePicture);
135
147
  const [folded, setFolded] = useState<number | null>(0);
136
148
  const [sectionOpen, setSectionOpen] = useState<number | null>(0);
149
+ const [sectionsSheet, setSectionsSheet] = useState(false);
150
+ const [sectionPicked, setSectionPicked] = useState('content');
151
+ const sectionItems = [
152
+ { id: 'content', label: t.forms.sections.content, icon: Article },
153
+ { id: 'status', label: t.forms.sections.status, icon: Eye, badge: 2 },
154
+ { id: 'other', label: t.forms.otherFields, icon: SlidersHorizontal },
155
+ ];
137
156
  const [tags, setTags] = useState(['a', 'b']);
138
157
  const dialog = useRef<HTMLDialogElement>(null);
139
158
  const fieldsDialog = useRef<HTMLDialogElement>(null);
@@ -423,6 +442,26 @@ export default function Parts({ env }: { env: Env }) {
423
442
  </section>
424
443
  </Part>
425
444
 
445
+ <Part name="MenuRow" use={use.menuRow}>
446
+ <MenuRow
447
+ label={t.advanced.place}
448
+ hint={t.advanced.placeHint}
449
+ value={menuPick}
450
+ options={[
451
+ { value: 'auto', label: t.advanced.placeAuto },
452
+ { value: 'eu', label: jurisdictionName('eu') },
453
+ ]}
454
+ onChange={setMenuPick}
455
+ />
456
+ <MenuRow
457
+ label={t.advanced.place}
458
+ value="auto"
459
+ options={[{ value: 'auto', label: t.advanced.placeAuto }]}
460
+ disabled
461
+ onChange={() => {}}
462
+ />
463
+ </Part>
464
+
426
465
  <Part name="UnfoldButton, subListClass" use={use.unfold}>
427
466
  <ul className={listClass}>
428
467
  <ListItem
@@ -645,6 +684,14 @@ export default function Parts({ env }: { env: Env }) {
645
684
  />
646
685
  </Part>
647
686
 
687
+ <Part name="MainDrawer, MainDrawerSection, MainDrawerItem, MenuButton" use={use.mainDrawer}>
688
+ <MainDrawerSample />
689
+ </Part>
690
+
691
+ <Part name="EditorFooter" use={use.editorFooter}>
692
+ <EditorFooter />
693
+ </Part>
694
+
648
695
  <Part name="FoldSection" use={use.foldSection}>
649
696
  <div>
650
697
  {[t.forms.sections.content, t.forms.otherFields].map((title, i) => (
@@ -660,6 +707,30 @@ export default function Parts({ env }: { env: Env }) {
660
707
  </div>
661
708
  </Part>
662
709
 
710
+ <Part name="SectionList, SectionPane, SectionSheet, SectionsButton" use={use.sectionList}>
711
+ <div className="space-y-4">
712
+ <div className={row}>
713
+ <SectionsButton label={t.forms.sections.title} open={sectionsSheet} onClick={() => setSectionsSheet(true)} />
714
+ </div>
715
+ <div className="max-w-sm">
716
+ <SectionPane title={t.forms.sections.title} open>
717
+ <SectionList title={t.forms.sections.title} items={sectionItems} value={sectionPicked} onChange={setSectionPicked} />
718
+ </SectionPane>
719
+ </div>
720
+ </div>
721
+ <SectionSheet title={t.forms.sections.title} open={sectionsSheet} onClose={() => setSectionsSheet(false)}>
722
+ <SectionList
723
+ title={t.forms.sections.title}
724
+ items={sectionItems}
725
+ value={sectionPicked}
726
+ onChange={(id) => {
727
+ setSectionPicked(id);
728
+ setSectionsSheet(false);
729
+ }}
730
+ />
731
+ </SectionSheet>
732
+ </Part>
733
+
663
734
  <Part name="SharePreview" use={use.sharePreview}>
664
735
  <SharePreview image={examplePicture} domain={sample.domain} title={examples.pages[0]} description={examples.text} />
665
736
  </Part>
@@ -31,7 +31,7 @@ export const useSearched = () => useContext(Shown).query.trim();
31
31
  export const useGrouped = () => useContext(Shown).sort === 'group';
32
32
 
33
33
  /** The search, the order and the filter over the parts (`children`, PartGroup), with "Ingen dele passer
34
- * til søgningen." (or `none`) when none is shown. `keep`: the key they are kept under; `search`: the search's name. */
34
+ * til søgningen" (or `none`) when none is shown. `keep`: the key they are kept under; `search`: the search's name. */
35
35
  export function PartsShown({
36
36
  groups,
37
37
  keep,
@@ -38,7 +38,7 @@ export default function PickerParts() {
38
38
  <Part name="DatePicker" use={say.datePicker}>
39
39
  <Preview>
40
40
  <div className="space-y-6 p-4">
41
- <div className="grid gap-6 sm:grid-cols-2" role="group" aria-label={say.fields}>
41
+ <div className="grid gap-6 medium:grid-cols-2" role="group" aria-label={say.fields}>
42
42
  <SiteDatePicker label={say.date} name="date" />
43
43
  <SiteDatePicker label={say.date} name="date-modal" kind="modal" value={picked} />
44
44
  <SiteDatePicker label={say.period} name="from" endName="to" range value={start} endValue={end} />
@@ -126,7 +126,7 @@ export default function PickerParts() {
126
126
  </Part>
127
127
  <Part name="TimePicker" use={say.timePicker}>
128
128
  <Preview>
129
- <div className="grid gap-6 p-4 sm:grid-cols-2" role="group" aria-label={say.fields}>
129
+ <div className="grid gap-6 p-4 medium:grid-cols-2" role="group" aria-label={say.fields}>
130
130
  <SiteTimePicker label={say.time} name="time" />
131
131
  <SiteTimePicker label={say.time} name="time-set" value="14:30" />
132
132
  <SiteTimePicker label={`${say.time} (${say.twelveHours})`} name="time-12" value="14:30" timeFormat="12h" />
package/src/Redirects.tsx CHANGED
@@ -1,4 +1,4 @@
1
- // "Videresendelser" under Indstillinger › "Søgemaskineoptimering" (#seo/videresendelser): two rows with how many
1
+ // "Videresendelser" under Indhold (#seo/videresendelser): two rows with how many
2
2
  // each has, each opening a screen of its own.
3
3
  // "Alle videresendelser" (#seo/videresendelser/alle): every address of a page the site sends on elsewhere
4
4
  // (redirectData.ts), by address, with where it sends to, and search. Tapping one opens a sheet where
@@ -45,6 +45,7 @@ import { byText } from './schema';
45
45
  import { savedText } from './deploy';
46
46
  import { config, whatIsAt } from '@site/editor';
47
47
  import { siteT, t } from './i18n';
48
+ import { backFrom } from './site';
48
49
 
49
50
  // An address as typed, as one: "/ny-side/", "/" for the front page, a #part kept, the site's own
50
51
  // domain left out; a link to another site as it is. '' when nothing is typed.
@@ -251,7 +252,7 @@ export default function Redirects({ env, tab }: { env: Env; tab?: Tab }) {
251
252
  { tab: 'missing' as const, label: t.redirects.missing.title, icon: LinkBreak, count: missing && files ? stillMissing.length : null },
252
253
  ];
253
254
  return (
254
- <Screen title={t.common.parts.redirects} wide back={{ href: '#seo', label: t.common.parts.seo }}>
255
+ <Screen title={t.common.parts.redirects} wide back={backFrom('videresendelser')}>
255
256
  {problem && <ProblemNote>{problem}</ProblemNote>}
256
257
  <Group>
257
258
  {rows.map((r) => (
package/src/Screen.tsx CHANGED
@@ -1,22 +1,26 @@
1
- // A screen of the editor: a top bar like in an app (a back arrow, the screen's title, actions on the
2
- // right) above the content. The back arrow goes to the screen above, back in the history when that is
3
- // the screen before (nav.ts up), so it goes the same way as the phone's back gesture. Without a back
4
- // arrow (the start screen), the logo is there instead. On a computer the top bar goes across the whole
5
- // window beside the sidebar (which has the logo and the site's name, App.tsx): at the left the way there
6
- // as breadcrumbs (the start screen and the screen above, when that is not the start screen), the title
7
- // and the status after it; at the right the actions, the notifications, the settings and the account, on every screen.
8
- // On a phone the notifications are on every screen too, the settings and the account only on the start screen (`home`). The bar stays at
9
- // the top while scrolling and keeps clear of a phone's notch. A screen's tabs (`tabs`) are the bottom of the bar, under the title,
10
- // as wide as the content, and stay with it. The content is as wide as a form reads
11
- // well, or wider for lists and the grid of files (`wide`) and for a form with its fields beside the text
12
- // (`panel`).
1
+ // A screen of the editor: a top bar like in an app, beside the navigation rail and the drawer (it is drawn into
2
+ // topBarSlot.tsx's place, at the top of the content), above the content. Below medium, where there is no rail, the panel
3
+ // button at its start opens and closes the main drawer (MainMenu.tsx, menuOpen.ts; from medium it is the rail's); at expanded and wider the logo and the site's name after it (one link to the start screen,
4
+ // through `back.guard`), below expanded the back arrow, the title and the status as in an app (the logo only on the start
5
+ // screen); at the end the actions, the notifications and the account, on every screen (on a phone the account only on the
6
+ // start screen, `home`). At expanded and wider the way here as breadcrumbs (the screen above, when that is not the start
7
+ // screen), the title and the status stand in a row under the bar, at the top of the content. The bar stays at the top while
8
+ // scrolling and keeps clear of a phone's notch. A screen's tabs (`tabs`) are the bottom of the bar, under the title, as wide
9
+ // as the content, and stay with it. The back arrow goes to the screen above, back in the history when that is the screen
10
+ // before (nav.ts up). The content is as wide as a form reads well, or wider for lists and the grid of files (`wide`) and for
11
+ // a form with its fields beside the text (`panel`).
12
+ // Under the content of every screen is the editor's footer (EditorFooter.tsx).
13
13
  import { useId, type ReactNode } from 'react';
14
- import { ArrowLeft, Bell, CaretRight, Gear, UserCircle } from '@phosphor-icons/react';
15
- import { settings, siteName } from '@site/editor';
14
+ import { createPortal } from 'react-dom';
15
+ import { ArrowLeft, Bell, CaretRight, UserCircle } from '@phosphor-icons/react';
16
16
  import { t } from './i18n';
17
- import { IconButton, IconLink, TabPanel, TabRow, TopAppBar, type TabChoice } from './ui';
17
+ import { IconLink, MenuButton, TabPanel, TabRow, TopAppBar, useMenuLayout, type TabChoice } from './ui';
18
18
  import { up } from './nav';
19
+ import EditorFooter from './EditorFooter';
20
+ import { useMenuOpen } from './menuOpen';
19
21
  import { unseen, useNotifications } from './notificationData';
22
+ import { SiteLogoLink } from './SiteLogoLink';
23
+ import { useTopBarSlot } from './topBarSlot';
20
24
 
21
25
  // `label` is the screen back goes to, named by the back arrow and the breadcrumbs. `guard` takes every
22
26
  // way out of the screen (back, the breadcrumbs and the logo) and goes it, after warning about changes
@@ -25,9 +29,9 @@ export type Back = { href: string; label: string; guard?: (go: () => void) => vo
25
29
 
26
30
  // `status`: more about the screen, e.g. when it was last saved: under the title, on a computer after it.
27
31
  // `wide`: the content is wider on a computer (lists, the files); `panel`: a form with its other fields
28
- // beside the text on a wide computer screen (FormColumns in ui.tsx). `drawer`: the icon of the screen's navigation
29
- // drawer (NavigationDrawerButton, ModalNavigationDrawer), the last in the bar, after the account. `home`: the start screen,
30
- // with the settings and the account on a phone too. `tabs`: the screen's tabs, in the bar, with the open one's content.
32
+ // beside the text on a wide computer screen (FormColumns in ui.tsx). `drawer`: a button at the end of the
33
+ // bar, after the account (a form's SectionsButton). `home`: the start screen,
34
+ // with the account on a phone too. `tabs`: the screen's tabs, in the bar, with the open one's content.
31
35
  type Props<T extends string> = {
32
36
  title: string;
33
37
  back?: Back;
@@ -56,7 +60,7 @@ export function AccountLink({ className = '' }: { className?: string }) {
56
60
  );
57
61
  }
58
62
 
59
- /** The bell in the top bar, at the left of the gear: the notifications (#notifikationer, Notifications.tsx), with a dot
63
+ /** The bell in the top bar, before the account: the notifications (#notifikationer, Notifications.tsx), with a dot
60
64
  * while some are not seen yet. */
61
65
  export function NotificationsLink({ className = '' }: { className?: string }) {
62
66
  const count = unseen(useNotifications());
@@ -74,24 +78,6 @@ export function NotificationsLink({ className = '' }: { className?: string }) {
74
78
  );
75
79
  }
76
80
 
77
- /** The gear in the top bar, at the left of the account: "Indstillinger" (#indstillinger, Library.tsx Settings), marked
78
- * there and on the settings it opens ("Udseende", "Apps", "Analytics" … and their screens). Only when the site has any. */
79
- export function SettingsLink({ className = '' }: { className?: string }) {
80
- if (!settings.length) return null;
81
- const route = location.hash.slice(1);
82
- const current = route === 'indstillinger' || settings.some((id) => route === id || route.startsWith(`${id}/`));
83
- return (
84
- <IconLink
85
- href="#indstillinger"
86
- icon={Gear}
87
- label={t.common.parts.settings}
88
- aria-current={current ? 'page' : undefined}
89
- selected={current}
90
- className={className}
91
- />
92
- );
93
- }
94
-
95
81
  export default function Screen<T extends string>({
96
82
  title,
97
83
  back,
@@ -105,7 +91,9 @@ export default function Screen<T extends string>({
105
91
  children,
106
92
  }: Props<T>) {
107
93
  const id = useId();
108
- const width = wide ? 'max-w-3xl md:max-w-5xl' : panel ? 'max-w-3xl xl:max-w-6xl' : 'max-w-3xl';
94
+ const menu = useMenuOpen();
95
+ const { menuButtonInTopBar } = useMenuLayout();
96
+ const width = wide ? 'max-w-3xl medium:max-w-5xl' : panel ? 'max-w-3xl large:max-w-6xl' : 'max-w-3xl';
109
97
  // A link out of the screen, through `guard`: the back arrow goes up (nav.ts), the other links as links.
110
98
  const leave = (go?: () => void) => (e: React.MouseEvent<HTMLAnchorElement>) => {
111
99
  if (!go && !back?.guard) return;
@@ -115,62 +103,76 @@ export default function Screen<T extends string>({
115
103
  if (back?.guard) back.guard(way);
116
104
  else way();
117
105
  };
118
- return (
119
- <div className="min-h-dvh">
120
- <TopAppBar>
121
- <div className="flex h-16 items-center gap-1 px-2 md:gap-3 md:px-6">
122
- {back ? (
123
- <a
124
- href={back.href}
125
- onClick={leave(() => up(back.href))}
126
- title={t.common.backTo(back.label)}
127
- aria-label={t.common.backTo(back.label)}
128
- className="flex size-12 shrink-0 items-center justify-center rounded-full hover:bg-tint md:hidden"
129
- >
130
- <ArrowLeft size={24} />
131
- </a>
132
- ) : (
133
- // The start screen: the logo before the name.
134
- <img src="/editor/icon-192.png" alt="" className="mx-2 size-9 shrink-0 rounded-xl md:hidden" />
106
+ const slot = useTopBarSlot();
107
+ // The way here, without the start screen: the logo in the bar is the way there.
108
+ const crumbs = back && back.href !== '#' ? [back] : [];
109
+ const bar = (
110
+ <TopAppBar>
111
+ <div className="flex h-16 items-center gap-1 px-2 medium:gap-3 medium:px-6">
112
+ {menuButtonInTopBar && (
113
+ <MenuButton
114
+ label={menu.open ? t.library.closeMenu : t.library.openMenu}
115
+ open={menu.open}
116
+ onClick={menu.toggle}
117
+ className="shrink-0"
118
+ />
119
+ )}
120
+ <SiteLogoLink onClick={leave()} className={home ? '' : 'hidden expanded:flex'} />
121
+ {back && (
122
+ <a
123
+ href={back.href}
124
+ onClick={leave(() => up(back.href))}
125
+ title={t.common.backTo(back.label)}
126
+ aria-label={t.common.backTo(back.label)}
127
+ className="flex size-12 shrink-0 items-center justify-center rounded-full hover:bg-tint expanded:hidden"
128
+ >
129
+ <ArrowLeft size={24} />
130
+ </a>
131
+ )}
132
+ <div className="flex min-w-0 flex-1 flex-col pl-1 expanded:hidden">
133
+ <h1 className="truncate text-xl leading-tight">{title}</h1>
134
+ {status && (
135
+ <p data-subtitle className="truncate text-xs text-muted">
136
+ {status}
137
+ </p>
135
138
  )}
136
- <div className="flex min-w-0 flex-1 flex-col pl-1 md:hidden">
137
- <h1 className="truncate text-xl leading-tight">{title}</h1>
138
- {status && (
139
- <p data-subtitle className="truncate text-xs text-muted">
140
- {status}
141
- </p>
142
- )}
143
- </div>
144
- {/* On a computer: the way here, the title and the status. */}
145
- <div className="hidden min-w-0 flex-1 items-center gap-x-2 md:flex">
146
- {back && (
147
- <nav aria-label={t.library.breadcrumbs} className="flex shrink-0 items-center gap-2 text-muted">
148
- {(back.href === '#' ? [back] : [{ href: '#', label: siteName }, back]).map((c) => (
149
- <span key={c.href} className="flex items-center gap-2">
150
- <a href={c.href} onClick={leave()} className="max-w-48 truncate hover:text-accent hover:underline">
151
- {c.label}
152
- </a>
153
- <CaretRight size={14} className="shrink-0" />
154
- </span>
155
- ))}
156
- </nav>
157
- )}
158
- <h1 className="truncate font-medium">{title}</h1>
159
- {status && <p className="shrink-0 text-sm text-muted">{status}</p>}
160
- </div>
161
- <div className="flex shrink-0 items-center justify-end gap-1">{actions}</div>
162
- <NotificationsLink />
163
- <SettingsLink className={home ? '' : 'hidden md:inline-grid'} />
164
- <AccountLink className={home ? '' : 'hidden md:inline-grid'} />
165
- {drawer}
166
139
  </div>
167
- {tabs && (
168
- <div className={`mx-auto md:px-6 ${width}`}>
140
+ <div className="hidden min-w-0 flex-1 expanded:block" />
141
+ <div className="flex shrink-0 items-center justify-end gap-1">{actions}</div>
142
+ <NotificationsLink />
143
+ <AccountLink className={home ? '' : 'hidden medium:inline-grid'} />
144
+ {drawer}
145
+ </div>
146
+ {tabs && (
147
+ <div className="editor-bar-tabs">
148
+ <div className={`mx-auto medium:px-6 ${width}`}>
169
149
  <TabRow id={id} {...tabs} />
170
150
  </div>
171
- )}
172
- </TopAppBar>
173
- <main className={`editor-screen mx-auto px-4 pt-6 pb-[calc(6rem+env(safe-area-inset-bottom))] md:px-6 md:pt-8 ${width}`}>
151
+ </div>
152
+ )}
153
+ </TopAppBar>
154
+ );
155
+ return (
156
+ <div className="min-h-dvh">
157
+ {slot ? createPortal(bar, slot) : bar}
158
+ <main className={`editor-screen mx-auto px-4 pt-6 pb-[calc(6rem+env(safe-area-inset-bottom))] medium:px-6 medium:pt-8 ${width}`}>
159
+ {/* At expanded and wider: the way here, the title and the status, under the bar. */}
160
+ <div className="mb-6 hidden min-w-0 items-center gap-x-2 expanded:flex">
161
+ {crumbs.length > 0 && (
162
+ <nav aria-label={t.library.breadcrumbs} className="flex shrink-0 items-center gap-2 text-muted">
163
+ {crumbs.map((c) => (
164
+ <span key={c.href} className="flex items-center gap-2">
165
+ <a href={c.href} onClick={leave()} className="max-w-48 truncate hover:text-accent hover:underline">
166
+ {c.label}
167
+ </a>
168
+ <CaretRight size={14} className="shrink-0" />
169
+ </span>
170
+ ))}
171
+ </nav>
172
+ )}
173
+ <h1 className="truncate font-medium">{title}</h1>
174
+ {status && <p className="shrink-0 text-sm text-muted">{status}</p>}
175
+ </div>
174
176
  {tabs ? (
175
177
  <TabPanel id={id} value={tabs.value}>
176
178
  {children}
@@ -178,6 +180,7 @@ export default function Screen<T extends string>({
178
180
  ) : (
179
181
  children
180
182
  )}
183
+ <EditorFooter />
181
184
  </main>
182
185
  </div>
183
186
  );
@@ -1,10 +1,10 @@
1
- // "Dele" › "UI-komponenter" (BlockParts.tsx), the group "Kontakter, valg og sideark": the theme's switches, radio buttons,
1
+ // "Dele" › "UI-komponenter" (BlockParts.tsx), the group "Kontakter, valg og sideark": the theme's switches, setting rows, radio buttons,
2
2
  // sliders, dividers and side sheets (packages/editor/theme/ui/, through @site/editor) as the site draws them, in every
3
3
  // kind and state (the sliders also centered, in each size, with an icon and vertical), with made-up texts. The
4
4
  // switches, radio buttons and sliders can be tried (a slider's tracks and labels follow it here as on the site);
5
5
  // nothing is saved, and the side sheets are only drawn (on the site a modal one opens over the page with sideSheet()).
6
6
  // On a phone the standard one is drawn without the page beside it, as there is no room for both.
7
- import { SiteButton, SiteDivider, SiteRadioButton, SiteRadioGroup, SiteSideSheet, SiteSlider, SiteSwitch } from './themeUi';
7
+ import { SiteButton, SiteDivider, SiteRadioButton, SiteRadioGroup, SiteSettingRow, SiteSideSheet, SiteSlider, SiteSwitch } from './themeUi';
8
8
  import { Part } from './PartsShown';
9
9
  import { Preview } from './ui';
10
10
  import { t } from './i18n';
@@ -14,7 +14,7 @@ const dividerKinds = ['full-width', 'inset', 'middle-inset'] as const;
14
14
  const sliderSizes = ['xsmall', 'small', 'medium', 'large', 'xlarge'] as const;
15
15
 
16
16
  export default function SelectionParts() {
17
- const grid = 'grid gap-6 p-4 sm:grid-cols-2';
17
+ const grid = 'grid gap-6 p-4 medium:grid-cols-2';
18
18
  const sheet = (kind: 'standard' | 'modal') => (
19
19
  <SiteSideSheet
20
20
  kind={kind}
@@ -44,6 +44,22 @@ export default function SelectionParts() {
44
44
  </div>
45
45
  </Preview>
46
46
  </Part>
47
+ <Part name="SettingRow" use={say.settingRow}>
48
+ <Preview>
49
+ <div className={grid}>
50
+ <SiteSettingRow label={say.switchLabel} supporting={say.switchSupporting}>
51
+ <SiteButton kind="text" size="small">
52
+ {say.choices[0]}
53
+ </SiteButton>
54
+ </SiteSettingRow>
55
+ <SiteSettingRow label={say.switchLabel}>
56
+ <SiteButton kind="text" size="small" disabled>
57
+ {say.choices[1]}
58
+ </SiteButton>
59
+ </SiteSettingRow>
60
+ </div>
61
+ </Preview>
62
+ </Part>
47
63
  <Part name="RadioButton" use={say.radioButton}>
48
64
  <Preview>
49
65
  <div className={grid}>
@@ -69,7 +85,7 @@ export default function SelectionParts() {
69
85
  </Part>
70
86
  <Part name="Slider" use={say.slider}>
71
87
  <Preview>
72
- <div className="grid gap-8 p-4 sm:grid-cols-2">
88
+ <div className="grid gap-8 p-4 medium:grid-cols-2">
73
89
  <SiteSlider label={say.volume} value={40} valueLabel />
74
90
  <SiteSlider label={say.count} max={10} value={6} stops valueLabel="always" />
75
91
  <SiteSlider label={say.price} max={1000} step={50} range={[200, 700]} handleLabels={say.handles} valueLabel="always" />
@@ -77,7 +93,7 @@ export default function SelectionParts() {
77
93
  <SiteSlider label={say.balance} min={-50} max={50} value={20} centered valueLabel />
78
94
  <SiteSlider label={say.balance} min={-50} max={50} step={10} value={-30} centered stops valueLabel="always" />
79
95
  </div>
80
- <div className="grid gap-8 p-4 sm:grid-cols-2">
96
+ <div className="grid gap-8 p-4 medium:grid-cols-2">
81
97
  {sliderSizes.map((size) => (
82
98
  <SiteSlider key={size} label={say.sliderSizes[size]} size={size} value={60} />
83
99
  ))}
@@ -118,7 +134,7 @@ export default function SelectionParts() {
118
134
  <div className="space-y-2">
119
135
  <p className="px-2 text-sm text-muted">{say.sheetKinds.standard}</p>
120
136
  <div className="flex h-80 overflow-hidden">
121
- <p className="hidden min-w-0 flex-1 p-4 text-muted sm:block">{say.page}</p>
137
+ <p className="hidden min-w-0 flex-1 p-4 text-muted medium:block">{say.page}</p>
122
138
  {sheet('standard')}
123
139
  </div>
124
140
  </div>
@@ -0,0 +1,14 @@
1
+ // The logo and the site's name at the start of the top bar, after the menu button (Screen.tsx), and in the modal drawer after its close button (ui.tsx): one link to the start screen.
2
+ // Exposes SiteLogoLink; the link's click goes through `onClick` (Screen's `leave`, so the unsaved-changes warning shows).
3
+ import type { MouseEventHandler } from 'react';
4
+ import { siteName } from '@site/editor';
5
+
6
+ /** The site's logo (/editor/icon-192.png) and the site's name (at expanded and wider; for screen readers below), as a link to `#`. */
7
+ export function SiteLogoLink({ onClick, className = '' }: { onClick?: MouseEventHandler<HTMLAnchorElement>; className?: string }) {
8
+ return (
9
+ <a href="#" onClick={onClick} className={`flex min-w-0 shrink-0 items-center gap-3 font-semibold ${className}`}>
10
+ <img src="/editor/icon-192.png" alt="" className="size-7 shrink-0 rounded-lg" />
11
+ <span className="max-w-48 truncate max-expanded:sr-only">{siteName}</span>
12
+ </a>
13
+ );
14
+ }
@@ -0,0 +1,22 @@
1
+ // The start screen's FAB menu (Start in Library.tsx): a new page and a new blog post, each opening its empty form
2
+ // (#ny/<list>), as the + on its list does. Exposes newOnStart (the actions) and StartFab. Uses FabMenu (ui.tsx) and the
3
+ // collections and icons of site.ts.
4
+ import { Plus } from '@phosphor-icons/react';
5
+ import { t } from './i18n';
6
+ import { collections, icons } from './site';
7
+ import { FabMenu, type FabAction } from './ui';
8
+
9
+ /** The lists a new entry can be made in from the start screen, in this order. */
10
+ const ON_START = ['pages', 'blog'];
11
+
12
+ /** The start screen's actions: the lists of ON_START the site has and can make a new entry in (`create`). */
13
+ export function newOnStart(): FabAction[] {
14
+ return ON_START.flatMap((id) => {
15
+ const c = collections.find((x) => x.id === id);
16
+ return c?.create ? [{ icon: icons[id] ?? Plus, label: c.create, href: `#ny/${id}` }] : [];
17
+ });
18
+ }
19
+
20
+ export function StartFab() {
21
+ return <FabMenu icon={Plus} label={t.library.createNew} items={newOnStart()} />;
22
+ }
@@ -14,7 +14,7 @@ export function StartStatus({ builtAt }: { builtAt: Date | null }) {
14
14
  const updated = builtAt ? t.library.siteUpdated(when(builtAt, { short: true })) : null;
15
15
  return (
16
16
  <>
17
- <span className="expanded:inline hidden">
17
+ <span className="hidden expanded:inline">
18
18
  <CmsVersion />
19
19
  {updated && `${t.library.statusSeparator}${updated}`}
20
20
  </span>
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @typedef {import('./fileRecords.mjs').StoredRecord} StoredRecord
3
+ * @typedef {import('./fileRecords.mjs').RecordChange} RecordChange
4
+ */
5
+ /**
6
+ * @param {string} text
7
+ * @param {Iterable<string>} srcs
8
+ * @returns {string[]}
9
+ */
10
+ export function filesIn(text: string, srcs: Iterable<string>): string[];
11
+ /**
12
+ * @param {string} before
13
+ * @param {string} after
14
+ * @param {Iterable<string>} srcs
15
+ * @returns {string[]}
16
+ */
17
+ export function newlyUsed(before: string, after: string, srcs: Iterable<string>): string[];
18
+ /**
19
+ * @param {StoredRecord[]} records
20
+ * @param {string} folder
21
+ * @param {string[]} srcs
22
+ * @param {string[]} pageTags
23
+ * @param {() => number} [random]
24
+ * @returns {RecordChange[]}
25
+ */
26
+ export function autoTagChanges(records: StoredRecord[], folder: string, srcs: string[], pageTags: string[], random?: () => number): RecordChange[];
27
+ export type StoredRecord = import("./fileRecords.mjs").StoredRecord;
28
+ export type RecordChange = import("./fileRecords.mjs").RecordChange;
@@ -0,0 +1,68 @@
1
+ // The tags a file gets when it is newly used in a page or post (EntryForm.tsx, at "Gem"): every Filer file the saved page refers to and its last saved version did not gets the page's tags,
2
+ // except those removed from the file before (tagsFor), saved in the same commit as the page. Pure, so node --test can run it.
3
+
4
+ import { inherit } from './fileTags.mjs';
5
+ import { recordFor, writeRecords } from './fileRecords.mjs';
6
+
7
+ /**
8
+ * @typedef {import('./fileRecords.mjs').StoredRecord} StoredRecord
9
+ * @typedef {import('./fileRecords.mjs').RecordChange} RecordChange
10
+ */
11
+
12
+ /**
13
+ * @param {string} text
14
+ * @param {Iterable<string>} srcs
15
+ * @returns {string[]}
16
+ */
17
+ export function filesIn(text, srcs) {
18
+ const results = [];
19
+ for (const src of srcs) {
20
+ if (results.includes(src)) continue;
21
+ const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
+ const regex = new RegExp(`${escaped}(?![\\w.-])`);
23
+ if (regex.test(text)) {
24
+ results.push(src);
25
+ }
26
+ }
27
+ return results;
28
+ }
29
+
30
+ /**
31
+ * @param {string} before
32
+ * @param {string} after
33
+ * @param {Iterable<string>} srcs
34
+ * @returns {string[]}
35
+ */
36
+ export function newlyUsed(before, after, srcs) {
37
+ const srcArray = Array.from(srcs);
38
+ const beforeSet = new Set(filesIn(before, srcArray));
39
+ return filesIn(after, srcArray).filter((src) => !beforeSet.has(src));
40
+ }
41
+
42
+ /**
43
+ * @param {StoredRecord[]} records
44
+ * @param {string} folder
45
+ * @param {string[]} srcs
46
+ * @param {string[]} pageTags
47
+ * @param {() => number} [random]
48
+ * @returns {RecordChange[]}
49
+ */
50
+ export function autoTagChanges(records, folder, srcs, pageTags, random) {
51
+ if (pageTags.length === 0 || srcs.length === 0) {
52
+ return [];
53
+ }
54
+
55
+ const updates = [];
56
+ const seen = new Set();
57
+ for (const src of srcs) {
58
+ if (seen.has(src)) continue;
59
+ seen.add(src);
60
+ const record = recordFor(records, src);
61
+ updates.push({ src, next: inherit(record?.record, src, pageTags) });
62
+ }
63
+
64
+ if (random) {
65
+ return writeRecords(records, folder, updates, random);
66
+ }
67
+ return writeRecords(records, folder, updates);
68
+ }