@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
package/src/page/items.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { state } from "../vendor/prosemirror.vendor.js";
6
6
  import { schema } from "./schema.js";
7
7
  import { isKey, keyBetween, newId } from "./model.js";
8
+ import { isOpaque, opaqueMark } from "./format-codec.js";
8
9
  import { outline, subtreeRange } from "./blocks.js";
9
10
  import { asStep } from "./blocksel.js";
10
11
  import { pickFiles, hostPlace } from "./media.js";
@@ -67,8 +68,29 @@ export function retype(tr, index, item) {
67
68
  return tr.setBlockType(at + 1, at + node.nodeSize - 1, N[item.type], { ...keep, ...(item.attrs || {}) });
68
69
  }
69
70
 
71
+ // A command this runtime cannot carry out on the content it was given: the
72
+ // document and the clipboard keep what they hold.
73
+ export const REFUSED = Object.freeze({ status: "refused", code: "unsupported-format" });
74
+
75
+ const opaqueType = opaqueMark([]).type;
76
+ const holdsOpaque = (node) => {
77
+ let found = false;
78
+ node.forEach((child) => { if (!found && child.marks.some(isOpaque)) found = true; });
79
+ return found;
80
+ };
81
+
82
+ // A block that is not a textblock cannot be turned into anything, and one this
83
+ // runtime cannot rewrite, or whose opaque marks the target type would erase, is
84
+ // refused rather than converted into something that lost them.
85
+ export function canTurnInto(node, item) {
86
+ if (!node.isTextblock || node.type === N.opaque_block) return false;
87
+ return !holdsOpaque(node) || N[item.type].allowsMarkType(opaqueType);
88
+ }
89
+
70
90
  export function turnInto(st, index, item) {
71
- return st.doc.child(index).isTextblock ? retype(st.tr, index, item) : null;
91
+ const node = st.doc.child(index);
92
+ if (!node.isTextblock) return null;
93
+ return canTurnInto(node, item) ? retype(st.tr, index, item) : REFUSED;
72
94
  }
73
95
 
74
96
  // A new empty paragraph after the block at `index` and its subtree, at the
@@ -116,22 +138,40 @@ export function insertDivider(st, index, tr = st.tr) {
116
138
  return tr.setSelection(TextSelection.create(tr.doc, to + 2));
117
139
  }
118
140
 
141
+ // The skipped letters between a query and a candidate it can be read out of,
142
+ // or Infinity when it cannot. Only queries of 2 to 32 letters count, so a single
143
+ // letter never drags in half the menu.
144
+ function subsequenceCost(query, candidate) {
145
+ if (query.length < 2 || query.length > 32) return Infinity;
146
+ let at = -1;
147
+ let cost = 0;
148
+ for (const ch of query) {
149
+ const next = candidate.indexOf(ch, at + 1);
150
+ if (next < 0) return Infinity;
151
+ cost += next - at - 1;
152
+ at = next;
153
+ }
154
+ return cost;
155
+ }
156
+
119
157
  // Exact name or alias first, then label prefix, word or alias prefix,
120
- // substring; equal ranks keep menu order. No fuzzy matching.
158
+ // substring, description word, and last a fuzzy subsequence of the label, key
159
+ // or aliases. Equal tiers keep the lower cost, then menu order.
121
160
  export function rankItems(items, query) {
122
161
  const q = query.trim().toLowerCase();
123
162
  if (!q) return items;
124
163
  const rank = (it) => {
125
164
  const label = it.label.toLowerCase();
126
165
  const names = [it.key, ...(it.aliases || []), ...(it.hint ? [it.hint] : [])].map((s) => s.toLowerCase());
127
- if (label === q || names.includes(q)) return 0;
128
- if (label.startsWith(q)) return 1;
129
- if ([...label.split(/\s+/), ...names].some((s) => s.startsWith(q))) return 2;
130
- if ([label, ...names].some((s) => s.includes(q))) return 3;
131
- if (q.length >= 3 && it.description && it.description.toLowerCase().split(/[^a-z0-9]+/).some((w) => w.startsWith(q))) return 4;
132
- return Infinity;
166
+ if (label === q || names.includes(q)) return [0, 0];
167
+ if (label.startsWith(q)) return [1, 0];
168
+ if ([...label.split(/\s+/), ...names].some((s) => s.startsWith(q))) return [2, 0];
169
+ if ([label, ...names].some((s) => s.includes(q))) return [3, 0];
170
+ if (q.length >= 3 && it.description && it.description.toLowerCase().split(/[^a-z0-9]+/).some((w) => w.startsWith(q))) return [4, 0];
171
+ const cost = Math.min(...[label, it.key, ...(it.aliases || [])].map((s) => subsequenceCost(q, s.toLowerCase())));
172
+ return cost === Infinity ? null : [5, cost];
133
173
  };
134
- return items.map((it, order) => ({ it, order, rank: rank(it) })).filter((x) => x.rank !== Infinity).sort((a, b) => a.rank - b.rank || a.order - b.order).map((x) => x.it);
174
+ return items.map((it, order) => ({ it, order, rank: rank(it) })).filter((x) => x.rank).sort((a, b) => a.rank[0] - b.rank[0] || a.rank[1] - b.rank[1] || a.order - b.order).map((x) => x.it);
135
175
  }
136
176
 
137
177
  // What a pick does in the insert menu for the block at `index`. A divider and
@@ -141,12 +181,13 @@ export function runItem(view, item, index, remove = null) {
141
181
  const st = view.state;
142
182
  if (item.insert === "image" || item.insert === "file") return pickFiles(view, item.insert === "image" ? "image/*" : "", hostPlace(st, index), remove);
143
183
  if (item.insert === "csv") return pickCsv(view, remove);
144
- if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove);
145
- if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove);
184
+ if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove, null, index);
185
+ if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove, index);
146
186
  if (item.insert === "chart") return chartFromSlash(view, index, remove);
147
187
  let tr = st.tr;
148
188
  if (remove) tr.delete(remove.from, remove.to);
149
189
  const node = tr.doc.child(index);
190
+ if (node.isTextblock && node.content.size === 0 && !canTurnInto(node, item)) return REFUSED;
150
191
  if (item.insert === "divider") tr = insertDivider(st, index, tr);
151
192
  else if (node.isTextblock && node.content.size === 0) tr = retype(tr, index, item);
152
193
  else {
package/src/page/link.js CHANGED
@@ -79,6 +79,7 @@ class LinkPopover {
79
79
  this.edit.addEventListener("click", () => this.openEdit());
80
80
  this.remove.addEventListener("click", () => this.apply(""));
81
81
  this.input.addEventListener("keydown", (e) => {
82
+ if (e.isComposing || e.keyCode === 229) return;
82
83
  if (e.key === "Enter") { e.preventDefault(); this.apply(this.input.value); }
83
84
  else if (e.key === "Escape") { e.preventDefault(); this.close(); }
84
85
  });
@@ -8,6 +8,7 @@ import { retype, replaceWithDivider } from "./items.js";
8
8
  import { asStep } from "./blocksel.js";
9
9
  import { insertTableNode } from "./table.js";
10
10
  import { tableFromGrid } from "./table-paste.js";
11
+ import { safeHref } from "./address.js";
11
12
 
12
13
  const { InputRule } = inputrules;
13
14
  const N = schema.nodes;
@@ -29,6 +30,87 @@ const dividerRule = new InputRule(/^---$/, (st, match, start, end) => {
29
30
  return asStep(replaceWithDivider(st.tr, $s.index(0)));
30
31
  });
31
32
 
33
+ // Inline shortcuts typed around text: **bold** and __bold__ to strong, *italic*
34
+ // and _italic_ to em, `code`, ~~strike~~, and [label](address) to a link. The
35
+ // delimiters are deleted and the mark is added over the body, which stays where
36
+ // it is, so the marks it already carries survive. Backspace right after puts the
37
+ // delimiter characters back through the input rule's own undo.
38
+ const OPENS = "\\s\\(\\[\\{'\u2018\u201C\u0022";
39
+ const OPENS_ONE = new RegExp(`[${OPENS}]`);
40
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
41
+ const bodyRe = (exclude) => `([^\\s${exclude}](?:[\\s\\S]*?[^\\s${exclude}])?)`;
42
+
43
+ export const composing = (event, view) => !!event?.isComposing || event?.keyCode === 229 || !!view?.composing;
44
+
45
+ const trailingBackslashes = (text) => {
46
+ let n = 0;
47
+ for (let i = text.length - 1; i >= 0 && text[i] === "\\"; i--) n++;
48
+ return n;
49
+ };
50
+
51
+ // An opening delimiter needs the start of a textblock or whitespace and opening
52
+ // punctuation before it, and neither delimiter may be escaped.
53
+ const opensAt = (st, at) => {
54
+ const $at = st.doc.resolve(at);
55
+ if (!$at.parent.inlineContent) return false;
56
+ if (trailingBackslashes($at.parent.textBetween(0, $at.parentOffset, null, "\uFFFC")) % 2) return false;
57
+ return $at.parentOffset === 0 || OPENS_ONE.test($at.parent.textBetween($at.parentOffset - 1, $at.parentOffset));
58
+ };
59
+
60
+ const inline = (st, match, start, end, markOf) => {
61
+ const mark = markOf(match);
62
+ if (!mark || !st.selection.empty) return null;
63
+ if (start < 0 || start > end || end > st.doc.content.size) return null;
64
+ const $start = st.doc.resolve(start), $end = st.doc.resolve(end);
65
+ if (!$start.sameParent($end) || !$start.parent.inlineContent) return null;
66
+ const [all, prefix, body, close] = match;
67
+ if (inlineCode(st, start, end)) return null;
68
+ const open = all.slice(prefix.length, all.length - body.length - close.length);
69
+ const from = start + prefix.length;
70
+ if (!opensAt(st, from)) return null;
71
+ if (trailingBackslashes(all.slice(0, all.length - close.length)) % 2) return null;
72
+ const pending = all.length - (end - start);
73
+ if (pending < 0) return null;
74
+ const tr = st.tr;
75
+ if (pending) tr.insertText(all.slice(-pending), end, end);
76
+ const bodyStart = from + open.length;
77
+ const bodyEnd = bodyStart + body.length;
78
+ tr.delete(bodyEnd, start + all.length);
79
+ tr.delete(from, bodyStart);
80
+ tr.addMark(from, from + body.length, mark);
81
+ tr.removeStoredMark(mark.type);
82
+ return asStep(tr);
83
+ };
84
+
85
+ const inlineRule = (re, markOf) => new InputRule(
86
+ re,
87
+ (st, match, start, end) => inline(st, match, start, end, markOf),
88
+ { inCodeMark: false },
89
+ );
90
+
91
+ const markRule = (open, close, type) => inlineRule(
92
+ new RegExp(`(^|[${OPENS}])${escapeRe(open)}${bodyRe(escapeRe(close))}(${escapeRe(close)})$`),
93
+ () => type.create(),
94
+ );
95
+
96
+ const linkRule = inlineRule(
97
+ new RegExp(`(^|[${OPENS}])\\[${bodyRe("\\[\\]")}(\\]\\([^()\\s]*\\))$`),
98
+ (match) => {
99
+ const href = safeHref(match[3].slice(2, -1));
100
+ return href ? schema.marks.link.create({ href }) : null;
101
+ },
102
+ );
103
+
104
+ const INLINE_RULES = [
105
+ markRule("**", "**", schema.marks.strong),
106
+ markRule("__", "__", schema.marks.strong),
107
+ markRule("*", "*", schema.marks.em),
108
+ markRule("_", "_", schema.marks.em),
109
+ markRule("`", "`", schema.marks.code),
110
+ markRule("~~", "~~", schema.marks.strike),
111
+ linkRule,
112
+ ];
113
+
32
114
  const LISTS = ["paragraph", "list_item"];
33
115
 
34
116
  export const MARKDOWN_RULES = [
@@ -39,6 +121,7 @@ export const MARKDOWN_RULES = [
39
121
  blockRule(/^>\s$/, { type: "toggle", attrs: { open: true } }),
40
122
  blockRule(/^```$/, { type: "code_block" }, ["paragraph"], true),
41
123
  dividerRule,
124
+ ...INLINE_RULES,
42
125
  ];
43
126
 
44
127
  export const markdownPlugin = () => inputrules.inputRules({ rules: MARKDOWN_RULES });
@@ -59,7 +142,9 @@ export function tableFromMarkdownRow(st, dispatch) {
59
142
  return insertTableNode(st, dispatch, table, { from: $from.start(), to: $from.end() });
60
143
  }
61
144
 
62
- export const markdownKeys = () => keymap.keymap({ Enter: tableFromMarkdownRow });
145
+ export const markdownKeys = () => keymap.keymap({
146
+ Enter: (st, dispatch, view) => !composing(null, view) && tableFromMarkdownRow(st, dispatch),
147
+ });
63
148
 
64
149
  export function backspaceToParagraph(st, dispatch) {
65
150
  const { $from, empty } = st.selection;
@@ -0,0 +1,3 @@
1
+ // The bookmark renderer a media plugin can reach, filled by the media view part
2
+ // when it loads, per editor. An upload still tracks and settles with no renderer.
3
+ export const mediaViews = new WeakMap();
package/src/page/media.js CHANGED
@@ -9,7 +9,8 @@ import { outline } from "./blocks.js";
9
9
  import { newId } from "./model.js";
10
10
  import { asStep } from "./blocksel.js";
11
11
  import { announce } from "./announce.js";
12
- import { safeHref, safeFileHref } from "./address.js";
12
+ import { safeFileHref } from "./address.js";
13
+ import { mediaViews } from "./media-views.js";
13
14
 
14
15
  const { Plugin, PluginKey } = state;
15
16
  const { Decoration, DecorationSet } = pmView;
@@ -17,7 +18,7 @@ const N = schema.nodes;
17
18
 
18
19
  export const mediaKey = new PluginKey("page-media");
19
20
  export const EMBED_MAX = 1.5 * 1024 * 1024;
20
- const RETRY = new Set(["timeout", "network", "error", "bad-response"]);
21
+ export const RETRY = new Set(["timeout", "network", "error", "bad-response"]);
21
22
  const runtime = new WeakMap();
22
23
 
23
24
  const boundary = (doc, pos) => {
@@ -183,65 +184,21 @@ export function cancelUpload(view, id) {
183
184
  abort?.abort();
184
185
  }
185
186
 
186
- function button(doc, act, label, id) {
187
- const b = doc.createElement("button");
188
- b.type = "button";
189
- b.className = "page-upload-btn";
190
- b.dataset.act = act;
191
- b.dataset.upload = id;
192
- b.textContent = label;
193
- return b;
194
- }
195
-
196
- function bookmarkDOM(doc, it) {
197
- const box = doc.createElement("div");
198
- box.className = "page-upload";
199
- box.dataset.state = it.state;
200
- box.dataset.kind = it.kind;
201
- box.contentEditable = "false";
202
- box.setAttribute("role", it.state === "uploading" ? "status" : "alert");
203
- if (it.preview) {
204
- const img = doc.createElement("img");
205
- img.className = "page-upload-preview";
206
- img.src = it.preview;
207
- img.alt = "";
208
- box.append(img);
209
- }
210
- const name = doc.createElement("span");
211
- name.className = "page-upload-name";
212
- name.textContent = it.name;
213
- box.append(name);
214
- if (it.state === "uploading") {
215
- const bar = doc.createElement("span");
216
- bar.className = "page-upload-bar";
217
- bar.setAttribute("role", "progressbar");
218
- bar.setAttribute("aria-label", `Uploading ${it.name}`);
219
- bar.setAttribute("aria-valuemin", "0");
220
- bar.setAttribute("aria-valuemax", "100");
221
- bar.setAttribute("aria-valuenow", String(it.percent));
222
- const fill = doc.createElement("span");
223
- fill.style.width = `${it.percent}%`;
224
- bar.append(fill);
225
- box.append(bar, button(doc, "cancel", "Cancel", it.id));
226
- return box;
227
- }
228
- const msg = doc.createElement("span");
229
- msg.className = "page-upload-msg";
230
- msg.textContent = it.message;
231
- box.append(msg);
232
- if (it.state === "embed-choice") box.append(button(doc, "embed", "Keep this image inside the document", it.id));
233
- if (RETRY.has(it.code)) box.append(button(doc, "retry", "Retry", it.id));
234
- box.append(button(doc, "remove", "Remove", it.id));
235
- return box;
236
- }
237
-
238
187
  const htmlHasText = (html) => html.replace(/<(meta|style|img)\b[^>]*>/gi, "").replace(/<[^>]+>/g, "").trim() !== "";
239
188
 
240
- export function mediaPlugin({ upload }) {
189
+ export function mediaPlugin({ upload, features = null }) {
190
+ let editorView = null;
191
+ // Only the bookmark widget waits for the view part: the upload and its block
192
+ // are core, and the block draws through the schema until the part arrives.
193
+ const showBookmark = (view) => {
194
+ if (!features || features.has("media") || !runtime.has(view)) return;
195
+ features.ensure("media").catch(() => {});
196
+ };
241
197
  return new Plugin({
242
198
  key: mediaKey,
243
199
  state: mediaState,
244
200
  view(view) {
201
+ editorView = view;
245
202
  const doc = view.dom.ownerDocument;
246
203
  const input = doc.createElement("input");
247
204
  input.type = "file";
@@ -251,13 +208,18 @@ export function mediaPlugin({ upload }) {
251
208
  view.dom.parentElement.append(input);
252
209
  runtime.set(view, { upload, aborts: new Map(), input });
253
210
  input.addEventListener("change", () => {
211
+ const files = [...(input.files ?? [])];
254
212
  const picker = mediaKey.getState(view.state).picker;
255
213
  view.dispatch(view.state.tr.setMeta(mediaKey, { picker: null }));
256
- if (input.files?.length && picker) {
214
+ if (files.length && picker) {
215
+ const { remove } = picker;
216
+ if (remove && view.state.doc.textBetween(remove.from, remove.to) === remove.text) {
217
+ view.dispatch(asStep(view.state.tr.delete(remove.from, remove.to)));
218
+ }
257
219
  const index = indexOfId(view.state.doc, picker.place.hostId);
258
220
  if (!picker.place.hostId || index >= 0) {
259
- if (picker.remove) view.dispatch(asStep(view.state.tr.delete(picker.remove.from, picker.remove.to)));
260
- insertFiles(view, input.files, index >= 0 ? hostPlace(view.state, index) : picker.place);
221
+ insertFiles(view, files, index >= 0 ? hostPlace(view.state, index) : picker.place);
222
+ showBookmark(view);
261
223
  }
262
224
  }
263
225
  input.value = "";
@@ -277,6 +239,7 @@ export function mediaPlugin({ upload }) {
277
239
  return {
278
240
  destroy() {
279
241
  view.dom.removeEventListener("click", onClick);
242
+ if (editorView === view) editorView = null;
280
243
  const rt = runtime.get(view);
281
244
  runtime.delete(view);
282
245
  for (const it of mediaKey.getState(view.state).items) if (it.preview) URL.revokeObjectURL?.(it.preview);
@@ -288,8 +251,9 @@ export function mediaPlugin({ upload }) {
288
251
  props: {
289
252
  decorations(st) {
290
253
  const { items } = mediaKey.getState(st);
291
- if (!items.length) return null;
292
- return DecorationSet.create(st.doc, items.map((it) => Decoration.widget(boundary(st.doc, it.pos), (view) => bookmarkDOM(view.dom.ownerDocument, it), {
254
+ const render = editorView ? mediaViews.get(editorView) : null;
255
+ if (!items.length || !render) return null;
256
+ return DecorationSet.create(st.doc, items.map((it) => Decoration.widget(boundary(st.doc, it.pos), (view) => render(view.dom.ownerDocument, it), {
293
257
  side: -1000 + it.order, key: `${it.id}:${it.state}:${it.percent}`, stopEvent: () => true, ignoreSelection: true,
294
258
  })));
295
259
  },
@@ -298,7 +262,11 @@ export function mediaPlugin({ upload }) {
298
262
  const files = [...(data?.files ?? [])];
299
263
  if (!files.length || view.state.selection.$from.parent.type.spec.code) return false;
300
264
  if (htmlHasText(data.getData("text/html") || "")) return false;
301
- return insertFiles(view, files);
265
+ const place = hostPlace(view.state, view.state.selection.$head.index(0));
266
+ event.preventDefault();
267
+ insertFiles(view, files, place);
268
+ showBookmark(view);
269
+ return true;
302
270
  },
303
271
  handleDrop(view, event, _slice, moved) {
304
272
  const files = [...(event.dataTransfer?.files ?? [])];
@@ -310,7 +278,10 @@ export function mediaPlugin({ upload }) {
310
278
  const r = view.nodeDOM(ents[i].at)?.getBoundingClientRect?.();
311
279
  const before = r && event.clientY < r.top + r.height / 2;
312
280
  event.preventDefault();
313
- return insertFiles(view, files, { at: before ? ents[i].at : ents[ents[i].subEnd].end, parent: ents[i].node.attrs.parent ?? null, replaceId: null });
281
+ const place = { at: before ? ents[i].at : ents[ents[i].subEnd].end, parent: ents[i].node.attrs.parent ?? null, replaceId: null };
282
+ insertFiles(view, files, place);
283
+ showBookmark(view);
284
+ return true;
314
285
  },
315
286
  },
316
287
  });
@@ -326,189 +297,6 @@ export function pickFiles(view, accept, place = hostPlace(view.state, view.state
326
297
  return true;
327
298
  }
328
299
 
329
- const MIN_WIDTH = 48;
330
-
331
- const ALIGNS = [
332
- ["left", "Align left", '<path d="M2.5 3.5h11M2.5 6.5h6M2.5 9.5h11M2.5 12.5h6"/>'],
333
- ["center", "Centre", '<path d="M2.5 3.5h11M5 6.5h6M2.5 9.5h11M5 12.5h6"/>'],
334
- ["right", "Align right", '<path d="M2.5 3.5h11M7.5 6.5h6M2.5 9.5h11M7.5 12.5h6"/>'],
335
- ];
336
-
337
- export class ImageView {
338
- constructor(node, view, getPos) {
339
- this.node = node;
340
- this.view = view;
341
- this.getPos = getPos;
342
- const doc = view.dom.ownerDocument;
343
- this.dom = doc.createElement("figure");
344
- this.dom.className = "page-image";
345
- this.dom.addEventListener("dragstart", (event) => { event.preventDefault(); event.stopPropagation(); });
346
- this.dom.draggable = false;
347
- this.frame = doc.createElement("div");
348
- this.frame.className = "page-image-frame";
349
- this.img = doc.createElement("img");
350
- this.img.draggable = false;
351
- this.img.addEventListener("error", () => this.dom.toggleAttribute("data-broken", true));
352
- this.img.addEventListener("load", () => this.dom.removeAttribute("data-broken"));
353
- this.handle = doc.createElement("span");
354
- this.handle.className = "page-image-handle";
355
- this.handle.setAttribute("aria-hidden", "true");
356
- this.handle.addEventListener("pointerdown", (e) => this.resize(e));
357
- this.alignBar = doc.createElement("div");
358
- this.alignBar.className = "page-image-align";
359
- this.alignBar.setAttribute("role", "toolbar");
360
- this.alignBar.setAttribute("aria-label", "Image alignment");
361
- for (const [value, label, path] of ALIGNS) {
362
- const b = doc.createElement("button");
363
- b.type = "button";
364
- b.dataset.align = value;
365
- b.setAttribute("aria-label", label);
366
- b.title = label;
367
- b.innerHTML = `<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">${path}</svg>`;
368
- this.alignBar.append(b);
369
- }
370
- this.alignBar.addEventListener("mousedown", (e) => e.preventDefault());
371
- this.alignBar.addEventListener("click", (e) => {
372
- const b = e.target.closest("[data-align]");
373
- if (b) this.setAlign(b.dataset.align === "center" ? null : b.dataset.align);
374
- });
375
- this.frame.append(this.img, this.handle, this.alignBar);
376
- this.caption = doc.createElement("figcaption");
377
- this.caption.className = "page-image-caption";
378
- this.caption.contentEditable = "plaintext-only";
379
- this.caption.setAttribute("aria-label", "Caption");
380
- this.caption.dataset.placeholder = "Add a caption";
381
- this.caption.addEventListener("keydown", (e) => {
382
- if (e.isComposing || this.view.composing) return;
383
- if (e.key === "Enter") { e.preventDefault(); this.commitCaption(); this.view.focus(); }
384
- if (e.key === "Escape") { e.preventDefault(); this.caption.textContent = this.node.attrs.caption; this.view.focus(); }
385
- });
386
- this.caption.addEventListener("blur", () => this.commitCaption());
387
- this.caption.addEventListener("input", () => this.caption.toggleAttribute("data-empty", !this.caption.textContent));
388
- this.dom.append(this.frame, this.caption);
389
- this.render(node);
390
- }
391
-
392
- render(node) {
393
- this.node = node;
394
- this.dom.dataset.id = node.attrs.id ?? "";
395
- if (this.img.getAttribute("src") !== node.attrs.src) this.img.src = node.attrs.src;
396
- this.img.alt = node.attrs.alt || "";
397
- this.frame.style.width = node.attrs.width ? `${node.attrs.width}px` : "";
398
- if (node.attrs.align) this.dom.dataset.align = node.attrs.align;
399
- else delete this.dom.dataset.align;
400
- for (const b of this.alignBar.children) b.setAttribute("aria-pressed", String((node.attrs.align ?? "center") === b.dataset.align));
401
- if (this.dom.ownerDocument.activeElement !== this.caption) this.caption.textContent = node.attrs.caption || "";
402
- this.caption.toggleAttribute("data-empty", !node.attrs.caption);
403
- }
404
-
405
- update(node) {
406
- if (node.type !== this.node.type) return false;
407
- this.render(node);
408
- return true;
409
- }
410
-
411
- setAlign(align) {
412
- const pos = this.getPos();
413
- if (pos == null || align === (this.node.attrs.align ?? null)) return;
414
- this.view.dispatch(asStep(this.view.state.tr.setNodeMarkup(pos, null, { ...this.node.attrs, align })));
415
- }
416
-
417
- commitCaption() {
418
- const text = this.caption.textContent.replace(/\s+/g, " ").trim();
419
- const pos = this.getPos();
420
- if (pos == null || text === (this.node.attrs.caption || "")) return;
421
- this.view.dispatch(asStep(this.view.state.tr.setNodeMarkup(pos, null, { ...this.node.attrs, caption: text })));
422
- }
423
-
424
- resize(event) {
425
- event.preventDefault();
426
- event.stopPropagation();
427
- const col = this.view.dom.getBoundingClientRect().width;
428
- const start = this.frame.getBoundingClientRect().width;
429
- const rtl = this.dom.ownerDocument.defaultView.getComputedStyle(this.dom).direction === "rtl";
430
- const x0 = event.clientX;
431
- const target = event.currentTarget;
432
- target.setPointerCapture?.(event.pointerId);
433
- let width = this.node.attrs.width;
434
- const move = (e) => {
435
- const dx = (e.clientX - x0) * (rtl ? -1 : 1);
436
- width = Math.max(MIN_WIDTH, Math.min(Math.floor(col) || MIN_WIDTH, Math.round(start + dx)));
437
- this.frame.style.width = `${width}px`;
438
- };
439
- const done = (commit) => {
440
- target.removeEventListener("pointermove", move);
441
- target.removeEventListener("pointerup", up);
442
- target.removeEventListener("pointercancel", cancel);
443
- const pos = this.getPos();
444
- if (commit && pos != null && width !== this.node.attrs.width) this.view.dispatch(asStep(this.view.state.tr.setNodeMarkup(pos, null, { ...this.node.attrs, width })));
445
- else this.frame.style.width = this.node.attrs.width ? `${this.node.attrs.width}px` : "";
446
- };
447
- const up = () => done(true);
448
- const cancel = () => done(false);
449
- target.addEventListener("pointermove", move);
450
- target.addEventListener("pointerup", up);
451
- target.addEventListener("pointercancel", cancel);
452
- }
453
-
454
- selectNode() { this.dom.classList.add("is-selected"); }
455
- deselectNode() { this.dom.classList.remove("is-selected"); }
456
- stopEvent(event) { return this.caption.contains(event.target) || this.alignBar.contains(event.target) || event.target.classList?.contains("page-image-handle"); }
457
- ignoreMutation() { return true; }
458
- }
459
-
460
- const sizeLabel = (bytes) => {
461
- if (bytes == null) return "";
462
- if (bytes < 1024) return `${bytes} B`;
463
- if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
464
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
465
- };
466
-
467
- export class FileView {
468
- constructor(node, view) {
469
- this.node = node;
470
- const doc = view.dom.ownerDocument;
471
- this.dom = doc.createElement("div");
472
- this.dom.className = "page-file";
473
- this.dom.addEventListener("dragstart", (event) => { event.preventDefault(); event.stopPropagation(); });
474
- this.link = doc.createElement("a");
475
- this.link.className = "page-file-link";
476
- this.link.target = "_blank";
477
- this.link.rel = "noopener noreferrer";
478
- this.icon = doc.createElement("span");
479
- this.icon.className = "page-file-icon";
480
- this.icon.textContent = "⎘";
481
- this.name = doc.createElement("span");
482
- this.name.className = "page-file-name";
483
- this.meta = doc.createElement("span");
484
- this.meta.className = "page-file-meta";
485
- this.link.append(this.icon, this.name, this.meta);
486
- this.dom.append(this.link);
487
- this.render(node);
488
- }
489
-
490
- render(node) {
491
- this.node = node;
492
- this.dom.dataset.id = node.attrs.id ?? "";
493
- const href = safeHref(node.attrs.src);
494
- if (href) this.link.setAttribute("href", href);
495
- else this.link.removeAttribute("href");
496
- this.name.textContent = node.attrs.name || "file";
497
- this.meta.textContent = sizeLabel(node.attrs.size);
498
- }
499
-
500
- update(node) {
501
- if (node.type !== this.node.type) return false;
502
- this.render(node);
503
- return true;
504
- }
505
-
506
- selectNode() { this.dom.classList.add("is-selected"); }
507
- deselectNode() { this.dom.classList.remove("is-selected"); }
508
- stopEvent(event) { return this.link.contains(event.target); }
509
- ignoreMutation() { return true; }
510
- }
511
-
512
300
  const indexOfId = (doc, id) => {
513
301
  let found = -1;
514
302
  doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
@@ -0,0 +1,11 @@
1
+ // One outline per document: a ProseMirror document never changes, so its outline is
2
+ // built on first ask and reused by every pointer frame, menu and drag until the next edit.
3
+ import { outline } from "./blocks.js";
4
+
5
+ const outlines = new WeakMap();
6
+
7
+ export function outlineOf(doc) {
8
+ let ents = outlines.get(doc);
9
+ if (!ents) outlines.set(doc, ents = outline(doc));
10
+ return ents;
11
+ }
@@ -4,19 +4,39 @@ import { schema } from "./schema.js";
4
4
  const { Decoration, DecorationSet } = pmView;
5
5
 
6
6
  export const PLACEHOLDER_TEXT = "Type / for blocks";
7
+ export const TITLE_PLACEHOLDER = "Untitled";
8
+
9
+ function titlePlaceholder(doc) {
10
+ let done = false;
11
+ let decoration = null;
12
+ doc.forEach((node, offset) => {
13
+ if (done) return;
14
+ if (node.type !== schema.nodes.heading || node.attrs.level !== 1 || (node.attrs.parent ?? null) !== null) return;
15
+ done = true;
16
+ if (!node.content.size) decoration = Decoration.node(offset, offset + node.nodeSize, { class: "page-title-placeholder", "data-placeholder": TITLE_PLACEHOLDER });
17
+ });
18
+ return decoration;
19
+ }
7
20
 
8
21
  export function placeholderPlugin(text = PLACEHOLDER_TEXT) {
9
22
  return new state.Plugin({
10
23
  props: {
11
24
  decorations(st) {
25
+ const decorations = [];
26
+ const title = titlePlaceholder(st.doc);
27
+ if (title) decorations.push(title);
12
28
  const { selection } = st;
13
- if (!selection.empty) return null;
14
- const $from = selection.$from;
15
- if ($from.depth !== 1) return null;
16
- const node = $from.parent;
17
- if (node.type !== schema.nodes.paragraph || node.content.size) return null;
18
- const pos = $from.before(1);
19
- return DecorationSet.create(st.doc, [Decoration.node(pos, pos + node.nodeSize, { class: "page-placeholder", "data-placeholder": text })]);
29
+ if (selection.empty) {
30
+ const $from = selection.$from;
31
+ if ($from.depth === 1) {
32
+ const node = $from.parent;
33
+ if (node.type === schema.nodes.paragraph && !node.content.size) {
34
+ const pos = $from.before(1);
35
+ decorations.push(Decoration.node(pos, pos + node.nodeSize, { class: "page-placeholder", "data-placeholder": text }));
36
+ }
37
+ }
38
+ }
39
+ return decorations.length ? DecorationSet.create(st.doc, decorations) : null;
20
40
  },
21
41
  },
22
42
  });