@panphora/clayjs 1.15.2 → 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.
- package/README.md +4 -5
- package/THIRD-PARTY-NOTICES.md +16 -0
- package/compat/highlight-11.9.0.min.js +1213 -0
- package/compat/highlight-LICENSE +29 -0
- package/compat/journal-assets.lock.json +22 -0
- package/compat/overtype-2.6.1.min.js +116 -0
- package/compat/overtype-LICENSE +21 -0
- package/dist/all.js +90 -0
- package/dist/clay-dom.js +84 -0
- package/dist/clay-events.js +103 -0
- package/dist/clay-internals.js +85 -0
- package/dist/clay-options.js +93 -0
- package/dist/clay-router.js +98 -0
- package/dist/clay-ui.js +95 -0
- package/dist/clay-utils.js +84 -0
- package/dist/clay.js +146 -0
- package/dist/clay.standalone.js +83090 -58706
- package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
- package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +1 -0
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2JL6E75N.js +1 -0
- package/dist/m/c-2M73252O.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-33HUYEDI.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-35NBGMZB.js +1 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3IQRECYS.js +1 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4B6H4G5V.js +1 -0
- package/dist/m/c-4DRMS4QG.js +9 -0
- package/dist/m/c-4FFHZM32.js +1 -0
- package/dist/m/c-4JZDZFF6.js +1 -0
- package/dist/m/c-4L3BTFRK.js +1 -0
- package/dist/m/c-4Q2XTQ2N.js +2 -0
- package/dist/m/c-4WKFKEKF.js +1 -0
- package/dist/m/c-4YSCRICO.js +1 -0
- package/dist/m/c-54RCDCBU.js +1 -0
- package/dist/m/c-5M4YEKCX.js +4 -0
- package/dist/m/c-5R4PTRG2.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5Y6TNX5W.js +34 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-66O32MV3.js +28 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7O6AFR65.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-7VGZ7RU5.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-ANQTJ3B5.js +2 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-AYVOREBE.js +1 -0
- package/dist/m/c-B2ZXPOS3.js +3 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BMIZKMPV.js +1 -0
- package/dist/m/c-BTDI4Y23.js +1 -0
- package/dist/m/c-BWFVSBDF.js +1 -0
- package/dist/m/c-BYTKL6UX.js +1 -0
- package/dist/m/c-CFHW5AMY.js +6 -0
- package/dist/m/c-CJZSLMVQ.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DBE6ORSI.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-EDHUR4MX.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EY6UOM54.js +1 -0
- package/dist/m/c-F3CKJTJS.js +1 -0
- package/dist/m/c-F7Y6FLPY.js +1 -0
- package/dist/m/c-FA4DZVTC.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-G2BUZITR.js +1 -0
- package/dist/m/c-GB3JTV6E.js +1 -0
- package/dist/m/c-GBGWXTX4.js +1 -0
- package/dist/m/c-GHBCCVG4.js +1 -0
- package/dist/m/c-GJAEGGXY.js +1 -0
- package/dist/m/c-GOWBDM6J.js +1 -0
- package/dist/m/c-GQ6F6IDK.js +1 -0
- package/dist/m/c-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-HRDCD3WN.js +67 -0
- package/dist/m/c-HT33EQRN.js +1 -0
- package/dist/m/c-HVYXIS72.js +1 -0
- package/dist/m/c-IB52R237.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IKWNE4R4.js +1 -0
- package/dist/m/c-IU3IYEU6.js +1 -0
- package/dist/m/c-IZ2EBRAJ.js +1 -0
- package/dist/m/c-J7CA54IP.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KKWLDOPM.js +1 -0
- package/dist/m/c-KR4UW4KW.js +1 -0
- package/dist/m/c-KY3VJGUA.js +8 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LR4CGEPT.js +91 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LTGMXXKO.js +1 -0
- package/dist/m/c-LXENEBV7.js +1 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-MMUIUUZG.js +2 -0
- package/dist/m/c-MTQHWQC3.js +1 -0
- package/dist/m/c-N326R2JD.js +1 -0
- package/dist/m/c-N7JPUPGC.js +1 -0
- package/dist/m/c-NMJRXKPC.js +8 -0
- package/dist/m/c-NRPJEVJS.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NZMNXVF6.js +1 -0
- package/dist/m/c-OCSZ4QGN.js +1 -0
- package/dist/m/c-OIBUTDXI.js +1 -0
- package/dist/m/c-OIDRZKAF.js +1 -0
- package/dist/m/c-OYTFP5QN.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PPOSCAQD.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QIX5BJYN.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-QQHEX6HA.js +1 -0
- package/dist/m/c-R7RMANJI.js +9 -0
- package/dist/m/c-RI4NT75Y.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RUJKSUQW.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SGCCC4FW.js +25 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SWRJFCNN.js +1 -0
- package/dist/m/c-T4VO4OCB.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TQZ2JYHD.js +1 -0
- package/dist/m/c-TR6RMDQ7.js +3 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UKHTLWUW.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-UNN7LWCT.js +1 -0
- package/dist/m/c-UXBMRDZB.js +235 -0
- package/dist/m/c-V7PZZRG7.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VFX45IZQ.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VL52ADDR.js +1 -0
- package/dist/m/c-WN5AY3OZ.js +1 -0
- package/dist/m/c-WNYBLIOZ.js +1 -0
- package/dist/m/c-WRBKEJD4.js +1 -0
- package/dist/m/c-WWDBYXLK.js +1 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XNNW2CWK.js +1 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y6USGTS5.js +5 -0
- package/dist/m/c-YDH7Q3NY.js +1 -0
- package/dist/m/c-YG2NJYS6.js +135 -0
- package/dist/m/c-Z36J63PP.js +1 -0
- package/dist/m/c-Z4VRC7GU.js +7 -0
- package/dist/m/c-Z6K52OLP.js +19 -0
- package/dist/m/c-ZDY2MDAA.js +21 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
- package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
- package/dist/m/core/persist-HKHA66IY.js +3 -0
- package/dist/m/core/recovery-TBBSICE2.js +1 -0
- package/dist/m/core/save-WUCHTQBE.js +1 -0
- package/dist/m/core/save-core-FJVHT5M3.js +1 -0
- package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
- package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
- package/dist/m/dom/all-APS5ZSKP.js +1 -0
- package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
- package/dist/m/events/index-23DPTUT4.js +1 -0
- package/dist/m/internals/index-WOVAP3RU.js +1 -0
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-HXX6YNH5.js +1 -0
- package/dist/m/manifest-F22DEDAC.js +1 -0
- package/dist/m/manifest-edit-C3C7376B.js +1 -0
- package/dist/m/manifest.json +3491 -0
- package/dist/m/options/options-2LH76JQ3.js +1 -0
- package/dist/m/page/editor-XUTB2HB2.js +189 -0
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
- package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
- package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
- package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
- package/dist/m/plugins/live-NUTTXSBS.js +1 -0
- package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-KSUH7G6J.js +2 -0
- package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
- package/dist/m/plugins/people-AJJ32YCG.js +1 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
- package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
- package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
- package/dist/m/plugins/source-KHK5UUSK.js +1 -0
- package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
- package/dist/m/plugins/upload-XSGNILUY.js +1 -0
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
- package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
- package/dist/m/reader-core-ILWLXVMP.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
- package/dist/m/ui/index-EVHKMKBL.js +1 -0
- package/dist/m/utils/index-4TJCVLT7.js +1 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
- package/dist/offline-manifest.json +3800 -0
- package/dist/page/themes/light-1.css +406 -0
- package/dist/server/core.cjs +23662 -22860
- package/dist/server/offline-runtime.cjs +4954 -0
- package/dist/server/offline-runtime.mjs +4923 -0
- package/dist/server/rooms.mjs +9 -0
- package/docs/guide.md +1078 -669
- package/entries/all.js +2 -0
- package/entries/clay-data.js +1 -1
- package/entries/clay-dom.js +2 -0
- package/entries/clay-events.js +2 -0
- package/entries/clay-internals.js +2 -0
- package/entries/clay-options.js +2 -0
- package/entries/clay-router.js +2 -0
- package/entries/clay-ui.js +2 -0
- package/entries/clay-utils.js +2 -0
- package/entries/clay.js +2 -0
- package/entries/sap.js +6 -6
- package/fonts/ibm-plex-mono-LICENSE +93 -0
- package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-LICENSE +93 -0
- package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex.css +71 -0
- package/fonts/inter-LICENSE +93 -0
- package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
- package/fonts/inter.css +62 -0
- package/fonts/newsreader-LICENSE +93 -0
- package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
- package/fonts/newsreader.css +26 -0
- package/fonts/work-sans-LICENSE +93 -0
- package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
- package/fonts/work-sans.css +8 -0
- package/package.json +20 -3
- package/packed-contract.json +1 -0
- package/src/attrs/add-from.js +27 -4
- package/src/attrs/save-freeze.js +7 -52
- package/src/core/admin-attrs.js +119 -12
- package/src/core/admin-contenteditable.js +12 -10
- package/src/core/admin-inputs.js +14 -15
- package/src/core/admin-onclick.js +2 -0
- package/src/core/autosave.js +74 -21
- package/src/core/conflict-download.js +22 -0
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +72 -8
- package/src/core/host-permission.js +29 -0
- package/src/core/is-edit-mode.js +145 -12
- package/src/core/mode-key.js +125 -0
- package/src/core/mode-lifecycle.js +544 -0
- package/src/core/mode-receipt.js +30 -0
- package/src/core/mode-refusal.js +13 -0
- package/src/core/mode-resources.js +52 -0
- package/src/core/mode-support.js +82 -0
- package/src/core/mode-switch.js +259 -0
- package/src/core/mode-transition.js +112 -0
- package/src/core/page-data-write.js +305 -0
- package/src/core/page-data.js +26 -227
- package/src/core/page-format-wire.js +4 -0
- package/src/core/page-format.js +58 -0
- package/src/core/persist.js +37 -9
- package/src/core/preserve-authored.js +114 -0
- package/src/core/recovery.js +772 -0
- package/src/core/runtime-url.js +105 -0
- package/src/core/save-core.js +109 -12
- package/src/core/save-replace.js +72 -0
- package/src/core/save-size-notice.js +134 -0
- package/src/core/save-size.js +140 -0
- package/src/core/save.js +805 -118
- package/src/core/snapshot.js +121 -11
- package/src/core/source-map.js +157 -9
- package/src/core/unsaved-warning.js +14 -4
- package/src/core/upgrade-run.js +408 -0
- package/src/core/version.js +18 -0
- package/src/lib/assets.js +30 -0
- package/src/lib/autosave-debug-gate.js +50 -0
- package/src/lib/autosave-scheduler.js +141 -0
- package/src/lib/boot-imports.js +8 -0
- package/src/lib/derived.js +3 -1
- package/src/lib/dirty-gate.js +15 -0
- package/src/lib/doc-storage.js +33 -0
- package/src/lib/effect.js +9 -4
- package/src/lib/mutation.js +4 -2
- package/src/lib/offline-runtime.js +226 -0
- package/src/lib/preload.js +48 -0
- package/src/lib/root-attrs.js +1 -1
- package/src/lib/trash.js +18 -0
- package/src/lib/undo/capture-move.js +152 -0
- package/src/live/local-channel.js +61 -0
- package/src/live/local-lane.js +60 -9
- package/src/live/server-lane.js +6 -3
- package/src/loader-logic.js +76 -15
- package/src/loader.js +218 -30
- package/src/page/adopt-boot.js +76 -22
- package/src/page/adopt.js +25 -4
- package/src/page/awareness.js +230 -0
- package/src/page/blocks.js +79 -5
- package/src/page/blocksel.js +20 -4
- package/src/page/board-face.js +13 -0
- package/src/page/board-panel.js +5 -17
- package/src/page/board-style.js +2 -13
- package/src/page/board-view.js +1 -11
- package/src/page/board-views.js +17 -0
- package/src/page/board.js +2 -2
- package/src/page/boot-pack.js +26 -0
- package/src/page/boot.js +237 -0
- package/src/page/checklist.js +1 -1
- package/src/page/chrome.js +411 -61
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +40 -0
- package/src/page/document-title-runtime.js +76 -0
- package/src/page/document-title.js +9 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +330 -86
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +12 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/registry.js +23 -0
- package/src/page/features/table.js +383 -0
- package/src/page/{tool-view.js → features/tool.js} +33 -2
- package/src/page/format-codec.js +215 -0
- package/src/page/format-gate.js +179 -0
- package/src/page/format-level.js +157 -0
- package/src/page/highlight-slot.js +23 -0
- package/src/page/highlight.js +30 -16
- package/src/page/history-intent.js +78 -0
- package/src/page/inline.js +18 -25
- package/src/page/items.js +52 -11
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +86 -1
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +196 -50
- package/src/page/reader-patch.js +306 -0
- package/src/page/recover.js +1 -1
- package/src/page/rich/page-tool.js +1 -4
- package/src/page/runtime-owner.js +27 -0
- package/src/page/save-projection.js +7 -4
- package/src/page/schema.js +14 -0
- package/src/page/table-rect-key.js +3 -0
- package/src/page/table-select.js +4 -3
- package/src/page/table-style.js +1 -1
- package/src/page/table-views.js +3 -0
- package/src/page/table.js +8 -293
- package/src/page/themes/light-1.css +139 -2
- package/src/page/themes/light-1.source.css +269 -0
- package/src/page/tool-capability.js +3 -0
- package/src/page/tool-views.js +3 -0
- package/src/page/view.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +9 -1
- package/src/page/ybind/bridge.js +50 -47
- package/src/page/ybind/decode.js +52 -19
- package/src/page/ybind/doc.js +27 -2
- package/src/page/ybind/group.js +12 -3
- package/src/page/ybind/index.js +2 -0
- package/src/page/ybind/lineage.js +71 -0
- package/src/page/ybind/marks.js +25 -1
- package/src/page/ybind/migrate.js +48 -13
- package/src/page/ybind/moves.js +32 -21
- package/src/page/ybind/positions.js +50 -0
- package/src/page/ybind/provider.js +84 -22
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +130 -0
- package/src/page/ybind/tokens.js +15 -6
- package/src/page/ybind/walk.js +10 -3
- package/src/page/ybind/writer.js +48 -4
- package/src/plugins/ai-edit.js +167 -32
- package/src/plugins/bind-text.js +218 -0
- package/src/plugins/cms.js +93 -0
- package/src/plugins/indicator.js +57 -5
- package/src/plugins/live.js +282 -34
- package/src/plugins/menu/claygoo.svg.js +1 -0
- package/src/plugins/menu/seo-head.js +323 -0
- package/src/plugins/menu/seo.js +849 -0
- package/src/plugins/menu.js +425 -0
- package/src/plugins/page-view.js +131 -0
- package/src/plugins/page.js +1022 -275
- package/src/plugins/people.js +158 -31
- package/src/plugins/richclay.js +119 -9
- package/src/plugins/sortable.js +370 -60
- package/src/plugins/source.js +189 -366
- package/src/plugins/undo.js +289 -19
- package/src/plugins/unsent.js +132 -0
- package/src/plugins/upgrade-run.js +1 -0
- package/src/plugins/upgrade.js +210 -0
- package/src/plugins/upload-task.js +571 -0
- package/src/plugins/upload-ui.js +265 -0
- package/src/plugins/upload.js +34 -153
- package/src/plugins/visitor-drafts/inert.js +32 -0
- package/src/plugins/visitor-drafts/owner-chip.js +3 -0
- package/src/plugins/visitor-drafts/prompt.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +85 -0
- package/src/plugins/visitor-drafts.js +31 -42
- package/src/plugins/wire.js +82 -1
- package/src/reader-core.js +26 -0
- package/src/recovery/conflicts.js +287 -0
- package/src/recovery/identity.js +154 -0
- package/src/recovery/store.js +792 -0
- package/src/recovery/undo.js +257 -0
- package/src/server/checkpoint/index.js +68 -4
- package/src/server/offline-runtime/index.js +214 -0
- package/src/server/rooms/index.js +2 -1
- package/src/server/rooms/live-data-rooms.js +2 -2
- package/src/server/rooms/page-admission.js +33 -0
- package/src/server/rooms/page-lineage.js +56 -0
- package/src/server/rooms/page-rebase.js +11 -5
- package/src/server/rooms/page-state.js +2 -0
- package/src/server/rooms/page-write-admission.js +9 -3
- package/src/source/session.js +705 -0
- package/src/sync/conflict-revert.js +5 -4
- package/src/sync/conflicts-edit.js +119 -0
- package/src/sync/conflicts-facade.js +49 -0
- package/src/sync/conflicts.js +99 -1
- package/src/sync/editing-marker.js +37 -6
- package/src/sync/editing-sender.js +8 -1
- package/src/sync/live-sync-edit.js +63 -0
- package/src/sync/live-sync.js +364 -119
- package/src/sync/local-presence.js +187 -0
- package/src/sync/merge-declarations.js +10 -0
- package/src/sync/merge-engine.js +12 -0
- package/src/sync/presence-store.js +412 -0
- package/src/sync/presence-target.js +243 -0
- package/src/sync/presence.js +72 -41
- package/src/sync/saved-handoff.js +200 -0
- package/src/sync/saved-stream.js +535 -0
- package/src/sync/version-guard.js +92 -0
- package/src/sync/wire-profile.js +81 -0
- package/src/ui/bevel-controls.js +2 -8
- package/src/ui/page-scheme.js +7 -0
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/theme-config.js +150 -0
- package/src/ui/theme.js +6 -138
- package/src/ui/toast-lazy.js +11 -0
- package/src/vendor/LICENSES-optional.md +253 -0
- package/src/vendor/highlight.vendor.js +3 -0
- package/src/vendor/hljs-bash.vendor.js +2 -0
- package/src/vendor/hljs-css.vendor.js +2 -0
- package/src/vendor/hljs-javascript.vendor.js +2 -0
- package/src/vendor/hljs-json.vendor.js +2 -0
- package/src/vendor/hljs-python.vendor.js +2 -0
- package/src/vendor/hljs-xml.vendor.js +2 -0
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
- package/src/vendor/hyper-morph-identity.vendor.js +2 -0
- package/src/vendor/hyper-morph-json.vendor.js +3 -0
- package/src/vendor/hyper-morph.vendor.js +6 -6
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +169 -84
- package/src/vendor/marked.vendor.js +68 -0
- package/src/vendor/overtype.vendor.js +994 -0
- package/src/vendor/richclay.vendor.js +17 -17
- package/src/vendor/sap-core.vendor.js +5 -5
- package/src/vendor/sap-ownership.vendor.js +11 -7
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// The format gate: the one place a new-format command asks whether the document
|
|
2
|
+
// may use that format yet.
|
|
3
|
+
//
|
|
4
|
+
// R understands floor 1 and sends that capability on every page write, but has
|
|
5
|
+
// no floor-1 command of its own. F's first new-format command calls this gate
|
|
6
|
+
// before it touches content:
|
|
7
|
+
//
|
|
8
|
+
// const gate = createFormatGate({ host, installed, revision, lane });
|
|
9
|
+
// const result = await gate.run({ apply: (selection) => command(selection) });
|
|
10
|
+
//
|
|
11
|
+
// Activation adds the canonical marker to an otherwise equivalent capture and
|
|
12
|
+
// nothing else. It is idempotent, it is refused while an admitted page editor
|
|
13
|
+
// could not read floor 1, and a refusal leaves ordinary writing and every
|
|
14
|
+
// unsaved store exactly as they were. The command that asked only applies if the
|
|
15
|
+
// same editor installation and document revision still exist afterwards;
|
|
16
|
+
// otherwise the answer is retryable and the content is untouched.
|
|
17
|
+
//
|
|
18
|
+
// The host seam is deliberately one object, because the floor is read, checked
|
|
19
|
+
// and persisted under one per-document lock:
|
|
20
|
+
//
|
|
21
|
+
// read() -> Promise<{ floor, revision }> the canonical floor as it stands
|
|
22
|
+
// capabilities()-> Promise<number[]> admitted page editors' declared capabilities
|
|
23
|
+
// activate({ floor, capability, revision })
|
|
24
|
+
// -> Promise<{ accepted: true, revision, receipt }
|
|
25
|
+
// | { accepted: false, code }>
|
|
26
|
+
//
|
|
27
|
+
// `activate` holds the document's admission/write lock while it checks the
|
|
28
|
+
// admitted editors again and persists the marker, so a legacy registration that
|
|
29
|
+
// lands between `capabilities()` and `activate()` is refused rather than missed.
|
|
30
|
+
|
|
31
|
+
import { PAGE_FORMAT, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE, formatCapability, notePageFormatFloor, pageFormatDecision, pageFormatFloor } from "./format-level.js";
|
|
32
|
+
|
|
33
|
+
const UPGRADE_MESSAGE = "This document uses a newer page format; update the editor in the other tabs to keep writing";
|
|
34
|
+
|
|
35
|
+
const upgrade = (required) => ({ status: "upgrade-required", code: PAGE_FORMAT_UPGRADE, required, minimum: required, message: UPGRADE_MESSAGE });
|
|
36
|
+
|
|
37
|
+
export function createFormatGate({
|
|
38
|
+
host,
|
|
39
|
+
capability = PAGE_FORMAT,
|
|
40
|
+
supported = () => true,
|
|
41
|
+
installed = () => null,
|
|
42
|
+
revision = () => null,
|
|
43
|
+
lane = null,
|
|
44
|
+
} = {}) {
|
|
45
|
+
if (!host || typeof host.activate !== "function") throw new TypeError("format-gate: a host adapter is required");
|
|
46
|
+
let floor = 0;
|
|
47
|
+
let pending = null;
|
|
48
|
+
let lanePending = false;
|
|
49
|
+
let disposed = false;
|
|
50
|
+
|
|
51
|
+
// The floor this gate knows the document at. A save or a room answer can tell
|
|
52
|
+
// it, so a gate opened on an already-floor-1 document never asks to activate
|
|
53
|
+
// again.
|
|
54
|
+
function note(level) {
|
|
55
|
+
floor = Math.max(floor, formatCapability(level));
|
|
56
|
+
notePageFormatFloor(floor);
|
|
57
|
+
return floor;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function read() {
|
|
61
|
+
try {
|
|
62
|
+
const answer = await host.read();
|
|
63
|
+
note(answer?.floor ?? pageFormatFloor(answer?.html));
|
|
64
|
+
} catch {}
|
|
65
|
+
return floor;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function admittance() {
|
|
69
|
+
try {
|
|
70
|
+
const admitted = await host.capabilities();
|
|
71
|
+
return Array.isArray(admitted) ? admitted : [];
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Whether a floor rise may happen at all: the host must have announced the
|
|
78
|
+
// format contract, and this runtime must declare the capability it writes with.
|
|
79
|
+
async function supportedHere() {
|
|
80
|
+
try {
|
|
81
|
+
return (await supported()) !== false;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function activateNow() {
|
|
88
|
+
if (disposed) return { status: "disposed", retry: false };
|
|
89
|
+
if (!(await supportedHere())) return { status: "unsupported-host", code: "page-format-unsupported", extension: PAGE_FORMAT_EXTENSION };
|
|
90
|
+
await read();
|
|
91
|
+
if (floor >= PAGE_FORMAT) return { status: "active", floor };
|
|
92
|
+
const admitted = await admittance();
|
|
93
|
+
const early = pageFormatDecision({ floor, required: PAGE_FORMAT, capability, admitted });
|
|
94
|
+
if (!early.ok) return upgrade(early.minimum);
|
|
95
|
+
const stamp = { revision: revision(), installed: installed() };
|
|
96
|
+
let answer;
|
|
97
|
+
try {
|
|
98
|
+
answer = await host.activate({ floor: PAGE_FORMAT, capability, revision: stamp.revision, installed: stamp.installed });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return { status: "failed", code: "page-format-activation-failed", error, retry: true };
|
|
101
|
+
}
|
|
102
|
+
if (!answer || answer.accepted !== true) {
|
|
103
|
+
if (answer?.code === PAGE_FORMAT_UPGRADE) return upgrade(PAGE_FORMAT);
|
|
104
|
+
return { status: "failed", code: answer?.code ?? "page-format-activation-failed", retry: true };
|
|
105
|
+
}
|
|
106
|
+
if (disposed) {
|
|
107
|
+
// The save was accepted, so the document is at floor 1 either way; this
|
|
108
|
+
// tab is gone and must not act on it.
|
|
109
|
+
note(PAGE_FORMAT);
|
|
110
|
+
return { status: "disposed", retry: false };
|
|
111
|
+
}
|
|
112
|
+
note(PAGE_FORMAT);
|
|
113
|
+
lanePending = true;
|
|
114
|
+
await joinLane(answer.receipt ?? null);
|
|
115
|
+
return { status: "active", floor, receipt: answer.receipt ?? null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Joining the new lane scope happens after the receipt is accepted, and only
|
|
119
|
+
// then: an interrupted switch leaves this tab on floor 0's scope with every
|
|
120
|
+
// store intact and the switch still pending, never a lane it cannot cover.
|
|
121
|
+
async function joinLane(receipt) {
|
|
122
|
+
if (!lane || !lanePending) return false;
|
|
123
|
+
try {
|
|
124
|
+
const result = await lane.switchTo({ floor: PAGE_FORMAT, receipt });
|
|
125
|
+
if (result && result.status === "switched") {
|
|
126
|
+
lanePending = false;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
} catch {}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function activate() {
|
|
134
|
+
if (disposed) return Promise.resolve({ status: "disposed", retry: false });
|
|
135
|
+
if (floor >= PAGE_FORMAT) return joinLane(null).then(() => ({ status: "active", floor }));
|
|
136
|
+
if (!pending) pending = activateNow().finally(() => { pending = null; });
|
|
137
|
+
return pending;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
get floor() {
|
|
142
|
+
return floor;
|
|
143
|
+
},
|
|
144
|
+
get disposed() {
|
|
145
|
+
return disposed;
|
|
146
|
+
},
|
|
147
|
+
note,
|
|
148
|
+
read,
|
|
149
|
+
// Activation, once: a second caller while one is in flight waits on the
|
|
150
|
+
// same attempt, and a retry after a failure starts a new one.
|
|
151
|
+
activate,
|
|
152
|
+
// A new-format command: capture what it applies to, ask the gate, and apply
|
|
153
|
+
// only when activation succeeded and nothing moved underneath it.
|
|
154
|
+
async run(command) {
|
|
155
|
+
const stamp = { revision: revision(), installed: installed() };
|
|
156
|
+
const result = await activate();
|
|
157
|
+
if (result.status !== "active") return result;
|
|
158
|
+
if (disposed) return { status: "disposed", retry: false };
|
|
159
|
+
if (revision() !== stamp.revision || installed() !== stamp.installed) {
|
|
160
|
+
return { status: "retry", code: "format-gate-stale", retry: true };
|
|
161
|
+
}
|
|
162
|
+
return { status: "applied", floor, result: await command?.apply?.(stamp) };
|
|
163
|
+
},
|
|
164
|
+
// Retry what is still pending: a lane switch whose receipt is in hand, or an
|
|
165
|
+
// activation that failed on the wire. Unsaved work is never part of this.
|
|
166
|
+
async retry() {
|
|
167
|
+
if (disposed) return { status: "disposed", retry: false };
|
|
168
|
+
if (await joinLane(null)) return { status: "active", floor };
|
|
169
|
+
return activate();
|
|
170
|
+
},
|
|
171
|
+
// Disposal while awaiting the save: the answer is not applied, the lane is
|
|
172
|
+
// not switched, and the deferred command does not run.
|
|
173
|
+
dispose() {
|
|
174
|
+
disposed = true;
|
|
175
|
+
lanePending = false;
|
|
176
|
+
return { status: "disposed" };
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// The document format floor: how much of the page format a runtime must know
|
|
2
|
+
// before it may write.
|
|
3
|
+
//
|
|
4
|
+
// The canonical marker is document metadata, outside the unchanged `{v,g,y}`
|
|
5
|
+
// page value:
|
|
6
|
+
// <meta name="clay-page-format" content="1">
|
|
7
|
+
// Absence means floor 0. A document's floor only ever rises, and every path that
|
|
8
|
+
// writes a page declares the capability it writes with in
|
|
9
|
+
// `X-Clay-Page-Format`. The value is protocol capability, not authorization: the
|
|
10
|
+
// host still applies every permission check.
|
|
11
|
+
//
|
|
12
|
+
// `requiredPageFormat` answers the other direction: what floor does THIS content
|
|
13
|
+
// need? Its registry is frozen at R (floor 1), so it does not shrink back as a
|
|
14
|
+
// later release adds native schema support.
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
PAGE_FORMAT, PAGE_FORMAT_HEADER, PAGE_FORMAT_META, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE,
|
|
18
|
+
formatCapability, pageFormatFloor, withPageFormatFloor, knownPageFormatFloor, notePageFormatFloor,
|
|
19
|
+
} from "../core/page-format.js";
|
|
20
|
+
import { PAGE_FORMAT, PAGE_FORMAT_UPGRADE, formatCapability } from "../core/page-format.js";
|
|
21
|
+
|
|
22
|
+
const level = (value) => (Number.isSafeInteger(value) && value > 0 ? value : 0);
|
|
23
|
+
|
|
24
|
+
// The floor-0 registry, frozen: every block type, mark code and block field R
|
|
25
|
+
// understood when the floor was introduced. Built on first ask, so a client that
|
|
26
|
+
// never checks content for its floor never carries the list.
|
|
27
|
+
const REGISTRY = {
|
|
28
|
+
types: [
|
|
29
|
+
"paragraph", "heading", "list_item", "callout", "code_block", "toggle",
|
|
30
|
+
"divider", "image", "file", "table", "chart", "board", "tool", "preserved",
|
|
31
|
+
],
|
|
32
|
+
marks: ["a", "b", "c", "i", "s"],
|
|
33
|
+
fields: [
|
|
34
|
+
"id", "t", "parent", "c", "m", "level", "kind", "indent", "checked", "owner",
|
|
35
|
+
"due", "lang", "open", "src", "alt", "caption", "width", "align", "name",
|
|
36
|
+
"size", "mime", "cols", "rows", "sort", "source", "series", "title",
|
|
37
|
+
"highlight", "columns", "cards", "artifact", "rev", "state", "html", "pos",
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
let frozen = null;
|
|
41
|
+
|
|
42
|
+
export function floorZero() {
|
|
43
|
+
if (!frozen) {
|
|
44
|
+
frozen = Object.freeze({
|
|
45
|
+
types: Object.freeze([...REGISTRY.types]),
|
|
46
|
+
marks: Object.freeze([...REGISTRY.marks]),
|
|
47
|
+
fields: Object.freeze([...REGISTRY.fields]),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return frozen;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CODE = /^[A-Za-z]$/;
|
|
54
|
+
|
|
55
|
+
// A mark key holds one code per mark, each followed by its attrs as a JSON array
|
|
56
|
+
// when it carries them. Anything this registry has no code for is a newer
|
|
57
|
+
// runtime's: an unfamiliar compact token, or `x` with a named extension.
|
|
58
|
+
function newerMark(key) {
|
|
59
|
+
if (typeof key !== "string") throw new TypeError("page: a mark key is not a string");
|
|
60
|
+
if (!key || key[0] === "@") return false;
|
|
61
|
+
for (let i = 0; i < key.length;) {
|
|
62
|
+
const code = key[i];
|
|
63
|
+
if (!CODE.test(code)) throw new SyntaxError(`page: malformed mark key ${key}`);
|
|
64
|
+
if (!floorZero().marks.includes(code)) return true;
|
|
65
|
+
i += 1;
|
|
66
|
+
if (key[i] !== "[") continue;
|
|
67
|
+
let depth = 0, quoted = false;
|
|
68
|
+
const start = i;
|
|
69
|
+
for (; i < key.length; i++) {
|
|
70
|
+
const ch = key[i];
|
|
71
|
+
if (quoted) {
|
|
72
|
+
if (ch === "\\") i++;
|
|
73
|
+
else if (ch === '"') quoted = false;
|
|
74
|
+
} else if (ch === '"') quoted = true;
|
|
75
|
+
else if (ch === "[") depth++;
|
|
76
|
+
else if (ch === "]" && --depth === 0) { i++; break; }
|
|
77
|
+
}
|
|
78
|
+
if (i >= key.length && key[key.length - 1] !== "]") throw new SyntaxError(`page: unterminated attrs in mark key ${key.slice(start)}`);
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const newerRuns = (m) => {
|
|
84
|
+
if (!Array.isArray(m)) return false;
|
|
85
|
+
for (const run of m) {
|
|
86
|
+
if (Array.isArray(run)) {
|
|
87
|
+
if (run.length === 2 && Number.isInteger(run[0]) && newerMark(run[1])) return true;
|
|
88
|
+
} else if (newerMark(run)) return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The floor a prospective semantic model needs: 1 when it holds a named
|
|
95
|
+
* extension mark, an unfamiliar compact token, an unfamiliar block or a block
|
|
96
|
+
* field the floor-0 registry does not know, 0 otherwise.
|
|
97
|
+
*
|
|
98
|
+
* @param {Array<Object>} model - items as projection.js projects them
|
|
99
|
+
* @returns {number}
|
|
100
|
+
*/
|
|
101
|
+
export function requiredPageFormat(model) {
|
|
102
|
+
if (!Array.isArray(model)) throw new TypeError("page: requiredPageFormat expects a model");
|
|
103
|
+
for (const item of model) {
|
|
104
|
+
if (!item || typeof item !== "object") continue;
|
|
105
|
+
if (!floorZero().types.includes(item.t)) return PAGE_FORMAT;
|
|
106
|
+
for (const key of Object.keys(item)) {
|
|
107
|
+
if (!floorZero().fields.includes(key)) return PAGE_FORMAT;
|
|
108
|
+
}
|
|
109
|
+
if (newerRuns(item.m)) return PAGE_FORMAT;
|
|
110
|
+
if (!Array.isArray(item.rows)) continue;
|
|
111
|
+
for (const row of item.rows) {
|
|
112
|
+
for (const cell of Object.values(row?.cells ?? {})) {
|
|
113
|
+
if (cell && typeof cell === "object" && newerRuns(cell.m)) return PAGE_FORMAT;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* One floor decision for every writer: the client's activation gate and the
|
|
122
|
+
* room's admission helper ask the same question and get the same answer.
|
|
123
|
+
*
|
|
124
|
+
* `floor` is the document's active floor, `required` what the prospective model
|
|
125
|
+
* needs, `capability` what this writer declares, and `admitted` the capabilities
|
|
126
|
+
* of the page editors already in the room. A write is refused before any
|
|
127
|
+
* mutation when this writer is below what the content needs, and a floor rise is
|
|
128
|
+
* refused while an admitted editor could not read the result.
|
|
129
|
+
*
|
|
130
|
+
* @param {Object} input
|
|
131
|
+
* @param {number} [input.floor]
|
|
132
|
+
* @param {number} [input.required]
|
|
133
|
+
* @param {number} [input.capability]
|
|
134
|
+
* @param {number[]} [input.admitted]
|
|
135
|
+
* @returns {{ok: boolean, activate: boolean, floor: number, required: number}}
|
|
136
|
+
*/
|
|
137
|
+
export function pageFormatDecision({ floor = 0, required = 0, capability = 0, admitted = [] } = {}) {
|
|
138
|
+
const active = level(floor);
|
|
139
|
+
const needs = Math.max(level(required), active);
|
|
140
|
+
const refuse = (minimum) => ({ ok: false, activate: false, floor: active, required: needs, minimum });
|
|
141
|
+
if (formatCapability(capability) < needs) return refuse(needs);
|
|
142
|
+
const activate = needs > active;
|
|
143
|
+
if (activate && admitted.some((value) => formatCapability(value) < needs)) return refuse(needs);
|
|
144
|
+
return { ok: true, activate, floor: needs, required: needs };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// What a refused writer is told: which floor the document needs and that its own
|
|
148
|
+
// unsaved work is still its own. Nothing here is a reload.
|
|
149
|
+
export function pageFormatRefusal(required, message = null) {
|
|
150
|
+
const minimum = level(required) || PAGE_FORMAT;
|
|
151
|
+
const error = new Error(message ?? `This document uses a newer page format (floor ${minimum}); update the editor to keep writing`);
|
|
152
|
+
error.name = "PageFormatUpgradeRequired";
|
|
153
|
+
error.code = PAGE_FORMAT_UPGRADE;
|
|
154
|
+
error.statusCode = 409;
|
|
155
|
+
error.minimum = minimum;
|
|
156
|
+
return error;
|
|
157
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The decoration slot code colours go into: code blocks highlight themselves
|
|
2
|
+
// from a separate part, so the editor carries this empty plugin from boot and
|
|
3
|
+
// the highlighter fills it once it loads.
|
|
4
|
+
import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
|
|
5
|
+
|
|
6
|
+
const { Plugin, PluginKey } = state;
|
|
7
|
+
const { DecorationSet } = pmView;
|
|
8
|
+
|
|
9
|
+
export const highlightKey = new PluginKey("page-highlight");
|
|
10
|
+
|
|
11
|
+
export function highlightSlot() {
|
|
12
|
+
return new Plugin({
|
|
13
|
+
key: highlightKey,
|
|
14
|
+
state: {
|
|
15
|
+
init: () => DecorationSet.empty,
|
|
16
|
+
apply(tr, set) {
|
|
17
|
+
const decos = tr.getMeta(highlightKey);
|
|
18
|
+
return decos ? DecorationSet.create(tr.doc, decos) : set.map(tr.mapping, tr.doc);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
props: { decorations: (st) => highlightKey.getState(st) },
|
|
22
|
+
});
|
|
23
|
+
}
|
package/src/page/highlight.js
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
|
|
6
6
|
import { schema } from "./schema.js";
|
|
7
7
|
import { asStep } from "./blocksel.js";
|
|
8
|
+
import { highlightKey, highlightSlot } from "./highlight-slot.js";
|
|
8
9
|
|
|
9
|
-
const { Plugin
|
|
10
|
-
const { Decoration
|
|
10
|
+
const { Plugin } = state;
|
|
11
|
+
const { Decoration } = pmView;
|
|
12
|
+
|
|
13
|
+
export { highlightKey };
|
|
11
14
|
|
|
12
15
|
export const LANGS = [
|
|
13
16
|
["", "Plain text"],
|
|
@@ -106,9 +109,6 @@ const styleOf = (t) => {
|
|
|
106
109
|
return s.join("; ");
|
|
107
110
|
};
|
|
108
111
|
|
|
109
|
-
export const highlightKey = new PluginKey("page-highlight");
|
|
110
|
-
|
|
111
|
-
|
|
112
112
|
const CACHE_SIZE = 200;
|
|
113
113
|
|
|
114
114
|
// Each pass rebuilds every code block's colours from a cache of token runs keyed
|
|
@@ -206,18 +206,32 @@ class Highlighter {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
// The highlighter is no longer a plugin view, so nothing calls its update: it
|
|
210
|
+
// watches the editor's own dispatches and stops watching when it is destroyed.
|
|
211
|
+
export function startHighlighter(view, { tokenize: run = tokenize, delay = 300 } = {}) {
|
|
212
|
+
const highlighter = new Highlighter(view, run, delay);
|
|
213
|
+
const dispatch = view.dispatch.bind(view);
|
|
214
|
+
let doc = view.state.doc;
|
|
215
|
+
const wrapped = (tr) => {
|
|
216
|
+
dispatch(tr);
|
|
217
|
+
if (view.state.doc === doc) return;
|
|
218
|
+
const prev = { doc };
|
|
219
|
+
doc = view.state.doc;
|
|
220
|
+
highlighter.update(view, prev);
|
|
221
|
+
};
|
|
222
|
+
view.dispatch = wrapped;
|
|
223
|
+
const stop = highlighter.destroy.bind(highlighter);
|
|
224
|
+
highlighter.destroy = () => {
|
|
225
|
+
if (view.dispatch === wrapped) view.dispatch = dispatch;
|
|
226
|
+
stop();
|
|
227
|
+
};
|
|
228
|
+
return highlighter;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function highlightPlugin(options) {
|
|
210
232
|
return new Plugin({
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
init: () => DecorationSet.empty,
|
|
214
|
-
apply(tr, set) {
|
|
215
|
-
const decos = tr.getMeta(highlightKey);
|
|
216
|
-
return decos ? DecorationSet.create(tr.doc, decos) : set.map(tr.mapping, tr.doc);
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
props: { decorations: (st) => highlightKey.getState(st) },
|
|
220
|
-
view: (view) => new Highlighter(view, run, delay),
|
|
233
|
+
...highlightSlot().spec,
|
|
234
|
+
view: (view) => startHighlighter(view, options),
|
|
221
235
|
});
|
|
222
236
|
}
|
|
223
237
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Typing joins one undo item the way it does in other editors: Mod-z removes the last
|
|
2
|
+
// word typed, not the last letter. Only the editor's own input path marks a
|
|
3
|
+
// transaction; a command or a programmatic dispatch stays one undo item by itself.
|
|
4
|
+
import { state } from "../vendor/prosemirror.vendor.js";
|
|
5
|
+
import { STEP } from "./blocks.js";
|
|
6
|
+
|
|
7
|
+
export const HISTORY_INTENT = "page-history-intent";
|
|
8
|
+
export const GROUP_MS = 500;
|
|
9
|
+
|
|
10
|
+
const blockIdAt = (doc, pos) => {
|
|
11
|
+
const $pos = doc.resolve(pos);
|
|
12
|
+
return $pos.depth ? $pos.node(1).attrs.id : null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const markKey = (st) => (st.storedMarks ?? st.selection.$from.marks()).map((m) => `${m.type.name}:${JSON.stringify(m.attrs)}`).sort().join(",");
|
|
16
|
+
|
|
17
|
+
export function typingIntent(st, from, to, text) {
|
|
18
|
+
return { kind: "insert", blockId: blockIdAt(st.doc, from), from, end: from + text.length, replaced: from !== to, text, marks: markKey(st), at: Date.now() };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function continuesGroup(prev, next) {
|
|
22
|
+
if (!prev || !next || prev.kind !== next.kind) return false;
|
|
23
|
+
if (next.kind === "compose") return prev.id === next.id;
|
|
24
|
+
if (prev.blockId !== next.blockId || next.at - prev.at > GROUP_MS) return false;
|
|
25
|
+
if (next.kind === "insert") return !next.replaced && next.from === prev.end && next.marks === prev.marks && !(/\s$/.test(prev.text) && /^\S/.test(next.text));
|
|
26
|
+
return next.direction === prev.direction && (next.direction < 0 ? next.to === prev.from : next.from === prev.from);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DELETES = { deleteContentBackward: -1, deleteContentForward: 1 };
|
|
30
|
+
const pendingDeletes = new WeakMap();
|
|
31
|
+
const PENDING_MS = 1000;
|
|
32
|
+
export const DERIVED = "page-derived";
|
|
33
|
+
|
|
34
|
+
// A native deletion arrives as a DOM change with no handler, sometimes after a delayed flush
|
|
35
|
+
// (Android waits 20 ms), so the beforeinput that announced it is held with the document it saw
|
|
36
|
+
// and claimed by the next document change, whatever that change turns out to be.
|
|
37
|
+
export function takePendingDelete(view) {
|
|
38
|
+
const mark = pendingDeletes.get(view) ?? null;
|
|
39
|
+
pendingDeletes.delete(view);
|
|
40
|
+
return mark;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function deletionIntent(tr, mark) {
|
|
44
|
+
if (!mark || tr.before !== mark.doc || Date.now() - mark.at > PENDING_MS || tr.getMeta(STEP) || tr.steps.length !== 1) return null;
|
|
45
|
+
const step = tr.steps[0];
|
|
46
|
+
if (step.slice?.size !== 0 || step.from === step.to) return null;
|
|
47
|
+
const $from = tr.before.resolve(step.from), $to = tr.before.resolve(step.to);
|
|
48
|
+
if (!$from.parent.isTextblock || !$from.sameParent($to)) return null;
|
|
49
|
+
return { kind: "delete", blockId: $from.depth ? $from.node(1).attrs.id : null, direction: mark.direction, from: step.from, to: step.to, at: mark.at };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function intentOf(transactions, view = null) {
|
|
53
|
+
const pending = view ? takePendingDelete(view) : null;
|
|
54
|
+
const primary = transactions.filter((t) => !t.getMeta(DERIVED));
|
|
55
|
+
if (primary.length !== 1) return null;
|
|
56
|
+
const tr = primary[0];
|
|
57
|
+
const composition = tr.getMeta("composition");
|
|
58
|
+
if (composition != null) return { kind: "compose", id: composition, at: Date.now() };
|
|
59
|
+
return tr.getMeta(HISTORY_INTENT) ?? deletionIntent(tr, pending);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function historyIntentPlugin() {
|
|
63
|
+
return new state.Plugin({
|
|
64
|
+
props: {
|
|
65
|
+
handleTextInput(view, from, to, text, deflt) {
|
|
66
|
+
view.dispatch(deflt().setMeta(HISTORY_INTENT, typingIntent(view.state, from, to, text)));
|
|
67
|
+
return true;
|
|
68
|
+
},
|
|
69
|
+
handleDOMEvents: {
|
|
70
|
+
beforeinput(view, event) {
|
|
71
|
+
const direction = DELETES[event.inputType];
|
|
72
|
+
if (direction) pendingDeletes.set(view, { direction, at: Date.now(), doc: view.state.doc });
|
|
73
|
+
return false;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
package/src/page/inline.js
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
// "@" and the node's JSON. No mark key starts with "@", so a U+FFFC typed as
|
|
13
13
|
// text stays text. Private-use characters typed as text need nothing here:
|
|
14
14
|
// encodeForMerge (replay.js) gives each one a code of its own.
|
|
15
|
+
// A mark this schema has no type for keeps its token or named descriptor
|
|
16
|
+
// instead of throwing (format-codec.js): the unit's key is written back exactly
|
|
17
|
+
// as it was read.
|
|
15
18
|
import { model } from "../vendor/prosemirror.vendor.js";
|
|
19
|
+
import { descriptors, entriesOf, isOpaque, keyOf, opaqueMark } from "./format-codec.js";
|
|
16
20
|
|
|
17
21
|
export const ATOM = "";
|
|
18
22
|
export const CODES = { strong: "b", em: "i", strike: "s", code: "c", link: "a" };
|
|
@@ -21,6 +25,10 @@ const NAMES = Object.fromEntries(Object.entries(CODES).map(([name, code]) => [co
|
|
|
21
25
|
export function markKey(marks) {
|
|
22
26
|
let key = "";
|
|
23
27
|
for (const mark of marks) {
|
|
28
|
+
if (isOpaque(mark)) {
|
|
29
|
+
key += keyOf(mark.attrs.entries);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
24
32
|
const code = CODES[mark.type.name];
|
|
25
33
|
if (!code) throw new TypeError(`page: the mark ${mark.type.name} has no key code`);
|
|
26
34
|
key += code;
|
|
@@ -30,21 +38,6 @@ export function markKey(marks) {
|
|
|
30
38
|
return key;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
// The end of the JSON array that starts at s[i].
|
|
34
|
-
function arrayEnd(s, i) {
|
|
35
|
-
let depth = 0, str = false;
|
|
36
|
-
for (; i < s.length; i++) {
|
|
37
|
-
const ch = s[i];
|
|
38
|
-
if (str) {
|
|
39
|
-
if (ch === "\\") i++;
|
|
40
|
-
else if (ch === '"') str = false;
|
|
41
|
-
} else if (ch === '"') str = true;
|
|
42
|
-
else if (ch === "[") depth++;
|
|
43
|
-
else if (ch === "]" && --depth === 0) return i + 1;
|
|
44
|
-
}
|
|
45
|
-
throw new SyntaxError(`page: unterminated attrs in mark key ${s}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
41
|
const parsed = new WeakMap();
|
|
49
42
|
|
|
50
43
|
export function marksOf(schema, key) {
|
|
@@ -54,19 +47,19 @@ export function marksOf(schema, key) {
|
|
|
54
47
|
let marks = cache.get(key);
|
|
55
48
|
if (marks) return marks;
|
|
56
49
|
marks = [];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const end = arrayEnd(key, i);
|
|
64
|
-
const values = JSON.parse(key.slice(i, end));
|
|
65
|
-
attrs = Object.fromEntries(Object.keys(type.attrs).map((name, j) => [name, values[j]]));
|
|
66
|
-
i = end;
|
|
50
|
+
const opaque = [];
|
|
51
|
+
for (const desc of descriptors(key)) {
|
|
52
|
+
const name = desc.code === undefined ? null : NAMES[desc.code];
|
|
53
|
+
if (!name) {
|
|
54
|
+
opaque.push(desc);
|
|
55
|
+
continue;
|
|
67
56
|
}
|
|
57
|
+
const type = schema.marks[name];
|
|
58
|
+
if (!type) throw new TypeError(`page: unknown mark code ${desc.code} in ${key}`);
|
|
59
|
+
const attrs = desc.attrs ? Object.fromEntries(Object.keys(type.attrs).map((field, j) => [field, desc.attrs[j]])) : null;
|
|
68
60
|
marks = type.create(attrs).addToSet(marks);
|
|
69
61
|
}
|
|
62
|
+
if (opaque.length) marks = opaqueMark(entriesOf(opaque)).addToSet(marks);
|
|
70
63
|
cache.set(key, marks);
|
|
71
64
|
return marks;
|
|
72
65
|
}
|