@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
@@ -1,11 +1,15 @@
1
- // "Dele" › "UI-komponenter" (BlockParts.tsx), the group "Dialoger, ark og karrusel": the theme's dialog, bottom sheet and carousel
2
- // (packages/editor/theme/ui/, through @site/editor) as the site draws them, with made-up texts and the plain picture: a
3
- // basic dialog, one with an icon, a list and a divider, the container alone (a pop-up's box) and a full-screen one; the
4
- // modal bottom sheet and the standard one collapsed, half expanded and expanded at the bottom of a frame with a page
5
- // behind it (it can be dragged and tapped there, as on the site); and the carousel in each of Material's layouts. Only
6
- // the standard sheet and the carousels do something here; the buttons lead nowhere.
1
+ // "Dele" › "UI-komponenter" (BlockParts.tsx), the group "Dialoger, ark og karrusel": the theme's dialog, bottom sheet and
2
+ // carousel (packages/editor/theme/ui/, through @site/editor) as the site draws them, with made-up texts and the plain
3
+ // picture: a basic dialog, one with an icon, a list and a divider, the container alone (a pop-up's box) and a
4
+ // full-screen one; the modal bottom sheet, three times, one per wide (a button opens it in a real dialog: on a phone at
5
+ // half height, pulled up to full height; on a computer a dialog, a side sheet or a sheet with margins) and the standard
6
+ // one collapsed, half expanded and expanded (and with the resize and collapse buttons) at the bottom of a frame with a
7
+ // page behind it (it can be dragged and tapped there, as on the site; from the medium width they become a side sheet,
8
+ // and one with wide="sheet" stays a sheet); and the carousel in each of Material's
9
+ // layouts. The modal sheet, the standard sheet and the carousels do something here; the other buttons lead nowhere.
7
10
  import type { ReactNode } from 'react';
8
- import { SiteBottomSheet, SiteButton, SiteCarousel, SiteCheckbox, SiteDialog, SiteTextField, bottomSheetCoverClass } from './themeUi';
11
+ import { SiteBottomSheet, SiteButton, SiteCarousel, SiteCheckbox, SiteDialog, SiteTextField } from './themeUi';
12
+ import ModalSheetSample from './ModalSheetSample';
9
13
  import { Part } from './PartsShown';
10
14
  import { Preview } from './ui';
11
15
  import { examplePicture } from './examples';
@@ -44,7 +48,7 @@ function Dialogs() {
44
48
  </>
45
49
  );
46
50
  return (
47
- <div className="grid gap-6 p-4 lg:grid-cols-2">
51
+ <div className="grid gap-6 p-4 expanded:grid-cols-2">
48
52
  <Shown label={say.dialogKinds.basic}>
49
53
  <SiteDialog headline={say.heading} supportingText={say.text} actions={actions} />
50
54
  </Shown>
@@ -87,37 +91,85 @@ function Dialogs() {
87
91
  }
88
92
 
89
93
  function BottomSheets() {
90
- const standard = (state: 'collapsed' | 'half-expanded' | 'expanded', label: string) => (
94
+ // The frame holds the sheet (contain: layout) and is as high as a phone screen to it (a size container).
95
+ const frame = (label: string, sheet: ReactNode) => (
91
96
  <Shown label={label}>
92
- {/* The frame holds the sheet (contain: layout) and is as high as a screen to it (a size container). */}
93
- <div className="[container-type:size] relative h-96 overflow-hidden rounded-xl border border-line [contain:layout]">
97
+ <div className="[container-type:size] relative h-160 overflow-hidden rounded-xl border border-line [contain:layout]">
94
98
  <p className="p-4 text-muted">{say.page}</p>
95
- <SiteBottomSheet
96
- kind="standard"
97
- state={state}
98
- headline={say.heading}
99
- supportingText={say.sheetText}
100
- dragHandleLabel={say.dragHandle}
101
- >
102
- {numbers.slice(0, 4).map((n) => (
103
- <p key={n} className="mb-3 text-muted">
104
- {say.text}
105
- </p>
106
- ))}
107
- </SiteBottomSheet>
99
+ {sheet}
108
100
  </div>
109
101
  </Shown>
110
102
  );
103
+ const texts = (count: number) =>
104
+ Array.from({ length: count }, (_, i) => (
105
+ <p key={i + 1} className="mb-3 text-muted">
106
+ {say.text}
107
+ </p>
108
+ ));
109
+ const standard = (state: 'collapsed' | 'half-expanded' | 'expanded', label: string) =>
110
+ frame(
111
+ label,
112
+ <SiteBottomSheet
113
+ kind="standard"
114
+ state={state}
115
+ headline={say.heading}
116
+ supportingText={say.sheetText}
117
+ peek={say.sheetPeek}
118
+ dragHandleLabel={say.dragHandle}
119
+ >
120
+ {texts(4)}
121
+ </SiteBottomSheet>,
122
+ );
111
123
  return (
112
- <div className="grid gap-6 p-4 lg:grid-cols-2">
124
+ <div className="grid gap-6 p-4 expanded:grid-cols-2">
113
125
  <Shown label={say.sheetKinds.modal}>
114
- <SiteBottomSheet headline={say.heading} supportingText={say.sheetText} closeLabel={say.close} footer={say.footer} height="18rem">
115
- <p className={bottomSheetCoverClass}>{say.text}</p>
116
- </SiteBottomSheet>
126
+ <ModalSheetSample />
127
+ </Shown>
128
+ <Shown label={say.sheetKinds.modalSideSheet}>
129
+ <ModalSheetSample wide="side-sheet" label={say.openSideSheet} />
130
+ </Shown>
131
+ <Shown label={say.sheetKinds.modalSheet}>
132
+ <ModalSheetSample wide="sheet" label={say.openWideSheet} />
117
133
  </Shown>
118
134
  {standard('collapsed', say.sheetKinds.collapsed)}
119
135
  {standard('half-expanded', say.sheetKinds.halfExpanded)}
120
136
  {standard('expanded', say.sheetKinds.expanded)}
137
+ {frame(
138
+ say.sheetKinds.wideSheet,
139
+ <SiteBottomSheet
140
+ kind="standard"
141
+ wide="sheet"
142
+ headline={say.heading}
143
+ supportingText={say.sheetText}
144
+ dragHandleLabel={say.dragHandle}
145
+ >
146
+ {texts(4)}
147
+ </SiteBottomSheet>,
148
+ )}
149
+ {frame(
150
+ say.sheetKinds.fixed,
151
+ <SiteBottomSheet kind="standard" states={['expanded']} dragHandle={false} label={say.sheetLabel}>
152
+ <p className="text-muted">{say.text}</p>
153
+ </SiteBottomSheet>,
154
+ )}
155
+ {frame(
156
+ say.sheetKinds.resizeButton,
157
+ <SiteBottomSheet kind="standard" headline={say.heading} dragHandle={false} resizeLabel={say.resize}>
158
+ {texts(4)}
159
+ </SiteBottomSheet>,
160
+ )}
161
+ {frame(
162
+ say.sheetKinds.collapseButton,
163
+ <SiteBottomSheet
164
+ kind="standard"
165
+ state="expanded"
166
+ headline={say.heading}
167
+ dragHandleLabel={say.dragHandle}
168
+ collapseLabel={say.collapse}
169
+ >
170
+ {texts(12)}
171
+ </SiteBottomSheet>,
172
+ )}
121
173
  </div>
122
174
  );
123
175
  }
package/src/Deleted.tsx CHANGED
@@ -4,12 +4,14 @@
4
4
  // in one commit, under its old name: a page or post at its old address (not when another has it, or
5
5
  // the site sends it on, which it says, and where to), a price or testimonial last in the list. A
6
6
  // file from "Filer" is shown in a sheet first (FileView.tsx; a PDF, a video or a zip file with its icon
7
- // and name over it), with "Gendan" under it.
7
+ // and name over it), with "Gendan" under it, and comes back with its record of tags (fileRecords.mjs) when it
8
+ // was deleted together with it and no live record has its address.
8
9
  import { useEffect, useRef, useState } from 'react';
9
10
  import { ArrowCounterClockwise } from '@phosphor-icons/react';
10
11
  import Screen from './Screen';
11
12
  import { Button, listClass, BottomSheet, SheetTitle, EmptyNote, SheetBody, ProblemNote, ListItem } from './ui';
12
- import { fileKindOf, typeOf } from './images';
13
+ import { fileKindOf, publicPath, typeOf } from './images';
14
+ import { deletedRecordFor, recordsIn } from './fileRecords.mjs';
13
15
  import { fileIcons, FileView, kindOf } from './FileView';
14
16
  import { ask } from './ask';
15
17
  import { REDIRECT_FILES } from './redirectData';
@@ -99,14 +101,26 @@ export default function Deleted({ id, files, env }: { id: string; files: RepoFil
99
101
  const { order: _, ...rest } = JSON.parse(file.text) as Record<string, unknown>;
100
102
  content = `${JSON.stringify({ order: env.nextOrder(collection!), ...rest }, null, 2)}\n`;
101
103
  }
104
+ // A file from "Filer" comes back with its record of tags: the one deleted in the same commit (same time).
105
+ const changes: { path: string; content: string | Uint8Array; oid: string | null }[] = [{ path: file.path, content, oid: null }];
106
+ if (pictures) {
107
+ const deletedRecords = await env.deleted(config.fileRecords, { text: true });
108
+ const record = deletedRecordFor(publicPath(file.path), file.at, deletedRecords, recordsIn(files, config.fileRecords));
109
+ if (record) changes.push({ path: record.path, content: record.text, oid: null });
110
+ }
102
111
  const what = pictures ? siteT.media.kinds[kindOf(file.path)].word : asWritten(collection!).singular;
103
112
  const change = t.app.changes.restored(kindAndName(pictures ? t.media.kinds[kindOf(file.path)].word : collection!.singular, name));
104
113
  const oids = await env.commit(
105
114
  siteT.history.commit.restoreDeleted(what, `"${pictures ? fileName(file.path) : slugOf(file.path, folder)}"`),
106
- [{ path: file.path, content, oid: null }],
115
+ changes,
107
116
  change,
108
117
  );
109
- env.onSaved({ path: file.path, oid: oids[file.path], text: typeof content === 'string' ? content : '' });
118
+ env.onSaved(
119
+ { path: file.path, oid: oids[file.path], text: typeof content === 'string' ? content : '' },
120
+ changes.length > 1
121
+ ? { files: changes.slice(1).map((c) => ({ path: c.path, oid: oids[c.path], text: String(c.content) })), removed: [] }
122
+ : undefined,
123
+ );
110
124
  setDeleted((all) => all?.filter((f) => f.path !== file.path) ?? null);
111
125
  dialog.current?.close();
112
126
  env.notify({ text: savedText(change), kind: 'saved', edit: pictures ? undefined : { to: file.path, text: t.history.open } });
@@ -0,0 +1,14 @@
1
+ // The editor's footer: one line under the content of every screen (Screen.tsx), with a link to "Licenser" (#licenser,
2
+ // Licenses.tsx). Exposes EditorFooter. Uses LinkButton from ui.tsx; the safe area at the bottom is Screen's main.
3
+ import { t } from './i18n';
4
+ import { LinkButton } from './ui';
5
+
6
+ export default function EditorFooter() {
7
+ return (
8
+ <footer className="mt-12 flex justify-center">
9
+ <LinkButton href="#licenser" kind="text" size="small">
10
+ {t.common.parts.licenses}
11
+ </LinkButton>
12
+ </footer>
13
+ );
14
+ }
package/src/EntryForm.tsx CHANGED
@@ -2,8 +2,10 @@
2
2
  // Only what was changed is written: untouched fields keep their exact text, and the body is only
3
3
  // rewritten by Milkdown when the text was edited (packages/editor/scripts/editor-roundtrip.mjs checks that this
4
4
  // gives the same page). A new address (Adresse) moves the page or post, with a redirect from the
5
- // old one (addresses.ts). An earlier version (Versions.tsx) is opened as changes that are not saved,
5
+ // old one (addresses.ts). The Filer files the page newly refers to get the page's tags in the same commit (autoTags.mjs).
6
+ // An earlier version (Versions.tsx) is opened as changes that are not saved,
6
7
  // at the address the page or post has now.
8
+ // The form's sections are listed in a pane (large and wider) or a side sheet (below): SectionList in ui.tsx.
7
9
  import { useEffect, useMemo, useRef, useState } from 'react';
8
10
  import { flushSync } from 'react-dom';
9
11
  import { slugify, slugOf, type Field, type MarkdownCollection } from './schema';
@@ -22,8 +24,11 @@ import { useBlockDialog } from './BlockDialog';
22
24
  import { blockHtml } from '../lib/blocks.mjs';
23
25
  import { sectionBlocks } from './blocks';
24
26
  import { looksOf, pageH1 } from './sections.mjs';
25
- import { shownAt, useUploads } from './images';
26
- import { useMediaPicker } from './Media';
27
+ import { sectionInView, spyLine } from '../lib/section-in-view.mjs';
28
+ import { publicPath, shownAt, useUploads } from './images';
29
+ import { filerAddresses, useMediaPicker } from './Media';
30
+ import { autoTagChanges, newlyUsed } from './autoTags.mjs';
31
+ import { recordsIn } from './fileRecords.mjs';
27
32
  import { inUse, problemAt } from './check';
28
33
  import { isOver } from '../lib/notice.mjs';
29
34
  import { splitPicture } from '../lib/crop.mjs';
@@ -34,11 +39,13 @@ import {
34
39
  CheckList,
35
40
  FoldSection,
36
41
  FormColumns,
37
- ModalNavigationDrawer,
38
- NavigationDrawer,
39
- NavigationDrawerButton,
40
42
  type NavigationItem,
43
+ SectionList,
44
+ SectionPane,
45
+ SectionSheet,
46
+ SectionsButton,
41
47
  SharePreview,
48
+ useAtLeast,
42
49
  useInView,
43
50
  } from './ui';
44
51
  import FormFrame, { savedStatus, useSavedAt } from './FormFrame';
@@ -60,8 +67,8 @@ const SHARE = SHARE_FIELDS;
60
67
  // The sections of the form, under each other on one page, each folding in and out under its heading (FoldSection in
61
68
  // ui.tsx): the text with the title and heading (open from the start), the status, the other fields, those for search
62
69
  // engines, those for social media, and the earlier versions (folded from the start, so the page is short; nothing is remembered till
63
- // the next time). Its navigation drawer (on a wide computer screen in the right column, narrower from the icon at the
64
- // right of the top bar) opens one and scrolls to it, and marks the one in view. A field that keeps the form from being
70
+ // the next time). Its list (on large and wider screens in a pane at the end of the form, narrower in a side sheet from the
71
+ // button at the right of the top bar) opens one and scrolls to it, and marks the one in view. A field that keeps the form from being
65
72
  // saved has its section opened and is scrolled to (FieldInput in fields.tsx).
66
73
  type Section = 'content' | 'status' | 'other' | 'seo' | 'share' | 'versions';
67
74
  const sectionOf = (path: string): Section => {
@@ -102,9 +109,8 @@ export const newMarkdown = (collection: MarkdownCollection, off: string[] = []):
102
109
 
103
110
  // The address of a saved page or post: a page's own `path`, or else /<file name>/ (packages/editor/site/content.ts).
104
111
  // '' for a new one.
105
- // Where the redirects are, as the start screen has it: its group (the site's), or "Indstillinger", › "Søgemaskineoptimering" › "Videresendelser" ›
106
- // "Alle videresendelser".
107
- const redirectsPath = () => [placeOf('seo'), t.common.parts.seo, t.common.parts.redirects, t.redirects.all].filter(Boolean).join(' › ');
112
+ // Where the redirects are, as the menu has it: "Indhold" › "Videresendelser" › "Alle videresendelser".
113
+ const redirectsPath = () => [placeOf('videresendelser'), t.common.parts.redirects, t.redirects.all].filter(Boolean).join(' › ');
108
114
 
109
115
  function addressNow(collection: MarkdownCollection, file: RepoFile, data: Record<string, unknown>) {
110
116
  if (!file.path) return '';
@@ -220,8 +226,11 @@ function Form({
220
226
  (text) => notify({ text, error: true }),
221
227
  (path) => unfold(sectionOf(path)),
222
228
  );
223
- // On a wide computer screen: the navigation drawer beside the form, until its icon puts it away.
224
- const [drawer, setDrawer] = useState(true);
229
+ // On large and wider screens: the sections pane at the end of the form, until its button puts it away; narrower, the
230
+ // sections are in a side sheet, opened by the same button.
231
+ const large = useAtLeast('large');
232
+ const [pane, setPane] = useState(true);
233
+ const [sheet, setSheet] = useState(false);
225
234
  const has = (id: Section) => collection.fields.some(inSection(id));
226
235
  const sections: NavigationItem[] = [
227
236
  { id: 'content', label: t.forms.sections.content, icon: Article },
@@ -232,8 +241,8 @@ function Form({
232
241
  ...(isNew ? [] : [{ id: 'versions', label: t.history.versions, icon: ClockCounterClockwise }]),
233
242
  ];
234
243
  const shows = (id: Section) => sections.some((s) => s.id === id);
235
- // The section in view, marked in the drawer: while the page scrolls, the last one whose top is above a third of the
236
- // screen (at the bottom of the page, the last one in view). A tap in the drawer marks its own and scrolls there; the
244
+ // The section in view, marked in the sections list: while the page scrolls, the last one whose top is above a third of the
245
+ // screen (at the bottom of the page, the last one in view). A tap in the list marks its own and scrolls there; the
237
246
  // mark stays meanwhile (`steering`, until the scroll ends).
238
247
  const [section, setSection] = useState<Section>('content');
239
248
  const boxes = useRef<Partial<Record<Section, HTMLElement | null>>>({});
@@ -249,7 +258,7 @@ function Form({
249
258
  // The sections that are open; at first only the text (and "Søgemaskiner" when it is opened at it, `opening`).
250
259
  const [open, setOpen] = useState<ReadonlySet<Section>>(() => new Set(['content', ...(opening === 'seo' ? ['seo' as const] : [])]));
251
260
  const unfold = (id: Section) => setOpen((was) => (was.has(id) ? was : new Set([...was, id])));
252
- // A section opened by its heading is marked in the drawer; one folded, the one in view now (the page is shorter,
261
+ // A section opened by its heading is marked in the sections list; one folded, the one in view now (the page is shorter,
253
262
  // without a scroll).
254
263
  const toggle = (id: Section) => {
255
264
  const opening = !open.has(id);
@@ -267,14 +276,11 @@ function Form({
267
276
  spy.current = () => {
268
277
  frame = 0;
269
278
  if (performance.now() < steering.current) return;
270
- const bottom = innerHeight + scrollY >= document.documentElement.scrollHeight - 8;
271
- const line = bottom ? innerHeight - 96 : innerHeight / 3;
272
- let here: Section | undefined;
273
- for (const id of order.current) {
274
- const top = boxes.current[id]?.getBoundingClientRect().top;
275
- if (top !== undefined && (here === undefined || top <= line)) here = id;
276
- }
277
- if (here) setSection(here);
279
+ const atBottom = innerHeight + scrollY >= document.documentElement.scrollHeight - 8;
280
+ const tops: Record<string, number | undefined> = {};
281
+ for (const id of order.current) tops[id] = boxes.current[id]?.getBoundingClientRect().top;
282
+ const here = sectionInView(order.current, tops, spyLine(innerHeight, atBottom));
283
+ if (here) setSection(here as Section);
278
284
  };
279
285
  const onScroll = () => {
280
286
  if (!frame) frame = requestAnimationFrame(spy.current);
@@ -285,7 +291,7 @@ function Form({
285
291
  cancelAnimationFrame(frame);
286
292
  };
287
293
  }, []);
288
- // Opens a section and scrolls to it (or to `box` in it: the text), after the drawer on a phone has closed (the page does
294
+ // Opens a section and scrolls to it (or to `box` in it: the text), after the side sheet below large has been told to close (the page does
289
295
  // not scroll while a dialog is open). It is opened at once (flushSync), so it is there to scroll to.
290
296
  const textBox = useRef<HTMLDivElement>(null);
291
297
  const goTo = (id: Section, box?: HTMLElement | null) => {
@@ -301,11 +307,11 @@ function Form({
301
307
  if (shows('seo')) goTo('seo');
302
308
  }, []);
303
309
  // The field marked as wrong is scrolled to by itself (FieldInput), its section opened (useWrongField) and marked in the
304
- // drawer.
310
+ // sections list.
305
311
  useEffect(() => {
306
312
  if (wrongField.field) steer(sectionOf(wrongField.field.path));
307
313
  }, [wrongField.field?.at]);
308
- // A section: where it is, for the drawer, its heading (its name in the drawer), and whether it is open.
314
+ // A section: where it is, for the sections list, its heading (its name in the list), and whether it is open.
309
315
  const sectionProps = (id: Section) => ({
310
316
  ref: (el: HTMLElement | null) => void (boxes.current[id] = el),
311
317
  title: sections.find((s) => s.id === id)!.label,
@@ -362,7 +368,9 @@ function Form({
362
368
  site: sharing,
363
369
  });
364
370
 
365
- // A picture is kept in the browser until "Gem", which uploads it with the page.
371
+ // A picture is kept in the browser until "Gem", which uploads it with the page. A file in Filer the page refers to
372
+ // now and did not in its last saved version (a picture, a PDF field, a link) gets the page's tags at "Gem" (autoTags.mjs).
373
+ const tagged = collection.fields.some((f) => f.name === 'categories' && f.type === 'relation' && f.to === 'categories');
366
374
  const pictures = useUploads(notify);
367
375
  const [pickImage, picker] = useMediaPicker(env.media, pictures);
368
376
 
@@ -525,12 +533,22 @@ function Form({
525
533
  // Pictures that were added and are still used.
526
534
  const used = pictures.usedIn(next);
527
535
  const removed = base.path && path !== base.path ? [base.path] : [];
536
+ // The page's tags to the Filer files it newly refers to (the text as last saved against the text now), in the same commit.
537
+ const recordChanges = tagged
538
+ ? autoTagChanges(
539
+ recordsIn(env.files, config.fileRecords),
540
+ config.fileRecords,
541
+ newlyUsed(isNew ? '' : base.text, next, [...new Set([...filerAddresses(env.files), ...used.map((u) => publicPath(u.path))])]),
542
+ Array.isArray(saved.categories) ? (saved.categories as string[]) : [],
543
+ )
544
+ : [];
528
545
  const changes: Change[] = [
529
546
  { path, content: next, oid: path === base.path ? base.oid || null : null },
530
547
  ...removed.map((p) => ({ path: p, content: null, oid: base.oid })),
531
548
  ...others.map((f) => ({ path: f.path, content: f.text, oid: f.oid })),
532
549
  ...redirectChanges,
533
550
  ...used.map((u) => ({ path: u.path, content: u.bytes, oid: null })),
551
+ ...recordChanges,
534
552
  ];
535
553
  const what = `"${slugOf(path, collection.folder)}"`;
536
554
  const message = moved
@@ -565,6 +583,9 @@ function Form({
565
583
  ...redirectChanges
566
584
  .filter((c) => c.path === REDIRECTS)
567
585
  .map((c) => ({ path: REDIRECTS, oid: oids[REDIRECTS], text: String(c.content) })),
586
+ ...recordChanges
587
+ .filter((c) => typeof c.content === 'string')
588
+ .map((c) => ({ path: c.path, oid: oids[c.path], text: String(c.content) })),
568
589
  ],
569
590
  removed,
570
591
  });
@@ -657,14 +678,11 @@ function Form({
657
678
  status={savedStatus({ savedAt, saving, deleting, isNew })}
658
679
  view={isNew ? undefined : url}
659
680
  drawer={
660
- <>
661
- <div className="contents xl:hidden">
662
- <ModalNavigationDrawer title={t.forms.sections.title} items={sections} value={section} onChange={pick} />
663
- </div>
664
- <div className="hidden xl:contents">
665
- <NavigationDrawerButton label={t.forms.sections.title} open={drawer} onClick={() => setDrawer(!drawer)} />
666
- </div>
667
- </>
681
+ <SectionsButton
682
+ label={t.forms.sections.title}
683
+ open={large ? pane : sheet}
684
+ onClick={() => (large ? setPane(!pane) : setSheet(true))}
685
+ />
668
686
  }
669
687
  dirty={dirty}
670
688
  saving={saving || deleting}
@@ -673,12 +691,10 @@ function Form({
673
691
  onPreview={showPreview}
674
692
  onDelete={collection.deletable && !isNew ? () => env.remove(collection, base, setDeleting) : undefined}
675
693
  deleteLabel={t.forms.deleteOne(collection.singular)}
676
- panel
677
694
  >
678
695
  {version && <VersionNotice version={version} onCurrent={() => onVersion(null)} />}
679
696
  <FormColumns
680
697
  sticky
681
- wideOnly
682
698
  main={
683
699
  <>
684
700
  <FoldSection {...sectionProps('content')}>
@@ -700,6 +716,7 @@ function Form({
700
716
  : undefined
701
717
  }
702
718
  sections={front}
719
+ frame
703
720
  />
704
721
  </div>
705
722
  </FoldSection>
@@ -739,10 +756,29 @@ function Form({
739
756
  )}
740
757
  </>
741
758
  }
742
- side={drawer && <NavigationDrawer title={t.forms.sections.title} items={sections} value={section} onChange={pick} />}
759
+ pane={
760
+ pane ? (
761
+ <SectionPane title={t.forms.sections.title} open>
762
+ <SectionList title={t.forms.sections.title} items={sections} value={section} onChange={pick} />
763
+ </SectionPane>
764
+ ) : undefined
765
+ }
743
766
  />
744
767
  </FormFrame>
745
768
  {blockDialog}
769
+ {!large && (
770
+ <SectionSheet title={t.forms.sections.title} open={sheet} onClose={() => setSheet(false)}>
771
+ <SectionList
772
+ title={t.forms.sections.title}
773
+ items={sections}
774
+ value={section}
775
+ onChange={(id) => {
776
+ setSheet(false);
777
+ pick(id);
778
+ }}
779
+ />
780
+ </SectionSheet>
781
+ )}
746
782
  {picker}
747
783
  {previewSheet}
748
784
  </WrongField.Provider>
package/src/FormFrame.tsx CHANGED
@@ -13,7 +13,6 @@ import { hasUnsaved, setUnsaved } from './deploy';
13
13
  import { askToNotify } from './notificationData';
14
14
  import { choose } from './ask';
15
15
  import { when } from './when';
16
- import { useSidebar } from './sidebar';
17
16
  import type { Env } from './App';
18
17
  import { t } from './i18n';
19
18
 
@@ -33,7 +32,7 @@ type Props<T extends string> = {
33
32
  view?: string;
34
33
  /** More in the top bar, before the link to the site. */
35
34
  actions?: ReactNode;
36
- /** The icon of the form's navigation drawer, the last in the top bar (Screen). */
35
+ /** The button at the end of the top bar, after the account (a form's SectionsButton in ui.tsx). */
37
36
  drawer?: ReactNode;
38
37
  /** The form's tabs, in the top bar (Screen). */
39
38
  tabs?: TabChoice<T>;
@@ -150,8 +149,6 @@ export default function FormFrame<T extends string>({
150
149
  wide,
151
150
  children,
152
151
  }: Props<T>) {
153
- // On a computer the save bar starts where the sidebar (or its rail, sidebar.ts) ends.
154
- const sidebar = useSidebar();
155
152
  const save = useRef(onSave);
156
153
  save.current = onSave;
157
154
  useEffect(() => {
@@ -207,10 +204,8 @@ export default function FormFrame<T extends string>({
207
204
  </div>
208
205
  )}
209
206
  {(dirty || saving) && (
210
- <div
211
- className={`editor-save-bar fixed inset-x-0 bottom-0 z-20 border-t border-line bg-surface/90 pb-[env(safe-area-inset-bottom)] backdrop-blur-md ${sidebar ? 'md:left-64' : 'md:left-18'}`}
212
- >
213
- <div className="mx-auto max-w-3xl px-4 py-3 md:px-6">
207
+ <div className="editor-save-bar fixed inset-x-0 bottom-0 z-20 border-t border-line bg-surface/90 pb-[env(safe-area-inset-bottom)] backdrop-blur-md">
208
+ <div className="mx-auto max-w-3xl px-4 py-3 medium:px-6">
214
209
  {/* "Forhåndsvis" beside "Gem". */}
215
210
  <div className={onPreview ? 'grid grid-cols-[auto_1fr] gap-2' : ''}>
216
211
  {onPreview && (