@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/Media.tsx CHANGED
@@ -9,6 +9,7 @@
9
9
  // saved with the tick: the file moves, the pages that use it get the new address in the same commit,
10
10
  // and the old address sends on to it (addresses.ts). "+" uploads a new file, saved at once. The same
11
11
  // pictures (not the other files) are the gallery a picture is chosen from in the forms (useMediaPicker).
12
+ // The funnel in Filer also filters by a tag (fileTagFilter.mjs; only tags some file has).
12
13
  // The clock in the top bar, "Slettede filer", opens the files that were deleted (Deleted.tsx).
13
14
  import { useEffect, useMemo, useRef, useState, type ReactElement } from 'react';
14
15
  import {
@@ -41,10 +42,11 @@ import {
41
42
  BottomSheet,
42
43
  SheetTitle,
43
44
  SideSheet,
44
- useWide,
45
+ useAtLeast,
45
46
  EmptyNote,
46
47
  SheetBody,
47
48
  ListItem,
49
+ type Pickable,
48
50
  } from './ui';
49
51
  import type { Env } from './App';
50
52
  import type { RepoFile } from './github';
@@ -54,6 +56,9 @@ import { collections } from './site';
54
56
  import { readFields, splitFrontmatter } from './frontmatter.mjs';
55
57
  import { fileKindOf, pickedKindOf, pickImage, prepareFile, prepareImage, publicPath, showMoved, shownAt, type Uploads } from './images';
56
58
  import { fileIcons, FileView, KINDS, kindOf, type Kind } from './FileView';
59
+ import MediaTags from './MediaTags';
60
+ import { deleteRecord, recordsIn, renameRecord } from './fileRecords.mjs';
61
+ import { tagFilterOptions, tagIndex, withTag } from './fileTagFilter.mjs';
57
62
  import { kindAndName, savedText } from './deploy';
58
63
  import { locale, siteT, t } from './i18n';
59
64
 
@@ -72,6 +77,9 @@ export const filesOf = (files: RepoFile[]) =>
72
77
  ? files.filter((f) => f.path.startsWith(`${FILES}/`) && fileKindOf(f.path)).sort((a, b) => a.path.localeCompare(b.path, locale))
73
78
  : [];
74
79
 
80
+ /** The addresses on the site of every file Filer lists: the pictures, then the PDFs, videos and zip files. */
81
+ export const filerAddresses = (files: RepoFile[]): string[] => [...mediaOf(files), ...filesOf(files)].map((f) => publicPath(f.path));
82
+
75
83
  // What a file that uses a picture or another file is called: a page's or post's title, a fixed page's name, an
76
84
  // entry's as its list shows it (a pop-up); null for a file the editor does not open (the list of
77
85
  // picture sizes, image-manifest.json).
@@ -171,7 +179,7 @@ function Grid({ srcs, picked, onPick }: { srcs: string[]; picked?: string | null
171
179
  const [thumbs, setThumbs] = useState<Record<string, string> | null>(null);
172
180
  useEffect(() => void thumbnails().then(setThumbs), []);
173
181
  return (
174
- <ul className="grid grid-cols-3 gap-1.5 sm:grid-cols-4 md:grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] md:gap-2">
182
+ <ul className="grid grid-cols-3 gap-1.5 medium:grid-cols-4 expanded:grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] expanded:gap-2">
175
183
  {srcs.map((src) => (
176
184
  <li key={src}>
177
185
  <button
@@ -200,15 +208,20 @@ const monthOf = (src: string) => {
200
208
  };
201
209
  /** The pictures with search, their order and the filters, and the month between them when they are
202
210
  * in the order of their dates, the first letter when by name. used: the files a page uses (Filer): then
203
- * the funnel opens a sheet with both filters, by use and by type. picked: the file shown beside it. */
211
+ * the funnel opens a sheet with the filters by use and by type, and by tag when tags is given (each file's
212
+ * tags, from tagIndex) and some file has one of the tagOptions. picked: the file shown beside it. */
204
213
  function Gallery({
205
214
  srcs,
206
215
  used,
216
+ tags,
217
+ tagOptions = [],
207
218
  picked,
208
219
  onPick,
209
220
  }: {
210
221
  srcs: string[];
211
222
  used?: Set<string>;
223
+ tags?: Map<string, string[]>;
224
+ tagOptions?: Pickable[];
212
225
  picked?: string | null;
213
226
  onPick: (src: string) => void;
214
227
  }) {
@@ -226,6 +239,7 @@ function Gallery({
226
239
  });
227
240
  const [date, setDate] = useKept(all ? 'filer-dato' : null, '');
228
241
  const [use, setUse] = useKept<'' | 'used' | 'unused'>(all ? 'filer-brug' : null, '');
242
+ const [tag, setTag] = useKept(all ? 'filer-tag' : null, '');
229
243
 
230
244
  function pickSort(next: Sort) {
231
245
  setSort(next);
@@ -239,12 +253,16 @@ function Gallery({
239
253
  const years = [...new Set(months.filter(Boolean).map((m) => m.slice(0, 4)))];
240
254
  // The types there are files of (and the one chosen); the filter by type only when there are two or more.
241
255
  const kinds = KINDS.filter((k) => k === kind || srcs.some((src) => kindOf(src) === k));
256
+ // The tags some file has (and the one chosen), for the filter by tag.
257
+ const tagChoices = tags ? tagFilterOptions(tags, tagOptions, tag) : [];
258
+ const withTagged = tags ? new Set(withTag(srcs, tags, tag)) : null;
242
259
  const words = query.toLowerCase().split(/\s+/).filter(Boolean);
243
260
  const shown = srcs
244
261
  .filter((src) => words.every((w) => nameOfSrc(src).toLowerCase().includes(w)))
245
262
  .filter((src) => !date || monthOf(src) === date || monthOf(src).startsWith(`${date}-`))
246
263
  .filter((src) => !use || used?.has(src) === (use === 'used'))
247
264
  .filter((src) => !kind || kindOf(src) === kind)
265
+ .filter((src) => !withTagged || withTagged.has(src))
248
266
  .sort((a, b) =>
249
267
  sort === 'name'
250
268
  ? nameOfSrc(a).localeCompare(nameOfSrc(b), locale)
@@ -256,6 +274,7 @@ function Gallery({
256
274
  date && { label: dateName(date), clear: () => setDate('') },
257
275
  use && { label: use === 'used' ? t.media.inUse : t.common.notInUse, clear: () => setUse('') },
258
276
  kind && { label: t.media.kinds[kind].plural, clear: () => setKind('') },
277
+ tag && { label: tagOptions.find((o) => o.value === tag)?.label ?? tag, clear: () => setTag('') },
259
278
  ].filter((c) => !!c);
260
279
 
261
280
  return (
@@ -277,7 +296,7 @@ function Gallery({
277
296
  all && (
278
297
  <Filters
279
298
  title={t.media.filter}
280
- selected={!!use || !!kind}
299
+ selected={!!use || !!kind || !!tag}
281
300
  groups={[
282
301
  {
283
302
  title: t.media.use,
@@ -298,6 +317,12 @@ function Gallery({
298
317
  ],
299
318
  onChange: (v: string) => setKind(v as typeof kind),
300
319
  },
320
+ tagChoices.length > 0 && {
321
+ title: t.media.tagFilter,
322
+ value: tag,
323
+ options: [{ value: '', label: t.media.allTags }, ...tagChoices],
324
+ onChange: setTag,
325
+ },
301
326
  ].filter((g) => !!g)}
302
327
  />
303
328
  )
@@ -393,7 +418,8 @@ const codeImages = () =>
393
418
  .then((r) => (r.ok ? (r.json() as Promise<string[]>) : null))
394
419
  .catch(() => null));
395
420
 
396
- export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
421
+ // open: the address of a file to show picked at once (the route filer/<address>); ignored when no such file exists.
422
+ export default function Media({ files, env, open }: { files: RepoFile[]; env: Env; open?: string }) {
397
423
  // The pictures and the other files.
398
424
  const pictures = [...mediaOf(files), ...filesOf(files)];
399
425
  const [code, setCode] = useState<string[] | null>(null);
@@ -406,10 +432,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
406
432
  .map((f) => f.text);
407
433
  return new Set(pictures.map((p) => publicPath(p.path)).filter((src) => code?.includes(src) || texts.some((t) => t.includes(src))));
408
434
  }, [files, code]);
435
+ // Each file's tags, for the filter by tag.
436
+ const tags = useMemo(() => tagIndex(recordsIn(files, config.fileRecords)), [files]);
409
437
  const dialog = useRef<HTMLDialogElement>(null);
410
- const [src, setSrc] = useState<string | null>(null);
438
+ const opened = open && pictures.some((p) => publicPath(p.path) === open) ? open : null;
439
+ const [src, setSrc] = useState<string | null>(opened);
411
440
  // On a computer the file picked is beside the grid; on a phone in a sheet.
412
- const wide = useWide();
441
+ const wide = useAtLeast('expanded');
413
442
  const closeFile = () => {
414
443
  if (dialog.current?.open) dialog.current.close();
415
444
  else setSrc(null);
@@ -424,9 +453,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
424
453
  dialog.current.close();
425
454
  } else if (!wide && !dialog.current?.open) dialog.current?.showModal();
426
455
  }, [wide]);
456
+ // Opened at a file on a phone: the sheet opens with the screen.
457
+ useEffect(() => {
458
+ if (opened && !wide) dialog.current?.showModal();
459
+ }, []);
427
460
  const [adding, setAdding] = useState(false);
428
461
  const [deleting, setDeleting] = useState(false);
429
- const [name, setName] = useState('');
462
+ const [name, setName] = useState(opened ? stemOf(opened) : '');
430
463
  const [renaming, setRenaming] = useState(false);
431
464
  // The name being changed: the field shown instead of the pencil after the name.
432
465
  const [editing, setEditing] = useState(false);
@@ -463,7 +496,7 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
463
496
  }
464
497
  }
465
498
 
466
- // A file nothing uses, deleted at once in its own commit (after asking).
499
+ // A file nothing uses, deleted at once in its own commit (after asking), with its record of tags.
467
500
  async function remove(src: string) {
468
501
  const file = pictures.find((p) => publicPath(p.path) === src);
469
502
  if (!file || !(await ask(t.media.kinds[kindOf(src)].deleteAsk, t.common.delete))) return;
@@ -471,12 +504,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
471
504
  env.notify({ text: t.common.deleting });
472
505
  try {
473
506
  const change = t.app.changes.deleted(kindAndName(t.media.kinds[kindOf(src)].word, fileName(src)));
507
+ const record = deleteRecord(recordsIn(files, config.fileRecords), src);
474
508
  await env.commit(
475
509
  siteT.media.commit.delete(siteT.media.kinds[kindOf(src)].word, src.split('/').pop()!),
476
- [{ path: file.path, content: null, oid: file.oid }],
510
+ [{ path: file.path, content: null, oid: file.oid }, ...(record ? [record] : [])],
477
511
  change,
478
512
  );
479
- env.forget([file.path]);
513
+ env.forget([file.path, ...(record ? [record.path] : [])]);
480
514
  closeFile();
481
515
  env.notify({ text: savedText(change), kind: 'saved' });
482
516
  } catch (e) {
@@ -487,7 +521,8 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
487
521
  }
488
522
 
489
523
  // A new name for a picture, saved at once in one commit: the file under its new name (same folder
490
- // and ending), the old one deleted, the texts with its address changed and the redirect.
524
+ // and ending), the old one deleted, the texts with its address changed, the redirect and the file's record of tags (same
525
+ // record file, its address changed).
491
526
  async function rename(src: string) {
492
527
  const file = pictures.find((p) => publicPath(p.path) === src);
493
528
  const stem = slugify(name);
@@ -496,8 +531,16 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
496
531
  const to = publicPath(path);
497
532
  if (files.some((f) => f.path === path)) return env.notify({ text: t.media.nameTaken, error: true });
498
533
  const texts = files
499
- .filter((f) => f.text && f.path !== REDIRECTS && !f.path.startsWith(`${MEDIA}/`) && f.text.includes(src))
534
+ .filter(
535
+ (f) =>
536
+ f.text &&
537
+ f.path !== REDIRECTS &&
538
+ !f.path.startsWith(`${MEDIA}/`) &&
539
+ !f.path.startsWith(`${config.fileRecords}/`) &&
540
+ f.text.includes(src),
541
+ )
500
542
  .map((f) => ({ ...f, text: readdress(f.text, src, to) }));
543
+ const record = renameRecord(recordsIn(files, config.fileRecords), src, to);
501
544
  setRenaming(true);
502
545
  env.notify({ text: t.common.saving });
503
546
  try {
@@ -520,13 +563,20 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
520
563
  { path: file.path, content: null, oid: file.oid },
521
564
  ...texts.map((f) => ({ path: f.path, content: f.text, oid: files.find((x) => x.path === f.path)!.oid })),
522
565
  ...redirectChanges,
566
+ ...(record ? [record] : []),
523
567
  ],
524
568
  change,
525
569
  );
526
570
  showMoved(src, to);
527
571
  env.onSaved(
528
572
  { path, oid: oids[path], text: '' },
529
- { files: [...texts, ...moved].map((f) => ({ ...f, oid: oids[f.path] })), removed: [file.path] },
573
+ {
574
+ files: [...texts, ...moved, ...(record ? [{ path: record.path, oid: '', text: String(record.content) }] : [])].map((f) => ({
575
+ ...f,
576
+ oid: oids[f.path],
577
+ })),
578
+ removed: [file.path],
579
+ },
530
580
  );
531
581
  setSrc(to);
532
582
  setName(stem);
@@ -615,6 +665,7 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
615
665
  <p className="text-muted">{t.media.noPages}</p>
616
666
  )}
617
667
  </div>
668
+ <MediaTags key={src} src={src} files={files} env={env} />
618
669
  {code && !used.has(src) && (
619
670
  <Button kind="outlined" wide icon={Trash} disabled={deleting} onClick={() => remove(src)}>
620
671
  {deleting ? t.common.deleting : t.media.kinds[kindOf(src)].delete}
@@ -640,11 +691,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
640
691
  </>
641
692
  }
642
693
  >
643
- <div className="md:flex md:items-start md:gap-6">
694
+ <div className="expanded:flex expanded:items-start expanded:gap-6">
644
695
  <div className="min-w-0 flex-1">
645
696
  <Gallery
646
- srcs={pictures.map((p) => publicPath(p.path))}
697
+ srcs={filerAddresses(files)}
647
698
  used={used}
699
+ tags={tags}
700
+ tagOptions={env.relations.categories}
648
701
  picked={wide ? src : undefined}
649
702
  onPick={(src) => {
650
703
  setSrc(src);
@@ -0,0 +1,91 @@
1
+ // The tags of a file in Filer (Media.tsx): its tags as chips and "Tilføj tag", from the file's record (src/fileRecords.mjs).
2
+ // Each change is saved at once in its own commit, one save at a time; taps during a save go into the next.
3
+ // Exposes MediaTags; uses MultiChoice and SheetLink (ui.tsx), setTags (fileTags.mjs), writeRecords (fileRecords.mjs).
4
+ import { useRef, useState, type ReactElement } from 'react';
5
+ import { config } from '@site/editor';
6
+ import type { Env } from './App';
7
+ import { kindAndName, savedText } from './deploy';
8
+ import { leaveTo } from './FormFrame';
9
+ import { kindOf } from './FileView';
10
+ import { recordFor, recordsIn, writeRecords, type StoredRecord } from './fileRecords.mjs';
11
+ import { setTags } from './fileTags.mjs';
12
+ import type { RepoFile } from './github';
13
+ import { siteT, t } from './i18n';
14
+ import { MultiChoice, SheetLink } from './ui';
15
+
16
+ const fileName = (src: string) => src.split('/').pop() ?? '';
17
+
18
+ export default function MediaTags({ src, files, env }: { src: string; files: RepoFile[]; env: Env }): ReactElement {
19
+ // The record as last stored: from the files at first, then from each save's result (the files may not have updated yet).
20
+ const stored = useRef<StoredRecord | undefined>(recordFor(recordsIn(files, config.fileRecords), src));
21
+ const [value, setValue] = useState<string[]>(stored.current?.record.categories ?? []);
22
+ const running = useRef(false);
23
+ const waiting = useRef<string[] | null>(null);
24
+
25
+ // One commit for the tags `next`; throws when it fails.
26
+ async function save(next: string[]) {
27
+ const record = setTags(stored.current?.record, src, next);
28
+ const [change] = writeRecords(stored.current ? [stored.current] : [], config.fileRecords, [{ src, next: record }]);
29
+ if (!change) return;
30
+ const word = t.media.kinds[kindOf(src)].word;
31
+ const text = t.app.changes.edited(kindAndName(word, fileName(src)));
32
+ const oids = await env.commit(siteT.media.commit.tags(siteT.media.kinds[kindOf(src)].word, fileName(src)), [change], text);
33
+ if (change.content === null) {
34
+ stored.current = undefined;
35
+ env.forget([change.path]);
36
+ } else {
37
+ stored.current = { path: change.path, oid: oids[change.path], record };
38
+ env.onSaved({ path: change.path, oid: oids[change.path], text: change.content });
39
+ }
40
+ env.notify({ text: savedText(text), kind: 'saved' });
41
+ }
42
+
43
+ // Saves the waiting value, then the next one that came meanwhile; on an error the chips go back to what is stored.
44
+ async function drain() {
45
+ running.current = true;
46
+ while (waiting.current) {
47
+ const next = waiting.current;
48
+ waiting.current = null;
49
+ try {
50
+ await save(next);
51
+ } catch (e) {
52
+ waiting.current = null;
53
+ setValue(stored.current?.record.categories ?? []);
54
+ env.notify({ text: t.common.notSaved((e as Error).message), error: true, kind: 'failed' });
55
+ }
56
+ }
57
+ running.current = false;
58
+ }
59
+
60
+ function change(next: string[]) {
61
+ setValue(next);
62
+ waiting.current = next;
63
+ if (!running.current) void drain();
64
+ }
65
+
66
+ return (
67
+ <div>
68
+ <p className="mb-1 font-medium">{t.media.tags}</p>
69
+ <MultiChoice
70
+ label={t.media.tags}
71
+ options={env.relations.categories}
72
+ value={value}
73
+ add={t.media.addTag}
74
+ keep="categories"
75
+ footer={(close) => (
76
+ <SheetLink
77
+ href="#ny/kategorier"
78
+ onClick={(e) => {
79
+ e.preventDefault();
80
+ close();
81
+ leaveTo('#ny/kategorier');
82
+ }}
83
+ >
84
+ {t.media.createTag}
85
+ </SheetLink>
86
+ )}
87
+ onChange={change}
88
+ />
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,47 @@
1
+ // "Dele" › "UI-komponenter": the modal bottom sheet example (ContainmentParts.tsx). A button opens the theme's modal
2
+ // sheet in a real <dialog>, as the site does (bottomSheet() in bottomSheetControl.ts), so on a phone it opens at half
3
+ // height (startAt), is pulled up to full height and pulled down to close. `wide` is what it becomes from the medium
4
+ // breakpoint (BottomSheet's `wide`: a dialog, a side sheet or a sheet), `label` what the button says.
5
+ // Exposes ModalSheetSample; uses SiteBottomSheet.
6
+ import { useEffect, useRef } from 'react';
7
+ import { SiteBottomSheet, SiteButton, siteBottomSheet, siteBottomSheetClass } from './themeUi';
8
+ import { t } from './i18n';
9
+
10
+ const say = t.parts.screens.containment;
11
+
12
+ export default function ModalSheetSample({
13
+ startAt,
14
+ wide,
15
+ label,
16
+ }: {
17
+ startAt?: 'half' | 'full';
18
+ wide?: 'dialog' | 'side-sheet' | 'sheet';
19
+ label?: string;
20
+ }) {
21
+ const dialog = useRef<HTMLDialogElement>(null);
22
+ const controls = useRef<ReturnType<typeof siteBottomSheet>>(undefined);
23
+ useEffect(() => {
24
+ if (dialog.current) controls.current = siteBottomSheet(dialog.current);
25
+ }, []);
26
+ return (
27
+ <>
28
+ <SiteButton onClick={() => controls.current?.open()}>{label ?? say.openSheet}</SiteButton>
29
+ <dialog ref={dialog} className={siteBottomSheetClass} aria-label={say.heading}>
30
+ <SiteBottomSheet
31
+ startAt={startAt}
32
+ wide={wide}
33
+ headline={say.heading}
34
+ supportingText={say.sheetText}
35
+ closeLabel={say.close}
36
+ footer={say.footer}
37
+ >
38
+ {say.paragraphs.map((text) => (
39
+ <p key={text} className="mb-3 px-5 pt-3 text-muted">
40
+ {text}
41
+ </p>
42
+ ))}
43
+ </SiteBottomSheet>
44
+ </dialog>
45
+ </>
46
+ );
47
+ }
@@ -4,7 +4,8 @@
4
4
  // in the middle, with a subtitle, a long headline and collapsed), a menu (the header's pages, and actions in both
5
5
  // colours with their icons, shortcuts, texts, ticks, a choice, a disabled one, dividers, groups with a gap and with a
6
6
  // line, and a submenu drawn open), a dropdown menu closed and drawn open, the exposed dropdown menu in both kinds in
7
- // every state, the navigation drawer with an item's sub-pages unfolded, the tabs (only the text, as the bar of
7
+ // every state, the navigation drawer with an item's sub-pages unfolded, and the standard one with sections, icons and counts,
8
+ // in two levels (items with a trailing arrow, and the back row over a list), the tabs (only the text, as the bar of
8
9
  // "Mobilmenu", and primary and secondary, fixed and scrollable, with icons over and beside their labels, badges, and a
9
10
  // tab list with a tab that cannot be chosen), and the FABs and extended FABs in their colours and sizes, an extended
10
11
  // FAB also collapsed. Only drawn: the links lead nowhere, and nothing opens.
@@ -21,8 +22,10 @@ import {
21
22
  SiteMenuGroup,
22
23
  SiteMenuItem,
23
24
  SiteNavigationDrawer,
25
+ SiteNavigationDrawerBack,
24
26
  SiteNavigationDrawerItem,
25
27
  SiteNavigationDrawerRow,
28
+ SiteNavigationDrawerSection,
26
29
  SiteNavigationDrawerSubitem,
27
30
  SiteNavigationDrawerToggle,
28
31
  SiteSearch,
@@ -40,6 +43,9 @@ import { t } from './i18n';
40
43
  const say = t.parts.screens.navigation;
41
44
  const [home, open, parent, contact] = say.pages;
42
45
  const [sub, openSub] = say.subpages;
46
+ const [mail, account] = say.drawerSections;
47
+ const [backLabel] = say.drawerBack;
48
+ const [inbox, messages, favourites, trash, profile] = say.drawerItems;
43
49
  const row = 'flex flex-wrap items-center gap-4';
44
50
  const kinds = ['primary', 'secondary'] as const;
45
51
  const bar = say.appBar;
@@ -204,7 +210,7 @@ export default function NavigationParts() {
204
210
  <SiteMenu groups="divider" aria-label={menus.divider}>
205
211
  {groups()}
206
212
  </SiteMenu>
207
- <div className="pb-24 sm:pr-72">
213
+ <div className="pb-24 medium:pr-72">
208
214
  <SiteMenu aria-label={menus.share}>{groups(true)}</SiteMenu>
209
215
  </div>
210
216
  </div>
@@ -231,7 +237,7 @@ export default function NavigationParts() {
231
237
  {(['outlined', 'filled'] as const).map((kind) => {
232
238
  const options = menus.options.map((label, i) => ({ value: String(i), label, disabled: i === 3 }));
233
239
  return (
234
- <div key={kind} className="grid gap-6 sm:grid-cols-2" aria-label={menus.kinds[kind]} role="group">
240
+ <div key={kind} className="grid gap-6 medium:grid-cols-2" aria-label={menus.kinds[kind]} role="group">
235
241
  <SiteExposedDropdownMenu kind={kind} label={menus.field} options={options} />
236
242
  <SiteExposedDropdownMenu kind={kind} label={menus.field} options={options} value="1" />
237
243
  <SiteExposedDropdownMenu kind={kind} label={menus.field} options={options} value="0" supporting={menus.supporting} />
@@ -245,25 +251,65 @@ export default function NavigationParts() {
245
251
  </Part>
246
252
  <Part name="NavigationDrawer" use={say.navigationDrawer}>
247
253
  <Preview>
248
- <SiteNavigationDrawer className="px-3 py-4">
249
- <SiteNavigationDrawerRow>
250
- <SiteNavigationDrawerItem href="">{home}</SiteNavigationDrawerItem>
251
- </SiteNavigationDrawerRow>
252
- <SiteNavigationDrawerRow data-active="">
253
- <SiteNavigationDrawerItem href="" active>
254
- {parent}
254
+ <div className="flex flex-wrap items-start gap-6">
255
+ <SiteNavigationDrawer>
256
+ <SiteNavigationDrawerRow>
257
+ <SiteNavigationDrawerItem href="">{home}</SiteNavigationDrawerItem>
258
+ </SiteNavigationDrawerRow>
259
+ <SiteNavigationDrawerRow data-active="">
260
+ <SiteNavigationDrawerItem href="" active>
261
+ {parent}
262
+ </SiteNavigationDrawerItem>
263
+ <SiteNavigationDrawerToggle label={parent} expanded />
264
+ </SiteNavigationDrawerRow>
265
+ <SiteNavigationDrawerSubitem href="">{sub}</SiteNavigationDrawerSubitem>
266
+ <SiteNavigationDrawerSubitem href="" active>
267
+ {openSub}
268
+ </SiteNavigationDrawerSubitem>
269
+ <SiteNavigationDrawerRow>
270
+ <SiteNavigationDrawerItem href="">{contact}</SiteNavigationDrawerItem>
271
+ <SiteNavigationDrawerToggle label={contact} expanded={false} />
272
+ </SiteNavigationDrawerRow>
273
+ </SiteNavigationDrawer>
274
+ <SiteNavigationDrawer variant="standard">
275
+ <SiteNavigationDrawerSection label={mail}>
276
+ <SiteNavigationDrawerItem href="" icon="envelope-simple" badge={24} active>
277
+ {inbox}
278
+ </SiteNavigationDrawerItem>
279
+ <SiteNavigationDrawerItem href="" icon="chats" badge={3}>
280
+ {messages}
281
+ </SiteNavigationDrawerItem>
282
+ <SiteNavigationDrawerItem href="" icon="heart">
283
+ {favourites}
284
+ </SiteNavigationDrawerItem>
285
+ <SiteNavigationDrawerItem href="" icon="trash">
286
+ {trash}
287
+ </SiteNavigationDrawerItem>
288
+ </SiteNavigationDrawerSection>
289
+ <SiteNavigationDrawerSection label={account}>
290
+ <SiteNavigationDrawerItem href="" icon="user">
291
+ {profile}
292
+ </SiteNavigationDrawerItem>
293
+ </SiteNavigationDrawerSection>
294
+ </SiteNavigationDrawer>
295
+ <SiteNavigationDrawer variant="standard">
296
+ <SiteNavigationDrawerItem icon="envelope-simple" trailing="arrow-right" active>
297
+ {mail}
255
298
  </SiteNavigationDrawerItem>
256
- <SiteNavigationDrawerToggle label={parent} expanded />
257
- </SiteNavigationDrawerRow>
258
- <SiteNavigationDrawerSubitem href="">{sub}</SiteNavigationDrawerSubitem>
259
- <SiteNavigationDrawerSubitem href="" active>
260
- {openSub}
261
- </SiteNavigationDrawerSubitem>
262
- <SiteNavigationDrawerRow>
263
- <SiteNavigationDrawerItem href="">{contact}</SiteNavigationDrawerItem>
264
- <SiteNavigationDrawerToggle label={contact} expanded={false} />
265
- </SiteNavigationDrawerRow>
266
- </SiteNavigationDrawer>
299
+ <SiteNavigationDrawerItem icon="user" trailing="arrow-right">
300
+ {account}
301
+ </SiteNavigationDrawerItem>
302
+ </SiteNavigationDrawer>
303
+ <SiteNavigationDrawer variant="standard">
304
+ <SiteNavigationDrawerBack label={backLabel} />
305
+ <SiteNavigationDrawerItem href="" icon="envelope-simple" badge={24} active>
306
+ {inbox}
307
+ </SiteNavigationDrawerItem>
308
+ <SiteNavigationDrawerItem href="" icon="chats">
309
+ {messages}
310
+ </SiteNavigationDrawerItem>
311
+ </SiteNavigationDrawer>
312
+ </div>
267
313
  </Preview>
268
314
  </Part>
269
315
  <Part name="Tabs" use={say.tabs}>
@@ -2,7 +2,7 @@
2
2
  // (packages/editor/theme/ui/, through @site/editor) as the site draws them, with made-up destinations: the navigation bar
3
3
  // with the open one and its badges, with vertical items, horizontal ones and both by its width, the navigation rail
4
4
  // collapsed and expanded with its menu button and FAB, in sections with a count at each destination's end (the
5
- // editor's sidebar), expanded over the content (modal, over its scrim), and one that
5
+ // theme's rail, which sites use), expanded over the content (modal, over its scrim), and one that
6
6
  // hides while it is collapsed, collapsed (opened by a button beside it) and expanded, the search bar and the view it
7
7
  // opens, and the toolbars, docked and floating in both colours, across and vertical (the docked one with buttons as the
8
8
  // site's bar at the bottom of a phone; floating ones also with a FAB, and folded into it). Only drawn: the links lead