@panphora/clayjs 1.15.1 → 1.16.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 (506) hide show
  1. package/README.md +4 -5
  2. package/THIRD-PARTY-NOTICES.md +16 -0
  3. package/compat/highlight-11.9.0.min.js +1213 -0
  4. package/compat/highlight-LICENSE +29 -0
  5. package/compat/journal-assets.lock.json +22 -0
  6. package/compat/overtype-2.6.1.min.js +116 -0
  7. package/compat/overtype-LICENSE +21 -0
  8. package/dist/all.js +90 -0
  9. package/dist/clay-dom.js +84 -0
  10. package/dist/clay-events.js +103 -0
  11. package/dist/clay-internals.js +85 -0
  12. package/dist/clay-options.js +93 -0
  13. package/dist/clay-router.js +98 -0
  14. package/dist/clay-ui.js +95 -0
  15. package/dist/clay-utils.js +84 -0
  16. package/dist/clay.js +146 -0
  17. package/dist/clay.standalone.js +83090 -58706
  18. package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
  19. package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
  20. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +1 -0
  21. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2JL6E75N.js +1 -0
  24. package/dist/m/c-2M73252O.js +1 -0
  25. package/dist/m/c-2SPVQSHC.js +1 -0
  26. package/dist/m/c-33HUYEDI.js +1 -0
  27. package/dist/m/c-33JJ6CBX.js +2 -0
  28. package/dist/m/c-35NBGMZB.js +1 -0
  29. package/dist/m/c-3GJ4SDE2.js +1 -0
  30. package/dist/m/c-3IQRECYS.js +1 -0
  31. package/dist/m/c-3OWDMNJR.js +2 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4B6H4G5V.js +1 -0
  34. package/dist/m/c-4DRMS4QG.js +9 -0
  35. package/dist/m/c-4FFHZM32.js +1 -0
  36. package/dist/m/c-4JZDZFF6.js +1 -0
  37. package/dist/m/c-4L3BTFRK.js +1 -0
  38. package/dist/m/c-4Q2XTQ2N.js +2 -0
  39. package/dist/m/c-4WKFKEKF.js +1 -0
  40. package/dist/m/c-4YSCRICO.js +1 -0
  41. package/dist/m/c-54RCDCBU.js +1 -0
  42. package/dist/m/c-5M4YEKCX.js +4 -0
  43. package/dist/m/c-5R4PTRG2.js +1 -0
  44. package/dist/m/c-5V3MOVJT.js +1 -0
  45. package/dist/m/c-5VYOWDTI.js +1 -0
  46. package/dist/m/c-5Y6TNX5W.js +34 -0
  47. package/dist/m/c-5YIHJ3TJ.js +1 -0
  48. package/dist/m/c-66O32MV3.js +28 -0
  49. package/dist/m/c-6HMB24EV.js +1 -0
  50. package/dist/m/c-7DJDKPGL.js +1 -0
  51. package/dist/m/c-7O6AFR65.js +1 -0
  52. package/dist/m/c-7SWMYKAI.js +1 -0
  53. package/dist/m/c-7VGZ7RU5.js +1 -0
  54. package/dist/m/c-A7LS5UZW.js +1 -0
  55. package/dist/m/c-ANQTJ3B5.js +2 -0
  56. package/dist/m/c-AQLFEATB.js +1 -0
  57. package/dist/m/c-AYVOREBE.js +1 -0
  58. package/dist/m/c-B2ZXPOS3.js +3 -0
  59. package/dist/m/c-BBVGTQG7.js +1 -0
  60. package/dist/m/c-BMIZKMPV.js +1 -0
  61. package/dist/m/c-BTDI4Y23.js +1 -0
  62. package/dist/m/c-BWFVSBDF.js +1 -0
  63. package/dist/m/c-BYTKL6UX.js +1 -0
  64. package/dist/m/c-CFHW5AMY.js +6 -0
  65. package/dist/m/c-CJZSLMVQ.js +1 -0
  66. package/dist/m/c-CYWLPPUS.js +1 -0
  67. package/dist/m/c-CZUSOB7V.js +1 -0
  68. package/dist/m/c-DBE6ORSI.js +1 -0
  69. package/dist/m/c-DCFSEJZG.js +1 -0
  70. package/dist/m/c-DNZ5IFM6.js +1 -0
  71. package/dist/m/c-EDHUR4MX.js +1 -0
  72. package/dist/m/c-EJTGYSLN.js +1 -0
  73. package/dist/m/c-EY6UOM54.js +1 -0
  74. package/dist/m/c-F3CKJTJS.js +1 -0
  75. package/dist/m/c-F7Y6FLPY.js +1 -0
  76. package/dist/m/c-FA4DZVTC.js +1 -0
  77. package/dist/m/c-FILLTJA7.js +1 -0
  78. package/dist/m/c-G2BUZITR.js +1 -0
  79. package/dist/m/c-GB3JTV6E.js +1 -0
  80. package/dist/m/c-GBGWXTX4.js +1 -0
  81. package/dist/m/c-GHBCCVG4.js +1 -0
  82. package/dist/m/c-GJAEGGXY.js +1 -0
  83. package/dist/m/c-GOWBDM6J.js +1 -0
  84. package/dist/m/c-GQ6F6IDK.js +1 -0
  85. package/dist/m/c-H4LG442Q.js +1 -0
  86. package/dist/m/c-HA6INT6J.js +1 -0
  87. package/dist/m/c-HPURYVZS.js +2 -0
  88. package/dist/m/c-HRDCD3WN.js +67 -0
  89. package/dist/m/c-HT33EQRN.js +1 -0
  90. package/dist/m/c-HVYXIS72.js +1 -0
  91. package/dist/m/c-IB52R237.js +1 -0
  92. package/dist/m/c-IJUK2GPW.js +1 -0
  93. package/dist/m/c-IKWNE4R4.js +1 -0
  94. package/dist/m/c-IU3IYEU6.js +1 -0
  95. package/dist/m/c-IZ2EBRAJ.js +1 -0
  96. package/dist/m/c-J7CA54IP.js +1 -0
  97. package/dist/m/c-JAX2667S.js +6 -0
  98. package/dist/m/c-KI7M4AY3.js +1 -0
  99. package/dist/m/c-KIPQJX4B.js +1 -0
  100. package/dist/m/c-KKWLDOPM.js +1 -0
  101. package/dist/m/c-KR4UW4KW.js +1 -0
  102. package/dist/m/c-KY3VJGUA.js +8 -0
  103. package/dist/m/c-L4WQSC27.js +1 -0
  104. package/dist/m/c-LFPHCXY3.js +1 -0
  105. package/dist/m/c-LR4CGEPT.js +91 -0
  106. package/dist/m/c-LRPUO2MX.js +1 -0
  107. package/dist/m/c-LTGMXXKO.js +1 -0
  108. package/dist/m/c-LXENEBV7.js +1 -0
  109. package/dist/m/c-LZJPNR2W.js +1 -0
  110. package/dist/m/c-MMUIUUZG.js +2 -0
  111. package/dist/m/c-MTQHWQC3.js +1 -0
  112. package/dist/m/c-N326R2JD.js +1 -0
  113. package/dist/m/c-N7JPUPGC.js +1 -0
  114. package/dist/m/c-NMJRXKPC.js +8 -0
  115. package/dist/m/c-NRPJEVJS.js +1 -0
  116. package/dist/m/c-NUHP2XDB.js +1 -0
  117. package/dist/m/c-NZMNXVF6.js +1 -0
  118. package/dist/m/c-OCSZ4QGN.js +1 -0
  119. package/dist/m/c-OIBUTDXI.js +1 -0
  120. package/dist/m/c-OIDRZKAF.js +1 -0
  121. package/dist/m/c-OYTFP5QN.js +1 -0
  122. package/dist/m/c-P7KNDG34.js +1 -0
  123. package/dist/m/c-PPOSCAQD.js +1 -0
  124. package/dist/m/c-PQ5LK6QE.js +993 -0
  125. package/dist/m/c-PZ4XEPUP.js +2 -0
  126. package/dist/m/c-QHIWXHRO.js +1 -0
  127. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  128. package/dist/m/c-QIX5BJYN.js +1 -0
  129. package/dist/m/c-QNKREOL6.js +1 -0
  130. package/dist/m/c-QQHEX6HA.js +1 -0
  131. package/dist/m/c-R7RMANJI.js +9 -0
  132. package/dist/m/c-RI4NT75Y.js +1 -0
  133. package/dist/m/c-RM7J7BV2.js +1 -0
  134. package/dist/m/c-RUJKSUQW.js +1 -0
  135. package/dist/m/c-RVMWGXEJ.js +1 -0
  136. package/dist/m/c-S7H2DRCB.js +1 -0
  137. package/dist/m/c-S7LK4KUE.js +1 -0
  138. package/dist/m/c-SGCCC4FW.js +25 -0
  139. package/dist/m/c-SGIA4IEQ.js +188 -0
  140. package/dist/m/c-SWRJFCNN.js +1 -0
  141. package/dist/m/c-T4VO4OCB.js +1 -0
  142. package/dist/m/c-TCCA5BZN.js +2 -0
  143. package/dist/m/c-TQEMVJCS.js +1 -0
  144. package/dist/m/c-TQZ2JYHD.js +1 -0
  145. package/dist/m/c-TR6RMDQ7.js +3 -0
  146. package/dist/m/c-TRS6TM6O.js +1 -0
  147. package/dist/m/c-UGHH4MB3.js +1 -0
  148. package/dist/m/c-UKHTLWUW.js +1 -0
  149. package/dist/m/c-UL2YUY52.js +1 -0
  150. package/dist/m/c-UNN7LWCT.js +1 -0
  151. package/dist/m/c-UXBMRDZB.js +235 -0
  152. package/dist/m/c-V7PZZRG7.js +1 -0
  153. package/dist/m/c-VAK7SG37.js +1 -0
  154. package/dist/m/c-VDSS4K3Z.js +1 -0
  155. package/dist/m/c-VFX45IZQ.js +1 -0
  156. package/dist/m/c-VHNSBD2M.js +1 -0
  157. package/dist/m/c-VKEDWYDF.js +1 -0
  158. package/dist/m/c-VL52ADDR.js +1 -0
  159. package/dist/m/c-WN5AY3OZ.js +1 -0
  160. package/dist/m/c-WNYBLIOZ.js +1 -0
  161. package/dist/m/c-WRBKEJD4.js +1 -0
  162. package/dist/m/c-WWDBYXLK.js +1 -0
  163. package/dist/m/c-WYFDLITL.js +1 -0
  164. package/dist/m/c-XN6MBKI6.js +67 -0
  165. package/dist/m/c-XNNW2CWK.js +1 -0
  166. package/dist/m/c-XPELB6E5.js +1 -0
  167. package/dist/m/c-XQXZAO7N.js +1 -0
  168. package/dist/m/c-Y6USGTS5.js +5 -0
  169. package/dist/m/c-YDH7Q3NY.js +1 -0
  170. package/dist/m/c-YG2NJYS6.js +135 -0
  171. package/dist/m/c-Z36J63PP.js +1 -0
  172. package/dist/m/c-Z4VRC7GU.js +7 -0
  173. package/dist/m/c-Z6K52OLP.js +19 -0
  174. package/dist/m/c-ZDY2MDAA.js +21 -0
  175. package/dist/m/c-ZE46QDIH.js +1 -0
  176. package/dist/m/c-ZUCOKOHT.js +1 -0
  177. package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
  178. package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
  179. package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
  180. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  181. package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
  182. package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
  183. package/dist/m/core/persist-HKHA66IY.js +3 -0
  184. package/dist/m/core/recovery-TBBSICE2.js +1 -0
  185. package/dist/m/core/save-WUCHTQBE.js +1 -0
  186. package/dist/m/core/save-core-FJVHT5M3.js +1 -0
  187. package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
  188. package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
  189. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  190. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  191. package/dist/m/events/index-23DPTUT4.js +1 -0
  192. package/dist/m/internals/index-WOVAP3RU.js +1 -0
  193. package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
  194. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  195. package/dist/m/loader-HXX6YNH5.js +1 -0
  196. package/dist/m/manifest-F22DEDAC.js +1 -0
  197. package/dist/m/manifest-edit-C3C7376B.js +1 -0
  198. package/dist/m/manifest.json +3491 -0
  199. package/dist/m/options/options-2LH76JQ3.js +1 -0
  200. package/dist/m/page/editor-XUTB2HB2.js +189 -0
  201. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
  202. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  203. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  204. package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
  205. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  206. package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
  207. package/dist/m/plugins/live-NUTTXSBS.js +1 -0
  208. package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
  209. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  210. package/dist/m/plugins/page-KSUH7G6J.js +2 -0
  211. package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
  212. package/dist/m/plugins/people-AJJ32YCG.js +1 -0
  213. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  214. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  215. package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
  216. package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
  217. package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
  218. package/dist/m/plugins/source-KHK5UUSK.js +1 -0
  219. package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
  220. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  221. package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
  222. package/dist/m/plugins/upload-XSGNILUY.js +1 -0
  223. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
  224. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
  225. package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
  226. package/dist/m/reader-core-ILWLXVMP.js +68 -0
  227. package/dist/m/route/route-KOCZSEGN.js +1 -0
  228. package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
  229. package/dist/m/ui/index-EVHKMKBL.js +1 -0
  230. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  231. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  232. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  233. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  234. package/dist/offline-manifest.json +3800 -0
  235. package/dist/page/themes/light-1.css +406 -0
  236. package/dist/server/core.cjs +23662 -22860
  237. package/dist/server/offline-runtime.cjs +4954 -0
  238. package/dist/server/offline-runtime.mjs +4923 -0
  239. package/dist/server/rooms.mjs +9 -0
  240. package/docs/guide.md +1078 -669
  241. package/entries/all.js +2 -0
  242. package/entries/clay-data.js +1 -1
  243. package/entries/clay-dom.js +2 -0
  244. package/entries/clay-events.js +2 -0
  245. package/entries/clay-internals.js +2 -0
  246. package/entries/clay-options.js +2 -0
  247. package/entries/clay-router.js +2 -0
  248. package/entries/clay-ui.js +2 -0
  249. package/entries/clay-utils.js +2 -0
  250. package/entries/clay.js +2 -0
  251. package/entries/sap.js +6 -6
  252. package/fonts/ibm-plex-mono-LICENSE +93 -0
  253. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  254. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  255. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  256. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  257. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  258. package/fonts/ibm-plex-sans-LICENSE +93 -0
  259. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  260. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  261. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  262. package/fonts/ibm-plex.css +71 -0
  263. package/fonts/inter-LICENSE +93 -0
  264. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  265. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  266. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  267. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  268. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  269. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  270. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  271. package/fonts/inter.css +62 -0
  272. package/fonts/newsreader-LICENSE +93 -0
  273. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  274. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  275. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  276. package/fonts/newsreader.css +26 -0
  277. package/fonts/work-sans-LICENSE +93 -0
  278. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  279. package/fonts/work-sans.css +8 -0
  280. package/package.json +20 -3
  281. package/packed-contract.json +1 -0
  282. package/src/attrs/add-from.js +27 -4
  283. package/src/attrs/save-freeze.js +7 -52
  284. package/src/core/admin-attrs.js +119 -12
  285. package/src/core/admin-contenteditable.js +12 -10
  286. package/src/core/admin-inputs.js +14 -15
  287. package/src/core/admin-onclick.js +2 -0
  288. package/src/core/autosave.js +74 -21
  289. package/src/core/conflict-download.js +22 -0
  290. package/src/core/conflict-notice-ui.js +1173 -0
  291. package/src/core/conflict-notice.js +140 -697
  292. package/src/core/edit-mode.js +147 -14
  293. package/src/core/etag.js +30 -12
  294. package/src/core/host-meta.js +72 -8
  295. package/src/core/host-permission.js +29 -0
  296. package/src/core/is-edit-mode.js +145 -12
  297. package/src/core/mode-key.js +125 -0
  298. package/src/core/mode-lifecycle.js +544 -0
  299. package/src/core/mode-receipt.js +30 -0
  300. package/src/core/mode-refusal.js +13 -0
  301. package/src/core/mode-resources.js +52 -0
  302. package/src/core/mode-support.js +82 -0
  303. package/src/core/mode-switch.js +259 -0
  304. package/src/core/mode-transition.js +112 -0
  305. package/src/core/page-data-write.js +305 -0
  306. package/src/core/page-data.js +26 -227
  307. package/src/core/page-format-wire.js +4 -0
  308. package/src/core/page-format.js +58 -0
  309. package/src/core/persist.js +37 -9
  310. package/src/core/preserve-authored.js +114 -0
  311. package/src/core/recovery.js +772 -0
  312. package/src/core/runtime-url.js +105 -0
  313. package/src/core/save-core.js +109 -12
  314. package/src/core/save-replace.js +72 -0
  315. package/src/core/save-size-notice.js +134 -0
  316. package/src/core/save-size.js +140 -0
  317. package/src/core/save.js +805 -118
  318. package/src/core/snapshot.js +121 -11
  319. package/src/core/source-map.js +157 -9
  320. package/src/core/unsaved-warning.js +14 -4
  321. package/src/core/upgrade-run.js +408 -0
  322. package/src/core/version.js +18 -0
  323. package/src/lib/assets.js +30 -0
  324. package/src/lib/autosave-debug-gate.js +50 -0
  325. package/src/lib/autosave-scheduler.js +141 -0
  326. package/src/lib/boot-imports.js +8 -0
  327. package/src/lib/derived.js +3 -1
  328. package/src/lib/dirty-gate.js +15 -0
  329. package/src/lib/doc-storage.js +33 -0
  330. package/src/lib/effect.js +9 -4
  331. package/src/lib/mutation.js +4 -2
  332. package/src/lib/offline-runtime.js +226 -0
  333. package/src/lib/preload.js +48 -0
  334. package/src/lib/root-attrs.js +1 -1
  335. package/src/lib/trash.js +18 -0
  336. package/src/lib/undo/capture-move.js +152 -0
  337. package/src/live/local-channel.js +61 -0
  338. package/src/live/local-lane.js +60 -9
  339. package/src/live/server-lane.js +6 -3
  340. package/src/loader-logic.js +76 -15
  341. package/src/loader.js +218 -30
  342. package/src/page/adopt-boot.js +76 -22
  343. package/src/page/adopt.js +25 -4
  344. package/src/page/awareness.js +230 -0
  345. package/src/page/blocks.js +79 -5
  346. package/src/page/blocksel.js +20 -4
  347. package/src/page/board-face.js +13 -0
  348. package/src/page/board-panel.js +5 -17
  349. package/src/page/board-style.js +2 -13
  350. package/src/page/board-view.js +1 -11
  351. package/src/page/board-views.js +17 -0
  352. package/src/page/board.js +2 -2
  353. package/src/page/boot-pack.js +26 -0
  354. package/src/page/boot.js +237 -0
  355. package/src/page/checklist.js +1 -1
  356. package/src/page/chrome.js +411 -61
  357. package/src/page/csv-import.js +17 -2
  358. package/src/page/document-title-dom.js +40 -0
  359. package/src/page/document-title-runtime.js +76 -0
  360. package/src/page/document-title.js +9 -0
  361. package/src/page/drag.js +48 -13
  362. package/src/page/drop.js +2 -1
  363. package/src/page/editor.js +330 -86
  364. package/src/page/feature-loader.js +72 -0
  365. package/src/page/features/board.js +12 -0
  366. package/src/page/features/code.js +13 -0
  367. package/src/page/features/media.js +290 -0
  368. package/src/page/features/registry.js +23 -0
  369. package/src/page/features/table.js +383 -0
  370. package/src/page/{tool-view.js → features/tool.js} +33 -2
  371. package/src/page/format-codec.js +215 -0
  372. package/src/page/format-gate.js +179 -0
  373. package/src/page/format-level.js +157 -0
  374. package/src/page/highlight-slot.js +23 -0
  375. package/src/page/highlight.js +30 -16
  376. package/src/page/history-intent.js +78 -0
  377. package/src/page/inline.js +18 -25
  378. package/src/page/items.js +52 -11
  379. package/src/page/link.js +1 -0
  380. package/src/page/markdown.js +86 -1
  381. package/src/page/media-views.js +3 -0
  382. package/src/page/media.js +33 -245
  383. package/src/page/outline-cache.js +11 -0
  384. package/src/page/placeholder.js +27 -7
  385. package/src/page/projection.js +196 -50
  386. package/src/page/reader-patch.js +306 -0
  387. package/src/page/recover.js +1 -1
  388. package/src/page/rich/page-tool.js +1 -4
  389. package/src/page/runtime-owner.js +27 -0
  390. package/src/page/save-projection.js +7 -4
  391. package/src/page/schema.js +14 -0
  392. package/src/page/table-rect-key.js +3 -0
  393. package/src/page/table-select.js +4 -3
  394. package/src/page/table-style.js +1 -1
  395. package/src/page/table-views.js +3 -0
  396. package/src/page/table.js +8 -293
  397. package/src/page/themes/light-1.css +139 -2
  398. package/src/page/themes/light-1.source.css +269 -0
  399. package/src/page/tool-capability.js +3 -0
  400. package/src/page/tool-views.js +3 -0
  401. package/src/page/view.js +390 -107
  402. package/src/page/ybind/awareness.js +172 -0
  403. package/src/page/ybind/binding.js +9 -1
  404. package/src/page/ybind/bridge.js +50 -47
  405. package/src/page/ybind/decode.js +52 -19
  406. package/src/page/ybind/doc.js +27 -2
  407. package/src/page/ybind/group.js +12 -3
  408. package/src/page/ybind/index.js +2 -0
  409. package/src/page/ybind/lineage.js +71 -0
  410. package/src/page/ybind/marks.js +25 -1
  411. package/src/page/ybind/migrate.js +48 -13
  412. package/src/page/ybind/moves.js +32 -21
  413. package/src/page/ybind/positions.js +50 -0
  414. package/src/page/ybind/provider.js +84 -22
  415. package/src/page/ybind/recovery-proof.js +191 -0
  416. package/src/page/ybind/recovery-replay.js +130 -0
  417. package/src/page/ybind/tokens.js +15 -6
  418. package/src/page/ybind/walk.js +10 -3
  419. package/src/page/ybind/writer.js +48 -4
  420. package/src/plugins/ai-edit.js +167 -32
  421. package/src/plugins/bind-text.js +218 -0
  422. package/src/plugins/cms.js +93 -0
  423. package/src/plugins/indicator.js +57 -5
  424. package/src/plugins/live.js +282 -34
  425. package/src/plugins/menu/claygoo.svg.js +1 -0
  426. package/src/plugins/menu/seo-head.js +323 -0
  427. package/src/plugins/menu/seo.js +849 -0
  428. package/src/plugins/menu.js +425 -0
  429. package/src/plugins/page-view.js +131 -0
  430. package/src/plugins/page.js +1022 -275
  431. package/src/plugins/people.js +158 -31
  432. package/src/plugins/richclay.js +119 -9
  433. package/src/plugins/sortable.js +370 -60
  434. package/src/plugins/source.js +189 -366
  435. package/src/plugins/undo.js +289 -19
  436. package/src/plugins/unsent.js +132 -0
  437. package/src/plugins/upgrade-run.js +1 -0
  438. package/src/plugins/upgrade.js +210 -0
  439. package/src/plugins/upload-task.js +571 -0
  440. package/src/plugins/upload-ui.js +265 -0
  441. package/src/plugins/upload.js +34 -153
  442. package/src/plugins/visitor-drafts/inert.js +32 -0
  443. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  444. package/src/plugins/visitor-drafts/prompt.js +2 -2
  445. package/src/plugins/visitor-drafts-owner.js +85 -0
  446. package/src/plugins/visitor-drafts.js +31 -42
  447. package/src/plugins/wire.js +82 -1
  448. package/src/reader-core.js +26 -0
  449. package/src/recovery/conflicts.js +287 -0
  450. package/src/recovery/identity.js +154 -0
  451. package/src/recovery/store.js +792 -0
  452. package/src/recovery/undo.js +257 -0
  453. package/src/server/checkpoint/index.js +68 -4
  454. package/src/server/offline-runtime/index.js +214 -0
  455. package/src/server/rooms/index.js +2 -1
  456. package/src/server/rooms/live-data-rooms.js +2 -2
  457. package/src/server/rooms/page-admission.js +33 -0
  458. package/src/server/rooms/page-lineage.js +56 -0
  459. package/src/server/rooms/page-rebase.js +11 -5
  460. package/src/server/rooms/page-state.js +2 -0
  461. package/src/server/rooms/page-write-admission.js +9 -3
  462. package/src/source/session.js +705 -0
  463. package/src/sync/conflict-revert.js +5 -4
  464. package/src/sync/conflicts-edit.js +119 -0
  465. package/src/sync/conflicts-facade.js +49 -0
  466. package/src/sync/conflicts.js +99 -1
  467. package/src/sync/editing-marker.js +37 -6
  468. package/src/sync/editing-sender.js +8 -1
  469. package/src/sync/live-sync-edit.js +63 -0
  470. package/src/sync/live-sync.js +364 -119
  471. package/src/sync/local-presence.js +187 -0
  472. package/src/sync/merge-declarations.js +10 -0
  473. package/src/sync/merge-engine.js +12 -0
  474. package/src/sync/presence-store.js +412 -0
  475. package/src/sync/presence-target.js +243 -0
  476. package/src/sync/presence.js +72 -41
  477. package/src/sync/saved-handoff.js +200 -0
  478. package/src/sync/saved-stream.js +535 -0
  479. package/src/sync/version-guard.js +92 -0
  480. package/src/sync/wire-profile.js +81 -0
  481. package/src/ui/bevel-controls.js +2 -8
  482. package/src/ui/page-scheme.js +7 -0
  483. package/src/ui/skins/cms.js +1 -1
  484. package/src/ui/theme-config.js +150 -0
  485. package/src/ui/theme.js +6 -138
  486. package/src/ui/toast-lazy.js +11 -0
  487. package/src/vendor/LICENSES-optional.md +253 -0
  488. package/src/vendor/highlight.vendor.js +3 -0
  489. package/src/vendor/hljs-bash.vendor.js +2 -0
  490. package/src/vendor/hljs-css.vendor.js +2 -0
  491. package/src/vendor/hljs-javascript.vendor.js +2 -0
  492. package/src/vendor/hljs-json.vendor.js +2 -0
  493. package/src/vendor/hljs-python.vendor.js +2 -0
  494. package/src/vendor/hljs-xml.vendor.js +2 -0
  495. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  496. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  497. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  498. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  499. package/src/vendor/hyper-morph.vendor.js +6 -6
  500. package/src/vendor/hyper-undo.vendor.js +1 -1
  501. package/src/vendor/hypercms.vendor.js +169 -84
  502. package/src/vendor/marked.vendor.js +68 -0
  503. package/src/vendor/overtype.vendor.js +994 -0
  504. package/src/vendor/richclay.vendor.js +17 -17
  505. package/src/vendor/sap-core.vendor.js +5 -5
  506. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -5,15 +5,19 @@
5
5
  // --page-* tokens in editor.js (contract: default-page/v1/look/THEME-CONTRACT.md).
6
6
  import { state, commands } from "../vendor/prosemirror.vendor.js";
7
7
  import { schema } from "./schema.js";
8
- import { BlockSelection, outline, siblingSpan, moveBlock, indentBlocks, outdentBlocks } from "./blocks.js";
8
+ import { BlockSelection, siblingSpan, moveBlock, indentBlocks, outdentBlocks } from "./blocks.js";
9
+ import { outlineOf } from "./outline-cache.js";
9
10
  import { currentSpan, selectBlocks, duplicateBlocks, deleteBlocks, asStep, typeIntoBlocks } from "./blocksel.js";
10
11
  import { openAltEditor, setImageAttrs } from "./media.js";
11
12
  import { BlockDrag } from "./drag.js";
12
13
  import { openLinkEditor } from "./link.js";
13
- import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnInto, insertAfter, rankItems, runItem, retype, insertItemsFor } from "./items.js";
14
- import toast from "../ui/toast.js";
14
+ import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnInto, insertAfter, rankItems, runItem, retype, canTurnInto, insertItemsFor } from "./items.js";
15
+ import toast from "../ui/toast-lazy.js";
15
16
  import { insertChartAfterTable } from "./chart.js";
16
17
  import { pickCsv } from "./csv-import.js";
18
+ import { HISTORY_INTENT, typingIntent } from "./history-intent.js";
19
+ import { composing } from "./markdown.js";
20
+ import { announce } from "./announce.js";
17
21
 
18
22
  export { BLOCK_TYPES, blockTypeOf, turnInto, insertAfter } from "./items.js";
19
23
 
@@ -24,7 +28,7 @@ const N = schema.nodes;
24
28
  const MOD = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform) ? "⌘" : "Ctrl+";
25
29
 
26
30
  const selectedRoots = (st) => {
27
- const ents = outline(st.doc);
31
+ const ents = outlineOf(st.doc);
28
32
  const { lo, hi } = st.selection.span(st.doc);
29
33
  return ents.slice(lo, hi + 1).filter((e) => e.up === ents[lo].up);
30
34
  };
@@ -32,6 +36,30 @@ const selectedRoots = (st) => {
32
36
  const imageId = (view, index) => view.state.doc.child(index).attrs.id;
33
37
  const blockStart = (doc, index) => { let at = 0; for (let i = 0; i < index; i++) at += doc.child(i).nodeSize; return at; };
34
38
 
39
+ // The editor attributes a menu lends it while it is open. A slash menu puts them
40
+ // back as it found them; every other menu clears them.
41
+ const POPUP_ATTRS = ["aria-haspopup", "aria-controls", "aria-activedescendant"];
42
+ const TARGET_GONE = "That block is no longer available.";
43
+ const CANNOT_CONVERT = "This block holds content from a newer version, so it can't be converted here.";
44
+
45
+ // The part of the window the person can see: the visual viewport when the
46
+ // browser has one, so a keyboard or a pinch does not hide the bar.
47
+ export function phoneViewport(win) {
48
+ const v = win.visualViewport;
49
+ return v ? { left: v.offsetLeft, top: v.offsetTop, width: v.width, height: v.height }
50
+ : { left: 0, top: 0, width: win.innerWidth, height: win.innerHeight };
51
+ }
52
+
53
+ export function phoneBarBox(v, width, height, safeBottom = 0) {
54
+ const inset = PHONE_INSET;
55
+ const w = Math.min(width, Math.max(0, v.width - inset * 2));
56
+ return {
57
+ left: v.left + (v.width - w) / 2,
58
+ top: Math.max(v.top + inset, v.top + v.height - height - safeBottom - inset),
59
+ width: w,
60
+ };
61
+ }
62
+
35
63
  export function blockLink(id, loc = globalThis.location) {
36
64
  return `${loc.origin}${loc.pathname}${loc.search}#${encodeURIComponent(id)}`;
37
65
  }
@@ -83,6 +111,7 @@ const ICONS = {
83
111
  bullet: svg('<path d="M6.5 4.5h7M6.5 8h7M6.5 11.5h7"/><path stroke-width="2.4" d="M3 4.5h.01M3 8h.01M3 11.5h.01"/>'),
84
112
  ordered: svg('<path d="M7 4.5h6.5M7 8h6.5M7 11.5h6.5M2.5 3.5l1-.5v3M2.5 9.3c.2-.5.6-.8 1.1-.8.6 0 1 .4 1 .9 0 .9-2.1 1.4-2.1 2.6h2.1"/>'),
85
113
  check: svg('<rect x="2.5" y="2.5" width="11" height="11" rx="2.5"/><path d="M5.5 8.2l1.8 1.8 3.4-4"/>'),
114
+ more: svg('<path stroke-width="2.2" d="M3 8h.01M8 8h.01M13 8h.01"/>'),
86
115
  };
87
116
  const TOOLS = [
88
117
  ["strong", "Bold"],
@@ -94,6 +123,11 @@ const TOOLS = [
94
123
  ["check", "Checklist"],
95
124
  ["code", "Code"],
96
125
  ];
126
+ // The four controls a phone toolbar keeps: bold, italic, link and the menu that
127
+ // holds everything else. Wider screens show every tool.
128
+ const PHONE_TOOLS = new Set(["strong", "em", "link"]);
129
+ const MORE_ACTIONS = new Set(["up", "down", "indent", "outdent"]);
130
+ const PHONE_BAR_WIDTH = 204, PHONE_BAR_HEIGHT = 52, PHONE_INSET = 8;
97
131
  const LIST_KINDS = new Set(["bullet", "ordered", "check"]);
98
132
  const LINE_TYPES = new Set(["paragraph", "heading", "list_item"]);
99
133
 
@@ -173,8 +207,13 @@ function runList(view, kind) {
173
207
  const st = view.state;
174
208
  const off = listActive(st, kind);
175
209
  const item = BLOCK_TYPES.find((b) => b.key === (off ? "text" : kind));
210
+ const idx = listable(st).filter((i) => off || !isKind(st.doc.child(i), kind));
211
+ if (idx.some((i) => !canTurnInto(st.doc.child(i), item))) {
212
+ announce(view, CANNOT_CONVERT);
213
+ return;
214
+ }
176
215
  const tr = st.tr;
177
- for (const i of listable(st)) if (off || !isKind(tr.doc.child(i), kind)) retype(tr, i, item);
216
+ for (const i of idx) retype(tr, i, item);
178
217
  if (tr.docChanged) view.dispatch(asStep(tr));
179
218
  }
180
219
 
@@ -185,6 +224,7 @@ function runMark(view, name) {
185
224
  const lines = name === "code" && !code ? wholeLines(st) : null;
186
225
  if (LIST_KINDS.has(name)) runList(view, name);
187
226
  else if (code) view.dispatch(asStep(codeLinesTr(st, code)).scrollIntoView());
227
+ else if (lines && lines.some((i) => !canTurnInto(st.doc.child(i), BLOCK_TYPES.find((b) => b.key === "code")))) announce(view, CANNOT_CONVERT);
188
228
  else if (lines) view.dispatch(asStep(codeBlockTr(st, lines)).scrollIntoView());
189
229
  else commands.toggleMark(M[name])(st, view.dispatch);
190
230
  view.focus();
@@ -198,7 +238,7 @@ const el = (tag, cls, attrs = {}) => {
198
238
  };
199
239
 
200
240
  function toolButton(name, label) {
201
- const b = el("button", "page-tool", { type: "button", "data-cmd": name, "aria-label": label, title: label });
241
+ const b = el("button", `page-tool${PHONE_TOOLS.has(name) ? " page-tool-primary" : " page-tool-desk"}`, { type: "button", "data-cmd": name, "aria-label": label, title: label });
202
242
  b.innerHTML = ICONS[name];
203
243
  return b;
204
244
  }
@@ -208,6 +248,35 @@ let menuCount = 0;
208
248
  export const chromeKey = new PluginKey("page-chrome");
209
249
  const chromeFor = new WeakMap();
210
250
 
251
+ // A range a slash menu chose, held in plugin state so it follows the document
252
+ // while the action it belongs to waits for code to load.
253
+ export const slashRangeKey = new PluginKey("page-slash-range");
254
+ const slashRangeState = {
255
+ init: () => null,
256
+ apply(tr, value) {
257
+ let held = value;
258
+ if (held && tr.docChanged) {
259
+ const from = tr.mapping.map(held.from, 1), to = tr.mapping.map(held.to, -1);
260
+ held = from <= to && tr.doc.textBetween(from, to) === held.text ? { ...held, from, to } : null;
261
+ }
262
+ const m = tr.getMeta(slashRangeKey);
263
+ if (m !== undefined) held = m;
264
+ return held;
265
+ },
266
+ };
267
+
268
+ export const slashRangePlugin = () => new Plugin({ key: slashRangeKey, state: slashRangeState });
269
+
270
+ export const holdSlashRange = (view, range) => {
271
+ const text = range ? view.state.doc.textBetween(range.from, range.to) : null;
272
+ view.dispatch(view.state.tr.setMeta(slashRangeKey, range ? { ...range, text } : null));
273
+ };
274
+
275
+ export const heldSlashRange = (st) => {
276
+ const held = slashRangeKey.getState(st);
277
+ return held ? { from: held.from, to: held.to } : null;
278
+ };
279
+
211
280
  export function openPageMenu(view, anchor, items) {
212
281
  const chrome = chromeFor.get(view);
213
282
  if (!chrome) return false;
@@ -223,12 +292,21 @@ class Chrome {
223
292
  this.onAiTool = onAiTool;
224
293
  this.shell = view.dom.parentElement;
225
294
  this.menu = null;
295
+ this.priorAria = null;
226
296
  this.movedTimer = 0;
227
297
  this.hover = null;
298
+ this.pendingHover = null;
299
+ this.win = view.dom.ownerDocument.defaultView ?? globalThis;
300
+ this.frame = null;
301
+ this.destroyed = false;
302
+ this.probe = null;
303
+ this.roving = null;
228
304
 
229
305
  this.dock = el("div", "page-dock", { role: "toolbar", "aria-label": "Formatting" });
230
- this.turn = el("button", "page-tool page-tool-turn", { type: "button", "aria-haspopup": "menu" });
231
- this.dock.append(this.turn, el("span", "page-tool-sep"), ...TOOLS.map(([n, l]) => toolButton(n, l)));
306
+ this.turn = el("button", "page-tool page-tool-turn page-tool-desk", { type: "button", "aria-haspopup": "menu" });
307
+ this.more = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
308
+ this.more.innerHTML = ICONS.more;
309
+ this.dock.append(this.turn, el("span", "page-tool-sep page-tool-desk"), ...TOOLS.map(([n, l]) => toolButton(n, l)), this.more);
232
310
  this.plus = el("button", "page-plus", { type: "button", "aria-label": "Add a block", title: "Add a block" });
233
311
  this.plus.innerHTML = ICONS.plus;
234
312
  this.grip = el("button", "page-grip", { type: "button", "aria-label": "Block actions", title: "Drag to move, click for actions", "aria-haspopup": "menu" });
@@ -250,6 +328,8 @@ class Chrome {
250
328
  });
251
329
  }
252
330
  this.turn.addEventListener("click", () => this.toggleMenu("turn", this.turn));
331
+ this.more.addEventListener("click", () => this.toggleMenu("more", this.more));
332
+ this.dock.addEventListener("keydown", (e) => this.dockKey(e));
253
333
  this.plus.addEventListener("click", () => this.fromPlus());
254
334
  this.grip.addEventListener("click", (e) => {
255
335
  if (e.detail === 0) this.gripAction(this.gutterIndex());
@@ -272,10 +352,11 @@ class Chrome {
272
352
  this.onKey = () => {
273
353
  clearTimeout(this.movedTimer);
274
354
  this.shell.classList.remove("is-moved");
355
+ this.pendingHover = null;
275
356
  this.setHover(null);
276
357
  };
277
- this.onLeave = () => this.setHover(null);
278
- this.onResize = () => this.place();
358
+ this.onLeave = () => { this.pendingHover = null; this.setHover(null); };
359
+ this.onResize = () => this.scheduleLayout();
279
360
  this.onDocDown = (e) => {
280
361
  if (!this.menu) return;
281
362
  if ([this.menuEl, this.plus, this.grip, this.turn].some((part) => part.contains(e.target))) return;
@@ -285,7 +366,10 @@ class Chrome {
285
366
  this.shell.addEventListener("mousemove", this.onMove);
286
367
  this.shell.addEventListener("mouseleave", this.onLeave);
287
368
  this.shell.addEventListener("keydown", this.onKey);
288
- window.addEventListener("resize", this.onResize);
369
+ this.win.addEventListener("resize", this.onResize);
370
+ this.win.addEventListener("scroll", this.onResize, true);
371
+ this.win.visualViewport?.addEventListener("resize", this.onResize);
372
+ this.win.visualViewport?.addEventListener("scroll", this.onResize);
289
373
  this.update(view, null);
290
374
  }
291
375
 
@@ -295,11 +379,107 @@ class Chrome {
295
379
  return Math.min(st.doc.resolve(pos).index(0), st.doc.childCount - 1);
296
380
  }
297
381
 
382
+ isPhone() {
383
+ return !!this.win?.matchMedia?.("(max-width: 560px)").matches;
384
+ }
385
+
298
386
  gutterIndex() {
299
- if (this.view.dom.ownerDocument.defaultView.matchMedia?.("(max-width: 560px)").matches) return this.blockIndex();
387
+ if (this.isPhone()) return this.blockIndex();
388
+ this.settleHover();
300
389
  return Math.min(this.hover ?? this.blockIndex(), this.view.state.doc.childCount - 1);
301
390
  }
302
391
 
392
+ dockControls() {
393
+ const all = [...this.dock.querySelectorAll("button")].filter((b) => this.rendered(b));
394
+ return this.isPhone() ? all.filter((b) => b.classList.contains("page-tool-primary")) : all;
395
+ }
396
+
397
+ // Every transaction asks whether the roving control is still usable: one style read for
398
+ // that control, not one for every button in the dock.
399
+ inRoving(control) {
400
+ if (!control || !this.dock.contains(control)) return false;
401
+ if (this.isPhone() && !control.classList.contains("page-tool-primary")) return false;
402
+ return this.rendered(control);
403
+ }
404
+
405
+ rendered(control) {
406
+ if (control.hidden) return false;
407
+ const style = this.win?.getComputedStyle?.(control);
408
+ return !style || style.display !== "none";
409
+ }
410
+
411
+ setRoving(target) {
412
+ this.roving = target;
413
+ const roving = new Set(this.dockControls());
414
+ for (const b of this.dock.querySelectorAll("button")) b.tabIndex = b === target && roving.has(b) ? 0 : -1;
415
+ }
416
+
417
+ // The block selection's own Escape still runs first, and then the keyboard
418
+ // leaves the writing surface for the toolbar.
419
+ leaveToToolbar() {
420
+ queueMicrotask(() => {
421
+ if (!this.view.isDestroyed) this.focusToolbar();
422
+ });
423
+ }
424
+
425
+ focusToolbar() {
426
+ const [first] = this.dockControls();
427
+ if (!first) return false;
428
+ this.setRoving(first);
429
+ first.focus();
430
+ return true;
431
+ }
432
+
433
+ // Left and Right walk the toolbar; Escape hands the keyboard back to the
434
+ // editor. Tab is left alone, so it leaves the toolbar.
435
+ dockKey(event) {
436
+ if (event.key === "Escape") {
437
+ event.preventDefault();
438
+ this.view.focus();
439
+ return;
440
+ }
441
+ if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
442
+ const list = this.dockControls();
443
+ const at = list.indexOf(event.target);
444
+ if (at < 0) return;
445
+ event.preventDefault();
446
+ const next = list[(at + (event.key === "ArrowRight" ? 1 : -1) + list.length) % list.length];
447
+ this.setRoving(next);
448
+ next.focus();
449
+ }
450
+
451
+ // The blocks a menu's commands act on, by id, so a peer's edit between opening
452
+ // the menu and running the command cannot retarget them.
453
+ selectionIds() {
454
+ const st = this.view.state;
455
+ return blocksIn(st).map((i) => st.doc.child(i).attrs.id);
456
+ }
457
+
458
+ idIndex(id) {
459
+ let found = -1;
460
+ this.view.state.doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
461
+ return found;
462
+ }
463
+
464
+ // Where Escape sends focus: the control that opened the menu when it can hold
465
+ // focus, otherwise the editor.
466
+ invoker() {
467
+ const m = this.menu;
468
+ if (!m || m.slashAt !== null) return this.view.dom;
469
+ const a = m.anchor;
470
+ return a && a !== this.view.dom && (a.tagName === "BUTTON" || a.hasAttribute("tabindex")) ? a : this.view.dom;
471
+ }
472
+
473
+ // A block action from the phone menu acts on the caret's block when nothing
474
+ // more is selected, as the desktop command does.
475
+ selectCaretBlock(ids = null) {
476
+ const st = this.view.state;
477
+ if (st.selection instanceof BlockSelection) return;
478
+ const found = (ids ?? []).map((id) => this.idIndex(id)).filter((i) => i >= 0);
479
+ const first = found.length ? found[0] : this.blockIndex();
480
+ this.view.dispatch(st.tr.setSelection(selectBlocks(st.doc, first, found.length ? found[found.length - 1] : first)));
481
+ }
482
+
303
483
  gripAction(index) {
304
484
  if (this.menu) this.closeMenu();
305
485
  else this.openActions(this.grip, index);
@@ -307,25 +487,39 @@ class Chrome {
307
487
 
308
488
  trackHover(event) {
309
489
  if (this.menu || this.drag.state || [this.plus, this.grip, this.dock, this.bubble].some((part) => part.contains(event.target))) return;
490
+ this.pendingHover = { clientY: event.clientY };
491
+ this.scheduleLayout();
492
+ }
493
+
494
+ // The pointer's block, read once per frame from the newest move.
495
+ hoverAt(clientY) {
310
496
  const view = this.view;
311
- const hit = view.posAtCoords({ left: view.dom.getBoundingClientRect().left + 2, top: event.clientY });
497
+ const hit = view.posAtCoords({ left: view.dom.getBoundingClientRect().left + 2, top: clientY });
312
498
  if (!hit) return;
313
499
  const doc = view.state.doc;
314
- const ents = outline(doc);
500
+ const ents = outlineOf(doc);
315
501
  let i = Math.min(doc.resolve(hit.inside >= 0 ? hit.inside : hit.pos).index(0), ents.length - 1);
316
502
  while (i > 0 && ents[i].hidden) i--;
317
- this.setHover(i);
503
+ this.hover = i;
318
504
  }
319
505
 
320
506
  setHover(i) {
321
507
  if (i === this.hover) return;
322
508
  this.hover = i;
323
- this.place();
509
+ this.scheduleLayout();
510
+ }
511
+
512
+ settleHover() {
513
+ if (!this.pendingHover) return;
514
+ const { clientY } = this.pendingHover;
515
+ this.pendingHover = null;
516
+ this.hoverAt(clientY);
324
517
  }
325
518
 
326
519
  fromPlus() {
327
520
  this.view.focus();
328
- this.openMenu("insert", this.plus, null, this.gutterIndex());
521
+ const i = this.gutterIndex();
522
+ this.openMenu("insert", this.plus, null, null, this.view.state.doc.child(i).attrs.id);
329
523
  }
330
524
 
331
525
  toggleMenu(mode, anchor) {
@@ -333,10 +527,13 @@ class Chrome {
333
527
  else this.openMenu(mode, anchor);
334
528
  }
335
529
 
336
- openMenu(mode, anchor, slashAt = null, index = null) {
337
- this.menu = { mode, anchor, slashAt, index, query: "", active: 0, items: [] };
530
+ openMenu(mode, anchor, slashAt = null, ids = null, anchorId = null) {
531
+ this.menu = { mode, anchor, slashAt, anchorId, ids: ids ?? (mode === "insert" ? [] : this.selectionIds()), query: "", active: 0, items: [] };
338
532
  anchor.setAttribute("aria-expanded", "true");
339
- this.view.dom.setAttribute("aria-haspopup", "menu");
533
+ if (slashAt !== null) {
534
+ if (!this.priorAria) this.priorAria = POPUP_ATTRS.map((name) => [name, this.view.dom.getAttribute(name)]);
535
+ this.view.dom.setAttribute("aria-haspopup", "listbox");
536
+ } else this.view.dom.setAttribute("aria-haspopup", "menu");
340
537
  this.view.dom.setAttribute("aria-controls", this.menuId);
341
538
  this.renderMenu();
342
539
  }
@@ -346,32 +543,65 @@ class Chrome {
346
543
  this.menu = null;
347
544
  this.menuEl.hidden = true;
348
545
  this.scrim.hidden = true;
349
- for (const name of ["aria-haspopup", "aria-controls", "aria-activedescendant"]) this.view.dom.removeAttribute(name);
546
+ if (this.priorAria) {
547
+ for (const [name, value] of this.priorAria) {
548
+ if (value === null) this.view.dom.removeAttribute(name);
549
+ else this.view.dom.setAttribute(name, value);
550
+ }
551
+ this.priorAria = null;
552
+ } else for (const name of POPUP_ATTRS) this.view.dom.removeAttribute(name);
553
+ }
554
+
555
+ // Everything the phone toolbar folds away: the rest of the formatting, and the
556
+ // block actions the desktop menu carries. The move commands are the same ones.
557
+ moreItems() {
558
+ const tools = TOOLS.filter(([name]) => !PHONE_TOOLS.has(name))
559
+ .map(([name, label]) => ({ key: name, label, icon: ICONS[name], run: (view) => runMark(view, name) }));
560
+ const actions = ACTION_ITEMS.filter((a) => MORE_ACTIONS.has(a.key)).map((a) => ({ ...a, blocks: true }));
561
+ return [
562
+ ...tools,
563
+ { key: "turn", label: "Turn into", icon: "\u21bb", run: (view, chrome) => chrome.openMenu("turn", chrome.more) },
564
+ { key: "insert", label: "Insert", icon: "+", run: (view, chrome) => chrome.openMenu("insert", chrome.more, null, null, view.state.doc.child(chrome.blockIndex()).attrs.id) },
565
+ ...actions,
566
+ ];
350
567
  }
351
568
 
352
569
  menuItems(m) {
353
570
  if (m.mode === "external") return m.externalItems || [];
354
571
  if (m.mode === "actions") return actionsFor(this.view.state);
572
+ if (m.mode === "more") return this.moreItems();
355
573
  const items = m.mode === "turn" ? BLOCK_TYPES : insertItemsFor(this.view.state, this.onAiTool
356
574
  ? [...MENU_ITEMS, { key: "ai", label: "Make a tool with AI…", icon: "✦", aliases: ["tool", "generate"], insert: "ai" }]
357
575
  : MENU_ITEMS);
358
576
  return rankItems(items, m.query);
359
577
  }
360
578
 
579
+ // The types the blocks in the selection are now. A mixed selection has more
580
+ // than one and so checks no row.
581
+ currentTypes() {
582
+ const st = this.view.state;
583
+ return new Set(blocksIn(st).map((i) => blockTypeOf(st.doc.child(i)).key));
584
+ }
585
+
361
586
  openActions(anchor = this.plus, at = null) {
362
587
  const st = this.view.state;
363
- const ents = outline(st.doc);
588
+ const ents = outlineOf(st.doc);
364
589
  const sel = st.selection;
365
590
  const inside = at !== null && sel instanceof BlockSelection && sel.span(st.doc).lo <= at && at <= sel.span(st.doc).hi;
366
591
  const { lo, hi } = at === null || inside ? currentSpan(st, ents) : siblingSpan(ents, at, at);
367
592
  this.view.dispatch(st.tr.setSelection(selectBlocks(st.doc, lo, hi, ents)));
368
- this.openMenu("actions", anchor, null, lo);
593
+ const ids = [];
594
+ for (let i = lo; i <= hi; i++) ids.push(st.doc.child(i).attrs.id);
595
+ this.openMenu("actions", anchor, null, ids);
369
596
  }
370
597
 
371
598
  renderMenu() {
372
599
  const m = this.menu;
373
600
  m.items = this.menuItems(m);
374
601
  m.active = Math.min(m.active, Math.max(0, m.items.length - 1));
602
+ const slash = m.slashAt !== null;
603
+ const current = m.mode === "turn" ? this.currentTypes() : null;
604
+ this.menuEl.setAttribute("role", slash ? "listbox" : "menu");
375
605
  const nodes = [];
376
606
  if (m.mode === "turn") {
377
607
  const title = el("div", "page-menu-title");
@@ -385,7 +615,9 @@ class Chrome {
385
615
  }
386
616
  m.items.forEach((b, i) => {
387
617
  const active = i === m.active;
388
- const row = el("button", `page-menu-item${active ? " is-active" : ""}`, { type: "button", role: typeof b.checked === "boolean" ? "menuitemcheckbox" : "menuitem", tabindex: m.mode === "external" ? "0" : "-1", id: `${this.menuId}-${i}`, "data-index": String(i), "aria-selected": active ? "true" : "false", ...(typeof b.checked === "boolean" ? { "aria-checked": String(b.checked) } : {}) });
618
+ const checked = current ? current.size === 1 && b.key === [...current][0] : (typeof b.checked === "boolean" ? b.checked : null);
619
+ const role = m.mode === "turn" ? "menuitemradio" : typeof checked === "boolean" ? "menuitemcheckbox" : slash ? "option" : "menuitem";
620
+ const row = el("button", `page-menu-item${active ? " is-active" : ""}`, { type: "button", role, tabindex: !slash && active ? "0" : "-1", id: `${this.menuId}-${i}`, "data-index": String(i), ...(role === "menuitemradio" ? {} : { "aria-selected": active ? "true" : "false" }), ...(typeof checked === "boolean" ? { "aria-checked": String(checked) } : {}) });
389
621
  const icon = el("span", "page-menu-icon");
390
622
  icon.textContent = b.icon || "";
391
623
  const label = el("span", "page-menu-label");
@@ -405,12 +637,12 @@ class Chrome {
405
637
  nodes.push(row);
406
638
  });
407
639
  this.menuEl.replaceChildren(...nodes);
408
- if (m.items.length) this.view.dom.setAttribute("aria-activedescendant", `${this.menuId}-${m.active}`);
640
+ if (slash && m.items.length) this.view.dom.setAttribute("aria-activedescendant", `${this.menuId}-${m.active}`);
409
641
  else this.view.dom.removeAttribute("aria-activedescendant");
410
642
  this.menuEl.hidden = false;
411
643
  this.scrim.hidden = false;
412
- this.place();
413
- if (m.mode === "external") this.menuEl.querySelector("button")?.focus();
644
+ this.scheduleLayout();
645
+ if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus();
414
646
  }
415
647
 
416
648
  pick(i) {
@@ -419,34 +651,57 @@ class Chrome {
419
651
  if (!item) return;
420
652
  const st = this.view.state;
421
653
  const remove = m.slashAt !== null && st.selection.head >= m.slashAt ? { from: m.slashAt - 1, to: st.selection.head } : null;
654
+ // Resolve what the menu chose against the current document: a block a peer
655
+ // removed cancels the command instead of handing it whatever took its place.
656
+ const wanted = m.mode === "insert" ? (m.anchorId ? [m.anchorId] : []) : m.ids;
657
+ const stale = wanted.some((id) => !id || this.idIndex(id) < 0);
658
+ const index = wanted.length ? this.idIndex(wanted[0]) : -1;
422
659
  this.closeMenu();
660
+ if (stale && m.mode !== "external") {
661
+ announce(this.view, TARGET_GONE);
662
+ this.view.focus();
663
+ return;
664
+ }
423
665
  if (m.mode === "turn") {
424
- let tr = null;
425
- if (st.selection instanceof BlockSelection) {
426
- tr = st.tr;
427
- for (const root of selectedRoots(st)) retype(tr, root.i, item);
428
- } else tr = turnInto(st, this.blockIndex(), item);
429
- if (tr) this.view.dispatch(asStep(tr).scrollIntoView());
666
+ if (wanted.some((id) => !canTurnInto(st.doc.child(this.idIndex(id)), item))) {
667
+ announce(this.view, CANNOT_CONVERT);
668
+ this.view.focus();
669
+ return;
670
+ }
671
+ const tr = st.tr;
672
+ for (const id of wanted) retype(tr, this.idIndex(id), item);
673
+ this.view.dispatch(asStep(tr).scrollIntoView());
430
674
  this.view.focus();
431
- } else if (m.mode === "actions" || m.mode === "external") {
432
- item.run(this.view, this, m.index);
675
+ } else if (m.mode === "actions" || m.mode === "more" || m.mode === "external") {
676
+ if (item.blocks) this.selectCaretBlock(wanted);
677
+ item.run(this.view, this, index >= 0 ? index : this.blockIndex());
433
678
  if (!this.menu && item.focus !== false) this.view.focus();
434
679
  } else {
435
680
  if (item.insert === "ai") this.onAiTool(this.view, remove);
436
- else runItem(this.view, item, m.slashAt !== null ? this.blockIndex() : m.index, remove);
681
+ else runItem(this.view, item, index >= 0 ? index : this.blockIndex(), remove);
437
682
  }
438
683
  }
439
684
 
440
685
  // Keys while the menu is open; anything else falls through to the editor.
441
686
  key(event) {
442
- if (event.isComposing || this.view.composing) return false;
687
+ if (composing(event, this.view)) return false;
443
688
  const m = this.menu;
444
- if (!m) return false;
689
+ if (!m) {
690
+ if (event.key === "Escape" && !event.metaKey && !event.ctrlKey && !event.altKey
691
+ && this.view.state.selection instanceof BlockSelection) this.leaveToToolbar();
692
+ return false;
693
+ }
694
+ if ((event.key === "Backspace" || event.key === "Delete") && m.mode === "actions"
695
+ && this.view.state.selection instanceof BlockSelection) {
696
+ this.closeMenu();
697
+ this.view.focus();
698
+ deleteBlocks(this.view.state, this.view.dispatch);
699
+ return true;
700
+ }
445
701
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
446
702
  if (m.items.length) {
447
703
  m.active = (m.active + (event.key === "ArrowDown" ? 1 : -1) + m.items.length) % m.items.length;
448
704
  this.renderMenu();
449
- if (m.mode === "external") this.menuEl.querySelectorAll("button")[m.active]?.focus();
450
705
  }
451
706
  return true;
452
707
  }
@@ -454,7 +709,13 @@ class Chrome {
454
709
  if (m.items.length) this.pick(m.active);
455
710
  return true;
456
711
  }
457
- if (event.key === "Escape") { this.closeMenu(); return true; }
712
+ if (event.key === "Escape") {
713
+ const target = this.invoker();
714
+ this.closeMenu();
715
+ if (target === this.view.dom) this.view.focus();
716
+ else target?.focus?.();
717
+ return true;
718
+ }
458
719
  return false;
459
720
  }
460
721
 
@@ -473,33 +734,78 @@ class Chrome {
473
734
  this.renderMenu();
474
735
  }
475
736
 
476
- place() {
737
+ scheduleLayout() {
738
+ if (this.frame !== null || this.destroyed) return;
739
+ this.frame = this.win.requestAnimationFrame(() => {
740
+ this.frame = null;
741
+ if (this.destroyed) return;
742
+ const layout = this.measureLayout();
743
+ this.paintLayout(layout);
744
+ });
745
+ }
746
+
747
+ // The safe-area inset the browser reports, read through one hidden probe.
748
+ safeBottom() {
749
+ if (!this.probe) {
750
+ this.probe = el("div", "page-safe-probe");
751
+ this.shell.append(this.probe);
752
+ }
753
+ return parseFloat(this.win.getComputedStyle(this.probe).paddingBottom) || 0;
754
+ }
755
+
756
+ // A menu stays inside the visible viewport, above the bar when there is room.
757
+ menuBox(bar) {
758
+ const v = phoneViewport(this.win);
759
+ const maxHeight = Math.max(0, v.height - PHONE_INSET * 2);
760
+ const width = Math.min(280, Math.max(0, v.width - PHONE_INSET * 2));
761
+ const applied = this.menuEl.style.getPropertyValue("--page-pop-max");
762
+ this.menuEl.style.removeProperty("--page-pop-max");
763
+ const natural = this.menuEl.offsetHeight || 0;
764
+ if (applied) this.menuEl.style.setProperty("--page-pop-max", applied);
765
+ const height = Math.min(natural, maxHeight);
766
+ const left = Math.max(v.left + PHONE_INSET, Math.min(bar.left, v.left + v.width - PHONE_INSET - width));
767
+ let top = bar.top - 6 - height;
768
+ if (top < v.top + PHONE_INSET) top = Math.min(bar.top + bar.height + 6, v.top + v.height - PHONE_INSET - height);
769
+ return { left, top, width, maxHeight };
770
+ }
771
+
772
+ // Every geometry read the chrome needs, taken in one pass for the frame.
773
+ measureLayout() {
477
774
  const view = this.view;
478
775
  const st = view.state;
776
+ this.settleHover();
479
777
  const shellBox = this.shell.getBoundingClientRect();
480
778
  const edBox = view.dom.getBoundingClientRect();
481
779
  const i = this.gutterIndex();
482
780
  let at = 0;
483
781
  for (let k = 0; k < i; k++) at += st.doc.child(k).nodeSize;
484
782
  const dom = view.nodeDOM(at);
783
+ const out = { textLeft: edBox.left - shellBox.left, gutter: null, bubble: null, menu: null, bar: null, desk: false };
485
784
  if (dom && dom.getBoundingClientRect) {
486
785
  const r = dom.getBoundingClientRect();
487
786
  const lineH = parseFloat(getComputedStyle(dom).lineHeight) || r.height;
488
- for (const part of [this.plus, this.grip]) {
489
- part.style.setProperty("--page-plus-top", `${r.top - shellBox.top + Math.min(r.height, lineH) / 2}px`);
490
- part.style.setProperty("--page-plus-left", `${r.left - shellBox.left}px`);
491
- }
787
+ out.gutter = { top: r.top - shellBox.top + Math.min(r.height, lineH) / 2, left: r.left - shellBox.left };
788
+ }
789
+ const phone = this.isPhone();
790
+ // The fixed toolbar sits inside the visible viewport, so an on-screen keyboard
791
+ // or a pan cannot carry it off screen.
792
+ if (phone) {
793
+ const v = phoneViewport(this.win);
794
+ const height = this.dock.offsetHeight || PHONE_BAR_HEIGHT;
795
+ out.bar = { ...phoneBarBox(v, this.dock.offsetWidth || PHONE_BAR_WIDTH, height, this.safeBottom()), height };
796
+ } else {
797
+ out.desk = !!this.dock.style.left;
492
798
  }
493
- this.plus.style.setProperty("--page-text-left", `${edBox.left - shellBox.left}px`);
494
-
495
799
  const sel = st.selection;
496
- if (!this.bubble.hidden) {
800
+ if (!this.bubble.hidden && !phone) {
497
801
  const a = view.coordsAtPos(sel.from), b = view.coordsAtPos(sel.to, -1);
498
802
  const mid = (Math.min(a.left, b.left) + Math.max(a.right, b.right)) / 2;
499
- this.bubble.style.setProperty("--page-pop-x", `${mid - shellBox.left}px`);
500
- this.bubble.style.setProperty("--page-pop-y", `${Math.min(a.top, b.top) - shellBox.top}px`);
803
+ out.bubble = { x: mid - shellBox.left, y: Math.min(a.top, b.top) - shellBox.top };
501
804
  }
502
- if (this.menu) {
805
+ if (this.menu && phone) {
806
+ const box = this.menuBox(out.bar);
807
+ out.menu = { x: box.left, y: box.top, width: box.width, maxHeight: box.maxHeight };
808
+ } else if (this.menu) {
503
809
  const m = this.menu;
504
810
  let left, top;
505
811
  if (m.slashAt !== null) {
@@ -507,20 +813,56 @@ class Chrome {
507
813
  left = c.left;
508
814
  top = c.bottom + 6;
509
815
  const height = this.menuEl.offsetHeight;
510
- if (top + height > window.innerHeight - 8 && c.top - 6 - height >= 8) top = c.top - 6 - height;
816
+ if (top + height > this.win.innerHeight - 8 && c.top - 6 - height >= 8) top = c.top - 6 - height;
511
817
  } else {
512
818
  const r = m.anchor === this.plus ? (dom?.getBoundingClientRect?.() ?? edBox) : m.anchor.getBoundingClientRect();
513
819
  left = r.left;
514
820
  top = r.bottom + 6;
515
821
  }
516
- this.menuEl.style.setProperty("--page-pop-x", `${Math.max(8, Math.min(left - shellBox.left, shellBox.width - 280))}px`);
517
- this.menuEl.style.setProperty("--page-pop-y", `${top - shellBox.top}px`);
822
+ out.menu = { x: Math.max(8, Math.min(left - shellBox.left, shellBox.width - 280)), y: top - shellBox.top };
823
+ }
824
+ return out;
825
+ }
826
+
827
+ // The placement style writes, from the frame's measurements alone.
828
+ paintLayout(layout) {
829
+ if (layout.bar) {
830
+ this.dock.style.left = `${layout.bar.left}px`;
831
+ this.dock.style.top = `${layout.bar.top}px`;
832
+ this.dock.style.width = `${layout.bar.width}px`;
833
+ this.grip.style.setProperty("--page-grip-lift", `${layout.bar.height}px`);
834
+ } else {
835
+ if (layout.desk) for (const side of ["left", "top", "width"]) this.dock.style.removeProperty(side);
836
+ this.grip.style.removeProperty("--page-grip-lift");
837
+ }
838
+ if (layout.gutter) {
839
+ for (const part of [this.plus, this.grip]) {
840
+ part.style.setProperty("--page-plus-top", `${layout.gutter.top}px`);
841
+ part.style.setProperty("--page-plus-left", `${layout.gutter.left}px`);
842
+ }
843
+ }
844
+ this.plus.style.setProperty("--page-text-left", `${layout.textLeft}px`);
845
+ if (layout.bubble) {
846
+ this.bubble.style.setProperty("--page-pop-x", `${layout.bubble.x}px`);
847
+ this.bubble.style.setProperty("--page-pop-y", `${layout.bubble.y}px`);
848
+ }
849
+ if (layout.menu) {
850
+ this.menuEl.style.setProperty("--page-pop-x", `${layout.menu.x}px`);
851
+ this.menuEl.style.setProperty("--page-pop-y", `${layout.menu.y}px`);
852
+ if (layout.menu.width !== undefined) {
853
+ this.menuEl.style.setProperty("--page-pop-width", `${layout.menu.width}px`);
854
+ this.menuEl.style.setProperty("--page-pop-max", `${layout.menu.maxHeight}px`);
855
+ }
518
856
  }
519
857
  }
520
858
 
521
859
  update(view, prev) {
522
860
  this.view = view;
523
- if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc) this.closeMenu();
861
+ if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc) {
862
+ const hadFocus = this.menuEl.contains(document.activeElement);
863
+ this.closeMenu();
864
+ if (hadFocus) view.focus();
865
+ }
524
866
  const st = view.state;
525
867
  const node = st.doc.child(this.blockIndex());
526
868
  this.turn.textContent = `${blockTypeOf(node).label} ▾`;
@@ -529,19 +871,26 @@ class Chrome {
529
871
  this.bubble.hidden = !showBubble;
530
872
  if (showBubble) for (const b of this.bubble.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
531
873
  this.grip.classList.toggle("is-on", st.selection instanceof BlockSelection);
874
+ if (!this.inRoving(this.roving)) this.setRoving(this.dockControls()[0]);
532
875
  this.slashQuery();
533
- this.place();
876
+ this.scheduleLayout();
534
877
  }
535
878
 
536
879
  destroy() {
880
+ this.destroyed = true;
881
+ if (this.frame !== null) this.win.cancelAnimationFrame(this.frame);
882
+ this.frame = null;
537
883
  clearTimeout(this.movedTimer);
538
884
  this.drag.destroy();
539
885
  this.shell.removeEventListener("mousemove", this.onMove);
540
886
  this.shell.removeEventListener("mouseleave", this.onLeave);
541
887
  this.shell.removeEventListener("keydown", this.onKey);
542
- window.removeEventListener("resize", this.onResize);
888
+ this.win.removeEventListener("resize", this.onResize);
889
+ this.win.removeEventListener("scroll", this.onResize, true);
890
+ this.win.visualViewport?.removeEventListener("resize", this.onResize);
891
+ this.win.visualViewport?.removeEventListener("scroll", this.onResize);
543
892
  document.removeEventListener("mousedown", this.onDocDown, true);
544
- for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl]) part.remove();
893
+ for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl, this.probe]) part?.remove();
545
894
  }
546
895
  }
547
896
 
@@ -571,12 +920,13 @@ export function chromePlugin({ onAiTool = null } = {}) {
571
920
  const $from = view.state.doc.resolve(from);
572
921
  if ($from.depth !== 1 || !$from.parent.isTextblock || $from.parent.type.spec.code) return inserted;
573
922
  if ($from.parentOffset > 0 && !/\s/.test(view.state.doc.textBetween(from - 1, from, "", "\n"))) return inserted;
574
- if (!inserted) view.dispatch(view.state.tr.insertText("/", from, to));
575
- chrome.openMenu("insert", chrome.plus, from + 1);
923
+ if (!inserted) view.dispatch(view.state.tr.insertText("/", from, to).setMeta(HISTORY_INTENT, typingIntent(view.state, from, to, "/")));
924
+ const at = Math.min(view.state.doc.resolve(from).index(0), view.state.doc.childCount - 1);
925
+ chrome.openMenu("insert", chrome.plus, from + 1, null, view.state.doc.child(at).attrs.id);
576
926
  return true;
577
927
  },
578
928
  handleDOMEvents: {
579
- keydown(view, event) { return event.isComposing || view.composing; },
929
+ keydown(view, event) { return composing(event, view); },
580
930
  blur(view) {
581
931
  if (chrome) chrome.bubble.hidden = true;
582
932
  return false;