@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
@@ -4,7 +4,15 @@
4
4
  // on the site, which opens a menu (Ret, hide on the site, move, Fjern), with a free line kept between
5
5
  // two cards to write in. A fixed page's sections are such a text too (SectionsField.tsx), so both look
6
6
  // and work the same.
7
- import { forwardRef, Fragment as ReactFragment, useEffect, useImperativeHandle, useRef, useState } from 'react';
7
+ import {
8
+ forwardRef,
9
+ Fragment as ReactFragment,
10
+ type MouseEvent as ReactMouseEvent,
11
+ useEffect,
12
+ useImperativeHandle,
13
+ useRef,
14
+ useState,
15
+ } from 'react';
8
16
  import { editorViewCtx, type Editor } from '@milkdown/kit/core';
9
17
  import type { CmdKey } from '@milkdown/kit/core';
10
18
  import type { Ctx } from '@milkdown/kit/ctx';
@@ -35,6 +43,7 @@ import {
35
43
  LinkSimple,
36
44
  ListBullets,
37
45
  ListNumbers,
46
+ Palette,
38
47
  Plus,
39
48
  Quotes,
40
49
  TextAa,
@@ -46,7 +55,12 @@ import {
46
55
  TextT,
47
56
  } from '@phosphor-icons/react';
48
57
  import { HTML, makeEditor, markdownOf, partsOf, siteBlockSchema, spacing } from './markdown.mjs';
58
+ import { firstHeading, topHeadings } from './firstHeading.mjs';
59
+ import { showsH1Choice } from '../lib/heading-levels.mjs';
49
60
  import { TEXT_COLORS, colorClass } from '../lib/text-colors.mjs';
61
+ import { SECTION_COLORS } from '../lib/section-marker.mjs';
62
+ import { sectionHeading, setSectionColor } from './sectionState.mjs';
63
+ import { sectionFrame } from './sectionFrame';
50
64
  import { IMAGE, type BlockAsk } from './BlockDialog';
51
65
  import { askLink } from './LinkSheet';
52
66
  import { Menu, MenuItem, Toolbar, ToolbarButton } from './ui';
@@ -59,6 +73,9 @@ import { createRoot } from 'react-dom/client';
59
73
  import { flushSync } from 'react-dom';
60
74
  import { t } from './i18n';
61
75
  import { motionTiming } from '../lib/motion';
76
+ import { stillTimer } from '../lib/still-timer.mjs';
77
+ import { STILL_MS, wordRange } from './stillCursor.mjs';
78
+ import { hasLabel, labelledHeading, toggleLabel } from './eyebrowState.mjs';
62
79
  // The icons of the card's menu (Phosphor, as everywhere in the editor), as SVG: the card is not React.
63
80
  import pencilIcon from '@phosphor-icons/core/assets/regular/pencil-simple.svg?raw';
64
81
  import eyeIcon from '@phosphor-icons/core/assets/regular/eye.svg?raw';
@@ -73,6 +90,7 @@ import h1Icon from '@phosphor-icons/core/assets/regular/text-h-one.svg?raw';
73
90
  import h2Icon from '@phosphor-icons/core/assets/regular/text-h-two.svg?raw';
74
91
  import h3Icon from '@phosphor-icons/core/assets/regular/text-h-three.svg?raw';
75
92
  import paletteIcon from '@phosphor-icons/core/assets/regular/palette.svg?raw';
93
+ import tagIcon from '@phosphor-icons/core/assets/regular/tag.svg?raw';
76
94
  // ProseMirror's own styles: keep its invisible helpers (around pictures, at the end of lines) out of the layout.
77
95
  import '@milkdown/kit/prose/view/style/prosemirror.css';
78
96
 
@@ -82,8 +100,8 @@ export type MarkdownHandle = { markdown: () => string | null; parts: () => Retur
82
100
  // addImage: asks for a picture (the gallery, Media.tsx); its address on the site. Its description is
83
101
  // asked in the sheet. Without it there is no picture button. blocks: asks which block to put in and its fields (BlockDialog);
84
102
  // without it there is no block button; with it a free line is kept between two blocks, and before
85
- // and after them (spacing in markdown.mjs). short: a smaller field, for a short text. h1: the page's own heading, "Overskrift
86
- // (H1)", can be chosen too (a page's text, which has its H1 at the top; a blog post's is its title).
103
+ // and after them (spacing in markdown.mjs). short: a smaller field, for a short text. h1: a page's text, where the
104
+ // first heading is the page's H1 by itself (firstHeading.mjs); a blog post's H1 is its title.
87
105
  type Props = {
88
106
  value: string;
89
107
  onChange: () => void;
@@ -98,6 +116,9 @@ type Props = {
98
116
  /** The headings (H2) are the names of sections (a fixed page's, the front page's: sections.mjs), which the site
99
117
  * draws as text: no colour for them. */
100
118
  sections?: boolean;
119
+ /** Whether the text has the section frame and the colour button (pages and posts). Without it: on unless `sections`.
120
+ * The front page sets `sections` (its H2s are names drawn by the theme, no word colours) and `frame` (its sections take their colour). */
121
+ frame?: boolean;
101
122
  };
102
123
 
103
124
  // A part of the text at the top level, for `looks`.
@@ -446,7 +467,7 @@ async function editLink(view: EditorView) {
446
467
  /** What applies where the cursor is, or to all of the selection: 'strong', 'emphasis', 'link',
447
468
  * 'h2', 'h3', 'text' (a paragraph, not in a list or quote), 'bullet', 'ordered' and 'quote'; with a colour
448
469
  * (text-colors.mjs) 'textColor' and the colour's class ('color-accent'). For the toolbar and the selection's menu,
449
- * which mark them. */
470
+ * 'section' and 'section-<colour>' in a top-level H2 (sectionState.mjs), which mark them. */
450
471
  export function activeOf(state: EditorView['state']): Set<string> {
451
472
  const { selection, storedMarks, schema } = state;
452
473
  const { $from, from, to, empty } = selection;
@@ -463,6 +484,11 @@ export function activeOf(state: EditorView['state']): Set<string> {
463
484
  if (ancestors.includes('ordered_list')) on.add('ordered');
464
485
  if (ancestors.includes('blockquote')) on.add('quote');
465
486
  if ($from.parent.type.name === 'paragraph' && !on.has('bullet') && !on.has('ordered') && !on.has('quote')) on.add('text');
487
+ const section = sectionHeading(state);
488
+ if (section) {
489
+ on.add('section');
490
+ if (section.color) on.add(`section-${section.color}`);
491
+ }
466
492
  return on;
467
493
  }
468
494
 
@@ -487,15 +513,20 @@ export function paint(view: EditorView, color: string | null) {
487
513
  view.focus();
488
514
  }
489
515
 
490
- // off: not there for the selection (the button is hidden).
516
+ // off: not there for the selection (the button is hidden). word: at a still cursor it acts on the word the cursor is
517
+ // in, which is selected first.
491
518
  type Look = {
492
519
  icon: string;
493
520
  label: string;
494
521
  on: (state: EditorView['state']) => boolean;
495
522
  action: (button: HTMLElement) => void;
496
523
  off?: (state: EditorView['state']) => boolean;
524
+ word?: boolean;
497
525
  };
498
526
 
527
+ // The levels of the top-level headings of a document, in order.
528
+ const levelsOf = (doc: EditorView['state']['doc']): number[] => topHeadings(doc).map(({ node }) => node.attrs.level);
529
+
499
530
  // Whether a heading (H2) is selected, or a part of one.
500
531
  function inH2(state: EditorView['state']) {
501
532
  let found = false;
@@ -504,10 +535,15 @@ function inH2(state: EditorView['state']) {
504
535
  return found;
505
536
  }
506
537
 
507
- // When a word or more is selected: a row of icons over it, to make it bold, italic, a link, one of the site's colours
508
- // (a menu under the button, opened by `colors`), the page's H1 (`h1`), a heading or a subheading; the ones that apply to
509
- // the selection are marked. On a phone, where the phone's own menu is over the selection and its handles under it, the
510
- // row is at the bottom of the screen, over the keyboard. sections: no colour in a heading (H2), the name of a section.
538
+ // Whether the cursor is in a label above a heading (eyebrow.mjs), which has no bold, italic, link or colour.
539
+ const inLabel = (state: EditorView['state']) => state.selection.$from.parent.type.name === 'eyebrow';
540
+
541
+ // When a word or more is selected, or the cursor has stood still in a word (STILL_MS, stillCursor.mjs): a row of icons
542
+ // over it, to make it bold, italic, a link, one of the site's colours (a menu under the button, opened by `colors`), the
543
+ // page's H1 (`h1`, only while the text has none), a heading or a subheading, a label above the heading; the ones that apply are marked. At a still
544
+ // cursor, bold, italic, link and colour act on the whole word, the headings on the line; a move or a keypress hides the
545
+ // row. On a phone, where the phone's own menu is over the selection and its handles under it, the row is at the bottom
546
+ // of the screen, over the keyboard. sections: no colour in a heading (H2), the name of a section.
511
547
  function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button: HTMLElement) => void) {
512
548
  return new Plugin({
513
549
  view(view) {
@@ -518,30 +554,77 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
518
554
  menu.hidden = true;
519
555
  const run = <T,>(command: { key: CmdKey<T> }, payload?: T) => void callCommand(command.key, payload)(ctx);
520
556
  const marked = (state: EditorView['state'], name: string) => activeOf(state).has(name);
521
- const heading = (state: EditorView['state'], level: number) => activeOf(state).has(`h${level}`);
557
+ // On a page the Heading button also stands for the H1 (firstHeading.mjs).
558
+ const heading = (state: EditorView['state'], level: number) =>
559
+ activeOf(state).has(`h${level}`) || (h1 && level === 2 && activeOf(state).has('h1'));
522
560
  const looks: Look[] = [
523
- { icon: boldIcon, label: t.text.bold, on: (s) => marked(s, 'strong'), action: () => run(toggleStrongCommand) },
524
- { icon: italicIcon, label: t.text.italic, on: (s) => marked(s, 'emphasis'), action: () => run(toggleEmphasisCommand) },
561
+ {
562
+ icon: boldIcon,
563
+ label: t.text.bold,
564
+ on: (s) => marked(s, 'strong'),
565
+ action: () => run(toggleStrongCommand),
566
+ off: inLabel,
567
+ word: true,
568
+ },
569
+ {
570
+ icon: italicIcon,
571
+ label: t.text.italic,
572
+ on: (s) => marked(s, 'emphasis'),
573
+ action: () => run(toggleEmphasisCommand),
574
+ off: inLabel,
575
+ word: true,
576
+ },
525
577
  {
526
578
  icon: linkIcon,
527
579
  label: t.text.link,
528
580
  on: (s) => marked(s, 'link'),
529
581
  action: () => void editLink(view).then(() => update(view)),
582
+ off: inLabel,
583
+ word: true,
530
584
  },
531
585
  {
532
586
  icon: paletteIcon,
533
587
  label: t.text.color,
534
588
  on: (s) => marked(s, 'textColor'),
535
589
  action: colors,
536
- off: sections ? inH2 : undefined,
590
+ off: (s) => inLabel(s) || (sections && inH2(s)),
591
+ word: true,
537
592
  },
538
593
  ...(h1 ? [1, 2, 3] : [2, 3]).map((level) => ({
539
594
  icon: [h1Icon, h2Icon, h3Icon][level - 1],
540
595
  label: [t.text.pageHeading, t.text.heading, t.text.subheading][level - 1],
541
596
  on: (s: EditorView['state']) => heading(s, level),
542
597
  action: () => (heading(view.state, level) ? run(turnIntoTextCommand) : run(wrapInHeadingCommand, level)),
598
+ // The H1 choice is gone as soon as the page's text has an H1.
599
+ off: level === 1 ? (s: EditorView['state']) => !showsH1Choice(levelsOf(s.doc)) : undefined,
543
600
  })),
601
+ // A label above the heading. Not on a fixed page's H1 or H2, whose headings are its sections (sections.mjs).
602
+ {
603
+ icon: tagIcon,
604
+ label: t.blocks.eyebrow,
605
+ on: hasLabel,
606
+ action: () => toggleLabel(view),
607
+ off: (s) => {
608
+ const heading = labelledHeading(s);
609
+ return !heading || (sections && heading.level <= 2);
610
+ },
611
+ },
544
612
  ];
613
+ // Where the cursor stood still in a word, so the row is shown for that word; null after a move. A change made by
614
+ // a button (acting) is no move.
615
+ let stillAt: number | null = null;
616
+ let acting = false;
617
+ const still = stillTimer(STILL_MS, () => {
618
+ if (!view.hasFocus() || !wordRange(view.state)) return;
619
+ stillAt = view.state.selection.from;
620
+ update(view);
621
+ });
622
+ const stillWord = (state: EditorView['state']) => (stillAt === state.selection.from ? wordRange(state) : null);
623
+ // The state as if the word at a still cursor were selected, so the buttons are marked as for the word.
624
+ const asShown = (state: EditorView['state']) => {
625
+ const word = stillWord(state);
626
+ return word ? state.apply(state.tr.setSelection(TextSelection.create(state.doc, word.from, word.to))) : state;
627
+ };
545
628
  const buttons = looks.map((look) => {
546
629
  const b = menu.appendChild(document.createElement('button'));
547
630
  b.type = 'button';
@@ -550,7 +633,14 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
550
633
  b.innerHTML = look.icon.replace('<svg', '<svg width="20" height="20" fill="currentColor" aria-hidden="true"');
551
634
  if (look.action === colors) b.setAttribute('aria-haspopup', 'menu');
552
635
  const act = () => {
553
- look.action(b);
636
+ const word = look.word ? stillWord(view.state) : null;
637
+ if (word) view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, word.from, word.to)));
638
+ acting = true;
639
+ try {
640
+ look.action(b);
641
+ } finally {
642
+ acting = false;
643
+ }
554
644
  update(view);
555
645
  // Again when "Gem ændringer" has come after the first change.
556
646
  setTimeout(follow, 100);
@@ -568,16 +658,16 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
568
658
  });
569
659
  function update(view: EditorView) {
570
660
  const { selection, doc } = view.state;
571
- const shown =
572
- view.hasFocus() &&
573
- selection instanceof TextSelection &&
574
- !selection.empty &&
575
- doc.textBetween(selection.from, selection.to).trim() !== '';
661
+ const word = stillWord(view.state);
662
+ const selected =
663
+ selection instanceof TextSelection && !selection.empty && doc.textBetween(selection.from, selection.to).trim() !== '';
664
+ const shown = view.hasFocus() && (selected || !!word);
576
665
  menu.hidden = !shown;
577
666
  if (!shown) return;
667
+ const state = asShown(view.state);
578
668
  looks.forEach((look, i) => {
579
- buttons[i].hidden = !!look.off?.(view.state);
580
- buttons[i].setAttribute('aria-pressed', String(look.on(view.state)));
669
+ buttons[i].hidden = !!look.off?.(state);
670
+ buttons[i].setAttribute('aria-pressed', String(look.on(state)));
581
671
  });
582
672
  const { offsetWidth: width, offsetHeight: height } = menu;
583
673
  const screen = visualViewport ?? { offsetLeft: 0, offsetTop: 0, width: innerWidth, height: innerHeight };
@@ -589,8 +679,9 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
589
679
  return;
590
680
  }
591
681
  const text = view.dom.getBoundingClientRect();
592
- const start = view.coordsAtPos(selection.from);
593
- const end = view.coordsAtPos(selection.to);
682
+ const { from, to } = word ?? selection;
683
+ const start = view.coordsAtPos(from);
684
+ const end = view.coordsAtPos(to);
594
685
  const middle = start.top === end.top ? (start.left + end.right) / 2 : text.left + text.width / 2;
595
686
  // Over the selection; under it when the toolbar is there.
596
687
  const bar = document.querySelector('.editor-tools')?.getBoundingClientRect().bottom ?? 0;
@@ -608,8 +699,16 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
608
699
  visualViewport?.addEventListener('resize', follow);
609
700
  visualViewport?.addEventListener('scroll', follow);
610
701
  return {
611
- update,
702
+ // A move or a keypress hides the row at a still cursor and waits again.
703
+ update: (view: EditorView, prev: EditorState) => {
704
+ if (!acting && (!prev.selection.eq(view.state.selection) || !prev.doc.eq(view.state.doc))) {
705
+ stillAt = null;
706
+ still.restart();
707
+ }
708
+ update(view);
709
+ },
612
710
  destroy: () => {
711
+ still.stop();
613
712
  view.dom.removeEventListener('blur', refresh);
614
713
  view.dom.removeEventListener('focus', refresh);
615
714
  removeEventListener('scroll', follow);
@@ -742,10 +841,11 @@ function blockCard(
742
841
  }
743
842
 
744
843
  export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
745
- { value, onChange, addImage, blocks, sheet: sheetProp, short, h1, looks, sections },
844
+ { value, onChange, addImage, blocks, sheet: sheetProp, short, h1, looks, sections, frame },
746
845
  ref,
747
846
  ) {
748
847
  const spaced = !!blocks;
848
+ const framed = frame ?? !sections;
749
849
  const root = useRef<HTMLDivElement>(null);
750
850
  const toolbar = useRef<HTMLDivElement>(null);
751
851
  const editor = useRef<Editor | null>(null);
@@ -757,12 +857,20 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
757
857
  looked.current = looks;
758
858
  // What applies where the cursor is (activeOf), marked in the toolbar.
759
859
  const [active, setActive] = useState<string[]>([]);
860
+ // Whether the page's text has no H1, so the H1 choice is shown (showsH1Choice).
861
+ const [h1Choice, setH1Choice] = useState(true);
760
862
  // The colour button over the selection, while its menu with the colours is open.
761
863
  const [colorsAt, setColorsAt] = useState<HTMLElement | null>(null);
762
864
  useEffect(() => {
763
865
  colorsAt?.setAttribute('aria-expanded', 'true');
764
866
  return () => colorsAt?.setAttribute('aria-expanded', 'false');
765
867
  }, [colorsAt]);
868
+ // The button for the colour of a section, while its menu is open.
869
+ const [sectionAt, setSectionAt] = useState<HTMLElement | null>(null);
870
+ useEffect(() => {
871
+ sectionAt?.setAttribute('aria-expanded', 'true');
872
+ return () => sectionAt?.setAttribute('aria-expanded', 'false');
873
+ }, [sectionAt]);
766
874
  // The previews of the blocks, drawn again when what they show from elsewhere changes: the lists,
767
875
  // the testimonials, the rest of the site, the page's address or a fixed page's other fields.
768
876
  const cards = useRef(new Set<() => void>());
@@ -782,7 +890,10 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
782
890
  new Plugin({
783
891
  view: () => ({
784
892
  update: (view) => {
785
- const now = [...activeOf(view.state)].sort();
893
+ const choice = showsH1Choice(levelsOf(view.state.doc));
894
+ setH1Choice((was) => (was === choice ? was : choice));
895
+ // On a page the first heading is the H1 by itself (firstHeading.mjs); the Heading button stands for it.
896
+ const now = [...activeOf(view.state)].map((name) => (h1 && name === 'h1' ? 'h2' : name)).sort();
786
897
  setActive((was) => (was.join() === now.join() ? was : now));
787
898
  },
788
899
  }),
@@ -818,7 +929,7 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
818
929
  },
819
930
  }),
820
931
  );
821
- const made = makeEditor(root.current, value)
932
+ let made = makeEditor(root.current, value)
822
933
  .use(cardView)
823
934
  .use(pictureView)
824
935
  .use(selecting)
@@ -826,9 +937,12 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
826
937
  .use(marking)
827
938
  .use(onEdit)
828
939
  .use(looking)
940
+ .use(sectionFrame(framed))
829
941
  .use(gapHere)
830
942
  .use(history)
831
943
  .use(clipboard);
944
+ // A page (h1) keeps its first heading as its H1 and has no other H1 (firstHeading.mjs); a blog post's H1 is its title.
945
+ if (h1) made = made.use(firstHeading);
832
946
  (spaced ? made.use(spacing) : made).create().then((created) => {
833
947
  if (cancelled) return created.destroy();
834
948
  editor.current = created;
@@ -911,6 +1025,14 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
911
1025
  editor.current?.action((ctx) => paint(ctx.get(editorViewCtx), name));
912
1026
  }
913
1027
 
1028
+ function sectionColor(name: string | null) {
1029
+ editor.current?.action((ctx) => {
1030
+ const view = ctx.get(editorViewCtx);
1031
+ setSectionColor(view, name);
1032
+ view.focus();
1033
+ });
1034
+ }
1035
+
914
1036
  // A new picture from the gallery, then the sheet with it and what it shows, where it can still be
915
1037
  // changed or left out (the browser's own box for what it shows without a sheet).
916
1038
  async function image() {
@@ -968,17 +1090,27 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
968
1090
  });
969
1091
  }
970
1092
 
971
- const tools: { label: string; icon: typeof TextB; action: () => void; on?: string }[] = [
1093
+ const tools: { label: string; icon: typeof TextB; action: (e: ReactMouseEvent<HTMLButtonElement>) => void; on?: string }[] = [
972
1094
  { label: t.text.bold, icon: TextB, action: () => run(toggleStrongCommand), on: 'strong' },
973
1095
  { label: t.text.italic, icon: TextItalic, action: () => run(toggleEmphasisCommand), on: 'emphasis' },
974
1096
  { label: t.text.link, icon: LinkSimple, action: link, on: 'link' },
975
- ...(h1 ? [{ label: t.text.pageHeading, icon: TextHOne, action: () => setKind('h1'), on: 'h1' }] : []),
1097
+ ...(h1 && h1Choice ? [{ label: t.text.pageHeading, icon: TextHOne, action: () => setKind('h1'), on: 'h1' }] : []),
976
1098
  { label: t.text.heading, icon: TextHTwo, action: () => setKind('h2'), on: 'h2' },
977
1099
  { label: t.text.subheading, icon: TextHThree, action: () => setKind('h3'), on: 'h3' },
978
1100
  { label: t.text.paragraph, icon: TextT, action: () => setKind('text'), on: 'text' },
979
1101
  { label: t.text.bulletList, icon: ListBullets, action: () => setKind('bullet'), on: 'bullet' },
980
1102
  { label: t.text.orderedList, icon: ListNumbers, action: () => setKind('ordered'), on: 'ordered' },
981
1103
  { label: t.text.quote, icon: Quotes, action: () => setKind('quote'), on: 'quote' },
1104
+ // The colour of the section the cursor's H2 starts (not a fixed page's text, whose H2s are its sections' names; the front page has it).
1105
+ ...(framed && active.includes('section')
1106
+ ? [
1107
+ {
1108
+ label: t.text.sectionColor,
1109
+ icon: Palette,
1110
+ action: (e: ReactMouseEvent<HTMLButtonElement>) => setSectionAt(sectionAt ? null : e.currentTarget),
1111
+ },
1112
+ ]
1113
+ : []),
982
1114
  // A picture and the blocks are put in with "+" (a sheet with them all); without blocks (a blog
983
1115
  // post) a picture has its own button.
984
1116
  ...(blocks
@@ -1038,7 +1170,7 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
1038
1170
  <Toolbar
1039
1171
  ref={toolbar}
1040
1172
  label={t.text.tools}
1041
- className="editor-tools sticky top-[calc(4.5rem+1px+env(safe-area-inset-top))] z-10 justify-center-safe overflow-x-auto rounded-t-panel border-b border-line md:flex-wrap"
1173
+ className="editor-tools sticky top-[calc(4.5rem+1px+env(safe-area-inset-top))] z-10 justify-center-safe overflow-x-auto rounded-t-panel border-b border-line medium:flex-wrap"
1042
1174
  >
1043
1175
  {tools.map((tool) => (
1044
1176
  <ReactFragment key={tool.label}>
@@ -1059,16 +1191,27 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
1059
1191
  <span className="text-ink">{t.text.noColor}</span>
1060
1192
  </MenuItem>
1061
1193
  </Menu>
1194
+ {/* The theme's three colours for a section (section-marker.mjs), and "Ingen"; the one it has is marked. */}
1195
+ <Menu anchor={sectionAt} label={t.text.sectionColor} onClose={() => setSectionAt(null)}>
1196
+ <MenuItem icon={Palette} selected={!active.some((a) => a.startsWith('section-'))} onClick={() => sectionColor(null)}>
1197
+ {t.text.sectionColors.none}
1198
+ </MenuItem>
1199
+ {SECTION_COLORS.map((name) => (
1200
+ <MenuItem key={name} icon={Palette} selected={active.includes(`section-${name}`)} onClick={() => sectionColor(name)}>
1201
+ {t.text.sectionColors[name as keyof typeof t.text.sectionColors]}
1202
+ </MenuItem>
1203
+ ))}
1204
+ </Menu>
1062
1205
  <Menu anchor={kindsAt} label={t.text.kind} onClose={() => setKindsAt(null)}>
1063
1206
  {kinds.map(({ label, icon, action, on }) => (
1064
- <MenuItem key={label} icon={icon} selected={active.includes(on!)} onClick={action}>
1207
+ <MenuItem key={label} icon={icon} selected={active.includes(on!)} onClick={action as () => void}>
1065
1208
  {label}
1066
1209
  </MenuItem>
1067
1210
  ))}
1068
1211
  </Menu>
1069
1212
  <div
1070
1213
  ref={root}
1071
- className={`editor-text prose max-w-none px-5 py-4 md:px-8 ${short ? 'editor-text-short' : ''} ${spaced ? 'editor-spaced' : ''}`}
1214
+ className={`editor-text prose max-w-none px-5 py-4 medium:px-8 ${short ? 'editor-text-short' : ''} ${spaced ? 'editor-spaced' : ''}`}
1072
1215
  />
1073
1216
  </div>
1074
1217
  );