@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
@@ -0,0 +1,383 @@
1
+ import { schema } from "../schema.js";
2
+ import { orderedIds } from "../model.js";
3
+ import { DEFAULT_WIDTH, MAX_WIDTH, MIN_WIDTH, addColumn, addRow, deleteColumn, deleteRow, moveColumn, moveRow, renameColumn, selectTableCell, setCellText, setColumnKind, setColumnWidth, sortBy } from "../table.js";
4
+ import { COLUMN_KINDS } from "../rich/csv.js";
5
+ import { KIND_ICONS, KIND_LABELS, columnKinds } from "../table-kinds.js";
6
+ import { openPageMenu } from "../chrome.js";
7
+ import { insertChartAfterTable } from "../chart.js";
8
+ import { selectLine } from "../table-select.js";
9
+ import { TABLE_CSS } from "../table-style.js";
10
+ import { cellViews } from "../table-views.js";
11
+ import { asStep } from "../blocksel.js";
12
+
13
+ const N = schema.nodes;
14
+
15
+ const columns = (table) => orderedIds(table.attrs.cols);
16
+
17
+ const FONT = ["fontFamily", "fontSize", "fontWeight", "fontStyle", "letterSpacing"];
18
+ const textWidth = (el) => {
19
+ const doc = el.ownerDocument;
20
+ const style = doc.defaultView.getComputedStyle(el);
21
+ const probe = doc.createElement("span");
22
+ for (const key of FONT) probe.style[key] = style[key];
23
+ Object.assign(probe.style, { position: "absolute", visibility: "hidden", whiteSpace: "pre", left: "-9999px", top: "0" });
24
+ probe.textContent = el.textContent;
25
+ doc.body.append(probe);
26
+ const width = probe.getBoundingClientRect().width;
27
+ probe.remove();
28
+ return Math.ceil(width);
29
+ };
30
+ const widthOf = (value) => (Number.isFinite(value) && value > 0 ? Math.round(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, value))) : null);
31
+
32
+ const button = (doc, label, action) => {
33
+ const el = doc.createElement("button");
34
+ el.type = "button";
35
+ el.textContent = label;
36
+ el.setAttribute("aria-label", label);
37
+ el.addEventListener("click", action);
38
+ return el;
39
+ };
40
+
41
+ export class TableView {
42
+ constructor(node, view, getPos) {
43
+ this.node = node;
44
+ this.view = view;
45
+ this.getPos = getPos;
46
+ this.preview = null;
47
+ this.focusCol = null;
48
+ const doc = view.dom.ownerDocument;
49
+ this.dom = doc.createElement("div");
50
+ this.dom.className = "page-table";
51
+ this.dom.dataset.id = node.attrs.id ?? "";
52
+ this.dom.setAttribute("role", "group");
53
+ this.dom.setAttribute("aria-label", "Table");
54
+ const scroll = doc.createElement("div");
55
+ scroll.className = "page-table-scroll";
56
+ const table = doc.createElement("table");
57
+ this.table = table;
58
+ this.colgroup = doc.createElement("colgroup");
59
+ this.header = doc.createElement("thead");
60
+ this.header.contentEditable = "false";
61
+ this.contentDOM = doc.createElement("tbody");
62
+ table.append(this.colgroup, this.header, this.contentDOM);
63
+ scroll.append(table);
64
+ const addRowButton = button(doc, "+", () => this.append("row"));
65
+ addRowButton.className = "page-table-add-row";
66
+ addRowButton.setAttribute("aria-label", "Add a row");
67
+ addRowButton.title = "Add a row";
68
+ const addColumnButton = button(doc, "+", () => this.append("column"));
69
+ addColumnButton.className = "page-table-add-col";
70
+ addColumnButton.setAttribute("aria-label", "Add a column");
71
+ addColumnButton.title = "Add a column";
72
+ for (const control of [addRowButton, addColumnButton]) {
73
+ control.contentEditable = "false";
74
+ control.addEventListener("mousedown", (event) => event.preventDefault());
75
+ }
76
+ this.dom.append(scroll, addRowButton, addColumnButton);
77
+ this.render();
78
+ }
79
+ append(what) {
80
+ const pos = this.getPos();
81
+ if (pos == null) return;
82
+ const last = what === "row" ? [this.node.childCount - 1, 0] : [0, this.node.child(0).childCount - 1];
83
+ selectTableCell(this.view, pos, ...last);
84
+ (what === "row" ? addRow(true) : addColumn(true))(this.view.state, this.view.dispatch);
85
+ this.view.focus();
86
+ }
87
+ widths() {
88
+ const sized = columns(this.node).some((id) => this.node.attrs.cols[id].width);
89
+ const cols = columns(this.node).map((id) => {
90
+ const col = this.dom.ownerDocument.createElement("col");
91
+ col.dataset.col = id;
92
+ const width = this.node.attrs.cols[id].width;
93
+ if (sized) col.style.width = `${width ?? DEFAULT_WIDTH}px`;
94
+ return col;
95
+ });
96
+ this.colgroup.replaceChildren(...cols);
97
+ this.table.classList.toggle("is-sized", sized);
98
+ this.table.style.width = sized ? `${columns(this.node).reduce((sum, id) => sum + (this.node.attrs.cols[id].width ?? DEFAULT_WIDTH), 0)}px` : "";
99
+ }
100
+ resizer(colId, th, col, index) {
101
+ const doc = this.dom.ownerDocument;
102
+ const handle = doc.createElement("span");
103
+ handle.className = "page-table-resize";
104
+ handle.dataset.col = colId;
105
+ handle.setAttribute("role", "separator");
106
+ handle.setAttribute("aria-orientation", "vertical");
107
+ handle.setAttribute("aria-label", `Resize column ${col.name || `Column ${index + 1}`}`);
108
+ handle.setAttribute("aria-valuemin", String(MIN_WIDTH));
109
+ handle.setAttribute("aria-valuemax", String(MAX_WIDTH));
110
+ handle.setAttribute("aria-valuenow", String(this.widthOfColumn(colId)));
111
+ handle.tabIndex = 0;
112
+ handle.title = "Drag or use the arrow keys to resize, double-click to fit";
113
+ handle.addEventListener("mousedown", (event) => event.preventDefault());
114
+ handle.addEventListener("focus", () => { this.focusCol = colId; });
115
+ handle.addEventListener("blur", () => this.commitWidth(colId));
116
+ handle.addEventListener("keydown", (event) => this.resizeKey(event, colId));
117
+ handle.addEventListener("keyup", (event) => {
118
+ if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) this.commitWidth(colId);
119
+ });
120
+ handle.addEventListener("pointerdown", (event) => {
121
+ event.preventDefault();
122
+ const start = event.clientX;
123
+ const from = this.node.attrs.cols[colId].width ?? (th.getBoundingClientRect().width || DEFAULT_WIDTH);
124
+ const rtl = this.dom.ownerDocument.defaultView.getComputedStyle?.(this.dom).direction === "rtl";
125
+ let width = from;
126
+ const move = (e) => {
127
+ width = widthOf(from + (rtl ? start - e.clientX : e.clientX - start)) ?? from;
128
+ const col = [...this.colgroup.children].find((el) => el.dataset.col === colId);
129
+ if (col) { this.table.classList.add("is-sized"); col.style.width = `${width}px`; }
130
+ };
131
+ const up = () => {
132
+ doc.removeEventListener("pointermove", move);
133
+ doc.removeEventListener("pointerup", up);
134
+ if (width !== from) this.command(setColumnWidth(colId, width));
135
+ else this.widths();
136
+ };
137
+ doc.addEventListener("pointermove", move);
138
+ doc.addEventListener("pointerup", up);
139
+ });
140
+ handle.addEventListener("dblclick", (event) => { event.preventDefault(); this.fit(colId); });
141
+ return handle;
142
+ }
143
+ widthOfColumn(colId) {
144
+ return this.node.attrs.cols[colId]?.width ?? DEFAULT_WIDTH;
145
+ }
146
+ handleFor(colId) {
147
+ return [...this.header.querySelectorAll(".page-table-resize")].find((handle) => handle.dataset.col === colId) ?? null;
148
+ }
149
+ // The arrow keys, Home, End, Enter and Escape preview a width; the key that
150
+ // stops repeating, Enter, or leaving the handle commits the one command.
151
+ resizeKey(event, colId) {
152
+ const step = event.shiftKey ? 50 : 10;
153
+ const base = this.preview?.col === colId ? this.preview.width : this.widthOfColumn(colId);
154
+ let next;
155
+ if (event.key === "ArrowLeft") next = base - step;
156
+ else if (event.key === "ArrowRight") next = base + step;
157
+ else if (event.key === "Home") next = MIN_WIDTH;
158
+ else if (event.key === "End") next = MAX_WIDTH;
159
+ else if (event.key === "Enter") { event.preventDefault(); this.commitWidth(colId); return; }
160
+ else if (event.key === "Escape") { event.preventDefault(); this.cancelWidth(colId); return; }
161
+ else return;
162
+ event.preventDefault();
163
+ this.previewWidth(colId, Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, Math.round(next))));
164
+ }
165
+ previewWidth(colId, width) {
166
+ const base = this.preview?.col === colId ? this.preview.base : this.widthOfColumn(colId);
167
+ this.preview = { col: colId, width, base };
168
+ const col = [...this.colgroup.children].find((element) => element.dataset.col === colId);
169
+ if (col) { this.table.classList.add("is-sized"); col.style.width = `${width}px`; }
170
+ const handle = this.handleFor(colId);
171
+ if (handle) handle.setAttribute("aria-valuenow", String(width));
172
+ }
173
+ cancelWidth(colId) {
174
+ if (this.preview?.col === colId) this.preview = null;
175
+ this.widths();
176
+ this.handleFor(colId)?.setAttribute("aria-valuenow", String(this.widthOfColumn(colId)));
177
+ }
178
+ // The table and the column are looked up again here: a peer may have removed
179
+ // either one while the preview was up, and then there is nothing to commit.
180
+ commitWidth(colId) {
181
+ const preview = this.preview;
182
+ if (!preview || preview.col !== colId) return false;
183
+ this.preview = null;
184
+ const pos = this.getPos();
185
+ const table = pos == null ? null : this.view.state.doc.nodeAt(pos);
186
+ const col = table?.type === N.table && table.attrs.id === this.node.attrs.id ? table.attrs.cols[colId] : null;
187
+ if (!col) { this.widths(); return false; }
188
+ if ((col.width ?? DEFAULT_WIDTH) === preview.width) { this.widths(); return false; }
189
+ const cols = { ...table.attrs.cols, [colId]: { ...col, width: preview.width } };
190
+ this.view.dispatch(asStep(this.view.state.tr.setNodeMarkup(pos, null, { ...table.attrs, cols })));
191
+ return true;
192
+ }
193
+ command(run) {
194
+ const pos = this.getPos();
195
+ if (pos == null) return false;
196
+ selectTableCell(this.view, pos, 0, 0);
197
+ const done = run(this.view.state, this.view.dispatch);
198
+ this.view.focus();
199
+ return done;
200
+ }
201
+ fit(colId) {
202
+ let widest = 0;
203
+ for (const cell of this.contentDOM.querySelectorAll("td")) if (cell.dataset.col === colId) widest = Math.max(widest, textWidth(cell.firstElementChild ?? cell));
204
+ const head = [...this.header.querySelectorAll("th")].find((th) => th.dataset.col === colId)?.querySelector("button");
205
+ widest = Math.max(widest, head?.scrollWidth ?? 0);
206
+ return this.command(setColumnWidth(colId, widest + 24));
207
+ }
208
+ render(refocusCol = null) {
209
+ const doc = this.dom.ownerDocument;
210
+ const row = doc.createElement("tr");
211
+ const effective = columnKinds(this.node);
212
+ this.widths();
213
+ for (const [index, colId] of columns(this.node).entries()) {
214
+ const col = this.node.attrs.cols[colId];
215
+ const kind = effective[colId];
216
+ const th = doc.createElement("th");
217
+ th.dataset.col = colId;
218
+ th.dataset.kind = kind;
219
+ if (this.node.attrs.sort?.col === colId) th.setAttribute("aria-sort", this.node.attrs.sort.dir === "asc" ? "ascending" : "descending");
220
+ const control = button(doc, col.name || `Column ${index + 1}`, (event) => {
221
+ event.preventDefault();
222
+ const run = (command) => () => { selectTableCell(this.view, this.getPos(), 0, index); command(this.view.state, this.view.dispatch); this.view.focus(); };
223
+ const rename = () => {
224
+ const input = doc.createElement("input");
225
+ input.type = "text";
226
+ input.value = col.name;
227
+ input.setAttribute("aria-label", "Column name");
228
+ const finish = (save) => {
229
+ if (save && input.value.trim()) run(renameColumn(colId, input.value))();
230
+ else if (input.isConnected) { input.replaceWith(control); control.focus(); }
231
+ };
232
+ input.addEventListener("keydown", (e) => {
233
+ if (e.key === "Enter") { e.preventDefault(); finish(true); }
234
+ if (e.key === "Escape") { e.preventDefault(); finish(false); }
235
+ e.stopPropagation();
236
+ });
237
+ control.replaceWith(input);
238
+ input.focus();
239
+ input.select();
240
+ };
241
+ openPageMenu(this.view, control, [
242
+ { label: "Rename", run: rename, focus: false },
243
+ { label: "Kind ▸", icon: KIND_ICONS[kind], hint: col.kind ? KIND_LABELS[kind] : `Auto: ${KIND_LABELS[kind]}`, run: () => openPageMenu(this.view, control, [
244
+ { label: "Auto", icon: col.kind ? "" : KIND_ICONS[kind], hint: col.kind ? "" : KIND_LABELS[kind], checked: !col.kind, run: run(setColumnKind(colId, "auto")) },
245
+ ...COLUMN_KINDS.map((option) => ({ label: KIND_LABELS[option], icon: KIND_ICONS[option], checked: col.kind === option, run: run(setColumnKind(colId, option)) })),
246
+ ]), focus: false },
247
+ { label: "Sort ascending", icon: "↑", run: run(sortBy(colId, "asc")) },
248
+ { label: "Sort descending", icon: "↓", run: run(sortBy(colId, "desc")) },
249
+ { label: "Width ▸", icon: "↔", hint: col.width ? `${col.width}px` : "Auto", focus: false, run: () => openPageMenu(this.view, control, [
250
+ { label: "Wider", icon: "+", run: () => this.command(setColumnWidth(colId, (col.width ?? (th.getBoundingClientRect().width || DEFAULT_WIDTH)) + 40)) },
251
+ { label: "Narrower", icon: "−", run: () => this.command(setColumnWidth(colId, (col.width ?? (th.getBoundingClientRect().width || DEFAULT_WIDTH)) - 40)) },
252
+ { label: "Fit to content", icon: "↔", run: () => this.fit(colId) },
253
+ ...(col.width ? [{ label: "Reset width", icon: "×", run: () => this.command(setColumnWidth(colId, null)) }] : []),
254
+ ]) },
255
+ { label: "Chart this table", icon: "📊", run: () => insertChartAfterTable(this.view, this.getPos()) },
256
+ { label: "Insert column left", icon: "+", run: run(addColumn(false)) },
257
+ { label: "Insert column right", icon: "+", run: run(addColumn(true)) },
258
+ ...(index > 0 ? [{ label: "Move left", icon: "←", run: run(moveColumn(-1)) }] : []),
259
+ ...(index < columns(this.node).length - 1 ? [{ label: "Move right", icon: "→", run: run(moveColumn(1)) }] : []),
260
+ { label: "Select column", icon: "▦", run: () => selectLine(this.view, this.getPos(), "col", index) },
261
+ { label: "Delete column", icon: "×", run: run(deleteColumn) },
262
+ ]);
263
+ });
264
+ control.dataset.kind = kind;
265
+ th.append(control, this.resizer(colId, th, col, index));
266
+ row.append(th);
267
+ }
268
+ this.header.replaceChildren(row);
269
+ if (refocusCol) this.handleFor(refocusCol)?.focus();
270
+ }
271
+ update(node) {
272
+ if (node.type !== N.table) return false;
273
+ const focused = this.focusCol && this.dom.contains(this.dom.ownerDocument.activeElement);
274
+ const preview = this.preview;
275
+ this.node = node;
276
+ this.dom.dataset.id = node.attrs.id ?? "";
277
+ this.preview = null;
278
+ this.render(focused ? this.focusCol : null);
279
+ if (preview && node.attrs.cols[preview.col] && (node.attrs.cols[preview.col].width ?? DEFAULT_WIDTH) === preview.base) {
280
+ this.previewWidth(preview.col, preview.width);
281
+ }
282
+ const effective = columnKinds(node);
283
+ for (const cell of this.contentDOM.querySelectorAll("td[data-col]")) cell.dataset.kind = effective[cell.dataset.col] ?? "text";
284
+ return true;
285
+ }
286
+ stopEvent(event) { return !!event.target.closest?.("thead"); }
287
+ ignoreMutation(mutation) { return !this.contentDOM.contains(mutation.target); }
288
+ }
289
+
290
+
291
+ export class TableCellView {
292
+ constructor(node, view, getPos) {
293
+ this.node = node;
294
+ this.view = view;
295
+ this.getPos = getPos;
296
+ this.date = null;
297
+ const doc = view.dom.ownerDocument;
298
+ this.dom = doc.createElement("td");
299
+ cellViews.set(this.dom, this);
300
+ this.dom.dataset.col = node.attrs.col;
301
+ this.contentDOM = doc.createElement("span");
302
+ this.dom.append(this.contentDOM);
303
+ const menu = doc.createElement("button");
304
+ menu.type = "button";
305
+ menu.className = "page-table-row-menu";
306
+ menu.textContent = "Row actions";
307
+ menu.setAttribute("aria-label", "Row actions");
308
+ menu.contentEditable = "false";
309
+ menu.addEventListener("click", () => {
310
+ const $cell = this.view.state.doc.resolve(this.getPos());
311
+ const tablePos = $cell.before(1);
312
+ const row = $cell.index(1);
313
+ const run = (command) => () => { selectTableCell(this.view, tablePos, row, 0); command(this.view.state, this.view.dispatch); this.view.focus(); };
314
+ const count = $cell.node(1).childCount;
315
+ openPageMenu(this.view, menu, [
316
+ { label: "Insert above", icon: "+", run: run(addRow(false)) },
317
+ { label: "Insert below", icon: "+", run: run(addRow(true)) },
318
+ ...(row > 0 ? [{ label: "Move up", icon: "↑", run: run(moveRow(-1)) }] : []),
319
+ ...(row < count - 1 ? [{ label: "Move down", icon: "↓", run: run(moveRow(1)) }] : []),
320
+ { label: "Select row", icon: "▦", run: () => selectLine(this.view, tablePos, "row", row) },
321
+ { label: "Delete row", icon: "×", run: run(deleteRow) },
322
+ ]);
323
+ });
324
+ this.menu = menu;
325
+ this.dom.append(menu);
326
+ this.update(node);
327
+ }
328
+ update(node) {
329
+ if (node.type !== N.table_cell) return false;
330
+ this.node = node;
331
+ const $cell = this.view.state.doc.resolve(this.getPos());
332
+ const table = $cell.node(1);
333
+ this.dom.dataset.kind = columnKinds(table)[node.attrs.col] ?? "text";
334
+ this.menu.hidden = $cell.index(2) !== 0;
335
+ if (this.dom.dataset.kind !== "date") this.closeDate();
336
+ return true;
337
+ }
338
+ openDate() {
339
+ if (this.date || this.dom.dataset.kind !== "date") return false;
340
+ const doc = this.dom.ownerDocument;
341
+ const input = doc.createElement("input");
342
+ input.type = "date";
343
+ input.className = "page-table-date";
344
+ input.contentEditable = "false";
345
+ input.setAttribute("aria-label", "Pick a date");
346
+ const current = this.node.textContent.trim();
347
+ if (/^\d{4}-\d{2}-\d{2}$/.test(current)) input.value = current;
348
+ input.addEventListener("change", () => {
349
+ if (input.value) setCellText(this.view, this.getPos(), input.value);
350
+ this.closeDate();
351
+ this.view.focus();
352
+ });
353
+ input.addEventListener("keydown", (event) => {
354
+ if (event.key === "Escape") { event.preventDefault(); this.closeDate(); this.view.focus(); }
355
+ event.stopPropagation();
356
+ });
357
+ input.addEventListener("blur", () => this.closeDate());
358
+ this.date = input;
359
+ this.dom.append(input);
360
+ try { input.showPicker?.(); } catch {}
361
+ return true;
362
+ }
363
+ closeDate() {
364
+ const input = this.date;
365
+ this.date = null;
366
+ if (input?.isConnected) input.remove();
367
+ }
368
+ destroy() { this.closeDate(); }
369
+ stopEvent(event) { return !!event.target.closest?.(".page-table-row-menu, .page-table-date"); }
370
+ ignoreMutation(mutation) { return !this.contentDOM.contains(mutation.target); }
371
+ }
372
+
373
+ export function createFeature() {
374
+ return {
375
+ id: "table",
376
+ nodeViews: {
377
+ table: (node, view, getPos) => new TableView(node, view, getPos),
378
+ table_cell: (node, view, getPos) => new TableCellView(node, view, getPos),
379
+ },
380
+ css: TABLE_CSS,
381
+ dispose() {},
382
+ };
383
+ }
@@ -1,5 +1,20 @@
1
- import { schema } from "./schema.js";
2
- import { openPageMenu } from "./chrome.js";
1
+ // The tool block: its node view and its CSS. Loaded the first time a document
2
+ // has or takes a tool block; it mounts through the host its caller supplies, or
3
+ // through a host it makes for an editor that has none.
4
+ import { openPageMenu } from "../chrome.js";
5
+ import { schema } from "../schema.js";
6
+ import { createToolHost } from "../tools.js";
7
+ import { toolViews } from "../tool-views.js";
8
+
9
+ export const TOOL_CSS = `
10
+ .page-tool-block { position: relative; min-height: 48px; border: 1px solid var(--page-rule); border-radius: 8px; padding: 12px; }
11
+ .page-tool-settings { position: absolute; top: 4px; inset-inline-end: 4px; border: 0; border-radius: 5px; background: var(--page-pop-bg); color: var(--page-ink-soft); font: 12px var(--page-font); padding: 5px 8px; cursor: pointer; opacity: 0; }
12
+ .page-tool-block:hover .page-tool-settings, .page-tool-block:focus-within .page-tool-settings { opacity: 1; }
13
+ .page-ai-panel { position: relative; z-index: 5; width: min(520px, 100%); box-sizing: border-box; margin: 12px auto; padding: 14px; border: 1px solid var(--page-pop-line); border-radius: 10px; background: var(--page-pop-bg); color: var(--page-pop-ink); box-shadow: var(--page-pop-shadow); }
14
+ .page-ai-panel textarea { width: 100%; min-height: 88px; box-sizing: border-box; padding: 8px; border: 1px solid var(--page-pop-line); border-radius: 6px; background: var(--page-pop-bg); color: inherit; font: inherit; resize: vertical; }
15
+ .page-ai-actions { display: flex; gap: 8px; margin-top: 8px; }
16
+ .page-ai-actions button { border: 1px solid var(--page-pop-line); border-radius: 6px; background: var(--page-pop-bg); color: inherit; font: inherit; padding: 5px 10px; cursor: pointer; }
17
+ .page-ai-status, [data-tool-note], [data-tool-error] { color: var(--page-danger); font-size: 0.85em; }`;
3
18
 
4
19
  export class ToolView {
5
20
  constructor(node, view, getPos, { host, revise = null }) {
@@ -66,3 +81,19 @@ export class ToolView {
66
81
  ignoreMutation() { return true; }
67
82
  destroy() { this.instance?.dispose(); this.instance = null; }
68
83
  }
84
+
85
+ export function createFeature({ toolHost = null, revise = null, view = null } = {}) {
86
+ const host = toolHost ?? createToolHost({ mode: "edit" });
87
+ if (view) toolViews.set(view, host);
88
+ return {
89
+ id: "tool",
90
+ nodeViews: {
91
+ tool: (node, editorView, getPos) => new ToolView(node, editorView, getPos, { host, revise }),
92
+ },
93
+ css: TOOL_CSS,
94
+ dispose() {
95
+ if (view && toolViews.get(view) === host) toolViews.delete(view);
96
+ if (!toolHost) host.dispose();
97
+ },
98
+ };
99
+ }
@@ -0,0 +1,215 @@
1
+ // The inline mark key grammar, read without assuming the schema knows every
2
+ // mark in the key. inline.js keeps a unit's marks as one key: a code per mark,
3
+ // an attrs-carrying mark followed by its values as a JSON array, and a hard
4
+ // break as "@" and the node's JSON.
5
+ //
6
+ // A key may also carry marks this runtime has no schema type for, and those are
7
+ // kept rather than refused:
8
+ // u an unfamiliar compact code, with its exact token
9
+ // z["value"] an unfamiliar compact code carrying attrs
10
+ // x["underline",{}] an extensible named mark: x[name, attrs]
11
+ // x["text_color",{"color":"red"}]
12
+ // `x` takes exactly [name, attrs]; a name matches NAME, holds neither of the
13
+ // Yjs separators "~" and "!", and attrs are plain JSON. Values are kept as they
14
+ // arrived, and descriptors come back in one canonical order, so two peers that
15
+ // saw the same marks write the same key.
16
+ //
17
+ // Unfamiliar tokens and named descriptors have one internal editor mark,
18
+ // opaque_marks, whose entries list them; it is never written as a mark name of
19
+ // its own. Where they must cross the Yjs semantic-name representation, the
20
+ // compact tokens travel under the reserved name clay_opaque_code with
21
+ // { tokens: [...] }, and named descriptors keep their own name.
22
+ import { model } from "../vendor/prosemirror.vendor.js";
23
+ import { schema } from "./schema.js";
24
+
25
+ export const OPAQUE_MARK = "opaque_marks";
26
+ export const OPAQUE_CODE = "clay_opaque_code";
27
+ export const NAMED_CODE = "x";
28
+
29
+ export const NAME = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
30
+
31
+ // A descriptor's JSON is bounded where it is read: past these, the source is
32
+ // refused whole rather than cut short.
33
+ export const MAX_DEPTH = 32;
34
+ export const MAX_KEYS = 10000;
35
+
36
+ const CODE = /[A-Za-z]/;
37
+ const plain = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
38
+ const copy = (value) => (value === null || typeof value !== "object" ? value : JSON.parse(JSON.stringify(value)));
39
+
40
+ // Refuses JSON nested deeper than MAX_DEPTH or holding more than MAX_KEYS keys.
41
+ export function bounded(value) {
42
+ let keys = 0;
43
+ const walk = (item, depth) => {
44
+ if (depth > MAX_DEPTH) throw new RangeError(`page: inline JSON is deeper than ${MAX_DEPTH}`);
45
+ if (item === null || typeof item !== "object") return;
46
+ if (Array.isArray(item)) {
47
+ for (const child of item) walk(child, depth + 1);
48
+ return;
49
+ }
50
+ for (const name of Object.keys(item)) {
51
+ if (++keys > MAX_KEYS) throw new RangeError(`page: inline JSON has more than ${MAX_KEYS} keys`);
52
+ walk(item[name], depth + 1);
53
+ }
54
+ };
55
+ walk(value, 1);
56
+ return value;
57
+ }
58
+
59
+ // The end of the JSON array that starts at s[i].
60
+ export function arrayEnd(s, i) {
61
+ let depth = 0, str = false;
62
+ for (; i < s.length; i++) {
63
+ const ch = s[i];
64
+ if (str) {
65
+ if (ch === "\\") i++;
66
+ else if (ch === '"') str = false;
67
+ } else if (ch === '"') str = true;
68
+ else if (ch === "[") depth++;
69
+ else if (ch === "]" && --depth === 0) return i + 1;
70
+ }
71
+ throw new SyntaxError(`page: unterminated attrs in mark key ${s}`);
72
+ }
73
+
74
+ const bad = (key) => new SyntaxError(`page: malformed mark key ${key}`);
75
+
76
+ // The descriptors of one key, in the order it lists them: { code, attrs, token }
77
+ // for a compact code, its attrs the JSON array as written and token its exact
78
+ // text, and { name, attrs } for a named one.
79
+ export function descriptors(key) {
80
+ if (typeof key !== "string") throw new TypeError("page: a mark key is not a string");
81
+ const out = [];
82
+ for (let i = 0; i < key.length;) {
83
+ const code = key[i];
84
+ if (!CODE.test(code)) throw bad(key);
85
+ let end = i + 1;
86
+ let values = null;
87
+ if (key[end] === "[") {
88
+ end = arrayEnd(key, end);
89
+ values = bounded(JSON.parse(key.slice(i + 1, end)));
90
+ if (!Array.isArray(values)) throw bad(key);
91
+ }
92
+ if (code !== NAMED_CODE) {
93
+ out.push({ code, attrs: values, token: key.slice(i, end) });
94
+ } else {
95
+ if (!values || values.length !== 2 || typeof values[0] !== "string" || !NAME.test(values[0]) || !plain(values[1])) throw new SyntaxError(`page: the named mark in ${key} is not [name, attrs]`);
96
+ if (values[0] === OPAQUE_CODE) throw new SyntaxError(`page: ${OPAQUE_CODE} is reserved`);
97
+ out.push({ name: values[0], attrs: copy(values[1]) });
98
+ }
99
+ i = end;
100
+ }
101
+ return out;
102
+ }
103
+
104
+ const order = (a, b) => {
105
+ if (a.name !== b.name) return a.name < b.name ? -1 : 1;
106
+ const x = JSON.stringify(a.attrs), y = JSON.stringify(b.attrs);
107
+ return x === y ? 0 : x < y ? -1 : 1;
108
+ };
109
+
110
+ // The entries of an opaque_marks mark for these descriptors: named descriptors
111
+ // by name, then the compact tokens in the order they arrived. A descriptor
112
+ // listed twice is ambiguous, so it is refused.
113
+ export function entriesOf(descs) {
114
+ const named = [], tokens = [], seen = new Set();
115
+ for (const desc of descs) {
116
+ if (desc.name === undefined) {
117
+ if (seen.has(desc.token)) throw new TypeError(`page: duplicate mark descriptor ${desc.token}`);
118
+ seen.add(desc.token);
119
+ tokens.push(desc.token);
120
+ } else {
121
+ const id = JSON.stringify([desc.name, desc.attrs]);
122
+ if (seen.has(id)) throw new TypeError(`page: duplicate mark descriptor ${id}`);
123
+ seen.add(id);
124
+ named.push({ name: desc.name, attrs: copy(desc.attrs) });
125
+ }
126
+ }
127
+ named.sort(order);
128
+ return tokens.length ? [...named, { tokens }] : named;
129
+ }
130
+
131
+ // The entries of an opaque_marks mark, as they must be read back: the same
132
+ // validation and the same canonical order however they arrived.
133
+ export function normalizeEntries(entries) {
134
+ if (!Array.isArray(entries)) throw new TypeError("page: opaque entries are not a list");
135
+ const descs = [];
136
+ for (const entry of entries) {
137
+ if (!plain(entry)) throw new TypeError("page: an opaque entry is not an object");
138
+ if (entry.tokens !== undefined) {
139
+ if (Object.keys(entry).length !== 1 || !Array.isArray(entry.tokens)) throw new TypeError("page: an opaque entry is not { tokens: [...] }");
140
+ for (const token of entry.tokens) {
141
+ const parsed = descriptors(token);
142
+ if (parsed.length !== 1 || parsed[0].name !== undefined) throw new SyntaxError(`page: malformed mark token ${token}`);
143
+ descs.push(parsed[0]);
144
+ }
145
+ } else {
146
+ if (Object.keys(entry).length !== 2 || typeof entry.name !== "string" || !NAME.test(entry.name) || !plain(entry.attrs)) throw new TypeError("page: an opaque entry is not { name, attrs }");
147
+ if (entry.name === OPAQUE_CODE) throw new SyntaxError(`page: ${OPAQUE_CODE} is reserved`);
148
+ descs.push({ name: entry.name, attrs: bounded(entry.attrs) });
149
+ }
150
+ }
151
+ return entriesOf(descs);
152
+ }
153
+
154
+ // The key fragment for these entries, as the schema-free half of the record
155
+ // writes it. Named descriptors are canonical JSON; a compact token is written
156
+ // back exactly as it was read.
157
+ export function keyOf(entries) {
158
+ let key = "";
159
+ for (const entry of normalizeEntries(entries)) {
160
+ if (entry.tokens) key += entry.tokens.join("");
161
+ else key += NAMED_CODE + JSON.stringify([entry.name, entry.attrs]);
162
+ }
163
+ return key;
164
+ }
165
+
166
+ // The one internal mark unknown entries live in, which the page schema leaves
167
+ // out of marks: it is not a format, a command or a key code, and no data of
168
+ // ours ever names it as a mark of its own. It is last so a known mark keeps
169
+ // its place in the key.
170
+ const markType = (() => {
171
+ const type = new model.MarkType(OPAQUE_MARK, Object.keys(schema.marks).length, schema, {
172
+ attrs: { entries: { default: [] } },
173
+ inclusive: false,
174
+ excludes: "",
175
+ toDOM: () => ["span", 0],
176
+ });
177
+ type.excluded = [];
178
+ return type;
179
+ })();
180
+
181
+ export const isOpaque = (mark) => mark.type.name === OPAQUE_MARK;
182
+
183
+ export const opaqueMark = (entries) => markType.create({ entries: normalizeEntries(entries) });
184
+
185
+ // The semantic Yjs fields for these entries: a named descriptor under its own
186
+ // name and the compact tokens under the reserved one.
187
+ export function yjsFields(entries) {
188
+ const out = Object.create(null);
189
+ for (const entry of normalizeEntries(entries)) {
190
+ if (entry.tokens) out[OPAQUE_CODE] = { tokens: [...entry.tokens] };
191
+ else out[entry.name] = copy(entry.attrs);
192
+ }
193
+ return out;
194
+ }
195
+
196
+ // The base mark name of a Yjs key, without its operation or cancellation mark.
197
+ export const yjsName = (key) => key.split("!")[0].split("~")[0];
198
+ export const yjsCancelled = (key) => key.includes("!");
199
+
200
+ // The entries a set of Yjs mark fields stands for: cancelled names drop their
201
+ // marks exactly as ybind/marks.js already reads them.
202
+ export function entriesOfYjs(fields) {
203
+ const cancelled = new Set();
204
+ for (const key of Object.keys(fields)) if (yjsCancelled(key) && fields[key]) cancelled.add(key.split("!")[0]);
205
+ const entries = [];
206
+ for (const [key, value] of Object.entries(fields)) {
207
+ if (value === null || value === undefined || yjsCancelled(key) || cancelled.has(key)) continue;
208
+ const name = yjsName(key);
209
+ if (name === OPAQUE_CODE) {
210
+ if (!plain(value) || Object.keys(value).length !== 1 || !Array.isArray(value.tokens)) throw new TypeError(`page: ${OPAQUE_CODE} is not { tokens: [...] }`);
211
+ entries.push({ tokens: value.tokens });
212
+ } else entries.push({ name, attrs: plain(value) ? value : {} });
213
+ }
214
+ return normalizeEntries(entries);
215
+ }