@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
package/src/page/editor.js
CHANGED
|
@@ -6,30 +6,30 @@
|
|
|
6
6
|
// `page` cascade layer, so a theme is one plain stylesheet that sets tokens on
|
|
7
7
|
// .page-shell (and adds scoped rules where it needs to); unlayered theme rules
|
|
8
8
|
// always win. Contract: plans/.../default-page/v1/look/THEME-CONTRACT.md
|
|
9
|
-
import { state, view as pmView, keymap, commands, history, gapcursor, inputrules } from "../vendor/prosemirror.vendor.js";
|
|
9
|
+
import { state, view as pmView, model, keymap, commands, history, gapcursor, inputrules } from "../vendor/prosemirror.vendor.js";
|
|
10
10
|
import { schema } from "./schema.js";
|
|
11
11
|
import { identityPlugin, docFromBlocks, blockMap, keyBetween, newId } from "./model.js";
|
|
12
12
|
import { blockKeys, structurePlugin, collapsePlugin, pastePlugin, BlockSelection, STEP } from "./blocks.js";
|
|
13
|
-
import { chromePlugin } from "./chrome.js";
|
|
13
|
+
import { chromePlugin, slashRangePlugin } from "./chrome.js";
|
|
14
14
|
import { announcementPlugin } from "./announce.js";
|
|
15
15
|
import { linkPlugin } from "./link.js";
|
|
16
|
-
import { blockSelectionPlugin } from "./blocksel.js";
|
|
17
|
-
import { markdownPlugin, markdownKeys, backspaceToParagraph } from "./markdown.js";
|
|
16
|
+
import { blockSelectionPlugin, asStep } from "./blocksel.js";
|
|
17
|
+
import { markdownPlugin, markdownKeys, backspaceToParagraph, composing } from "./markdown.js";
|
|
18
18
|
import { placeholderPlugin } from "./placeholder.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
19
|
+
import { titlePlugin } from "./document-title-runtime.js";
|
|
20
|
+
import { mediaPlugin } from "./media.js";
|
|
21
|
+
import { highlightSlot } from "./highlight-slot.js";
|
|
22
|
+
import { tableKeys, tablePlugin } from "./table.js";
|
|
22
23
|
import { tableSelectPlugin } from "./table-select.js";
|
|
23
24
|
import { chartPlugin, ChartView } from "./chart.js";
|
|
24
|
-
import {
|
|
25
|
-
import { boardPlugin, cardPanel } from "./board-panel.js";
|
|
26
|
-
import { ToolView } from "./tool-view.js";
|
|
27
|
-
import { createToolHost } from "./tools.js";
|
|
25
|
+
import { boardPlugin } from "./board-views.js";
|
|
28
26
|
import { checklistPlugin, checklistKeys } from "./checklist.js";
|
|
29
27
|
import { csvImportPlugin } from "./csv-import.js";
|
|
30
|
-
import { TABLE_CSS } from "./table-style.js";
|
|
31
|
-
import { BOARD_CSS } from "./board-style.js";
|
|
32
28
|
import { styleWithLegacyId } from "../lib/style.js";
|
|
29
|
+
import { historyIntentPlugin } from "./history-intent.js";
|
|
30
|
+
import { createFeatureLoader } from "./feature-loader.js";
|
|
31
|
+
import { COMMAND_FEATURES, FEATURES, featureForType } from "./features/registry.js";
|
|
32
|
+
import { toolViews } from "./tool-views.js";
|
|
33
33
|
|
|
34
34
|
const STYLE_ID = "page-editor-style";
|
|
35
35
|
const INDENTS = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
@@ -126,7 +126,10 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
|
|
|
126
126
|
.page-tool { display: inline-flex; align-items: center; justify-content: center; min-width: 28px; height: 28px; padding: 0 6px; border: 0; border-radius: 5px; background: none; color: inherit; font: 500 13px/1 var(--page-font); cursor: pointer; }
|
|
127
127
|
.page-tool:hover, .page-tool[aria-pressed="true"] { background: var(--page-pop-hover); }
|
|
128
128
|
.page-tool-turn { gap: 4px; padding: 0 8px; }
|
|
129
|
+
/* The phone's fourth control: everything the narrow bar folds away. */
|
|
130
|
+
.page-tool-more { display: none; }
|
|
129
131
|
.page-tool-sep { width: 1px; height: 16px; margin: 0 3px; background: currentColor; opacity: 0.5; }
|
|
132
|
+
.page-safe-probe { position: fixed; left: 0; bottom: 0; width: 0; height: 0; padding-bottom: env(safe-area-inset-bottom); visibility: hidden; pointer-events: none; }
|
|
130
133
|
.page-plus { position: absolute; z-index: 2; top: calc(var(--page-plus-top, 0px) - var(--page-plus-size) / 2); left: calc(var(--page-plus-left, 0px) - var(--page-plus-size) - 12px); width: var(--page-plus-size); height: var(--page-plus-size); display: grid; place-items: center; padding: 0; cursor: pointer; }
|
|
131
134
|
.page-plus[aria-expanded="true"] { color: var(--page-chrome-ink-moved); background: var(--page-chrome-bg-moved); }
|
|
132
135
|
.page-grip { position: absolute; z-index: 2; top: calc(var(--page-plus-top, 0px) - var(--page-grip-size, var(--page-plus-size)) / 2); left: calc(var(--page-plus-left, 0px) - var(--page-plus-size) - 12px - var(--page-grip-size, var(--page-plus-size)) - 2px); width: var(--page-grip-size, var(--page-plus-size)); height: var(--page-grip-size, var(--page-plus-size)); display: grid; place-items: center; padding: 0; border: 0; border-radius: var(--page-chrome-radius); background: none; color: var(--page-chrome-ink-moved); opacity: 0; cursor: grab; touch-action: none; transition: opacity var(--page-fade); }
|
|
@@ -157,40 +160,8 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
|
|
|
157
160
|
.page-link-btn { display: inline-grid; place-items: center; min-width: 30px; height: 30px; padding: 0 6px; border: 0; border-radius: 6px; background: none; color: inherit; font: inherit; cursor: pointer; }
|
|
158
161
|
.page-link-btn:hover { background: var(--page-pop-hover); }
|
|
159
162
|
.page-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
160
|
-
.page-editor .page-image { margin-inline: 0; }
|
|
161
|
-
.page-image-frame { position: relative; margin: 0 auto; max-width: 100%; }
|
|
162
|
-
.page-image-frame img { display: block; width: 100%; height: auto; border-radius: var(--page-image-radius, 6px); }
|
|
163
|
-
.page-image[data-broken] .page-image-frame { min-height: 80px; background: var(--page-code-bg); border-radius: var(--page-image-radius, 6px); }
|
|
164
|
-
.page-image-handle { position: absolute; top: 50%; inset-inline-end: 6px; width: 6px; height: 36px; margin-top: -18px; border-radius: 3px; background: var(--page-chrome-ink-moved); opacity: 0; cursor: ew-resize; touch-action: none; transition: opacity var(--page-fade); }
|
|
165
|
-
.page-image:hover .page-image-handle, .page-image.is-selected .page-image-handle { opacity: 1; }
|
|
166
|
-
.page-editor .page-image.page-block-selected { background: none; box-shadow: none; }
|
|
167
|
-
.page-editor .page-image.page-block-selected .page-image-frame { box-shadow: 0 0 0 6px var(--page-block-selected, rgba(88, 120, 170, 0.12)); border-radius: var(--page-image-radius, 6px); }
|
|
168
|
-
.page-image[data-align="left"] .page-image-frame { margin-left: 0; margin-right: auto; }
|
|
169
|
-
.page-image[data-align="right"] .page-image-frame { margin-left: auto; margin-right: 0; }
|
|
170
|
-
.page-image[data-align="left"] .page-image-caption { text-align: left; }
|
|
171
|
-
.page-image[data-align="right"] .page-image-caption { text-align: right; }
|
|
172
|
-
.page-image-align { position: absolute; top: 8px; inset-inline-end: 8px; display: none; gap: 1px; padding: 2px; background: var(--page-pop-bg); color: var(--page-pop-ink); border: 1px solid var(--page-pop-line); box-shadow: var(--page-pop-shadow); border-radius: 7px; }
|
|
173
|
-
.page-image.is-selected .page-image-align, .page-image.page-block-selected .page-image-align { display: flex; }
|
|
174
|
-
.page-image-align button { display: grid; place-items: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 5px; background: none; color: inherit; cursor: pointer; }
|
|
175
|
-
.page-image-align button:hover, .page-image-align button[aria-pressed="true"] { background: var(--page-pop-hover); }
|
|
176
|
-
.page-image.is-selected .page-image-frame img { outline: 2px solid var(--page-selection); outline-offset: 2px; }
|
|
177
|
-
.page-image-caption { margin-top: 6px; color: var(--page-ink-soft); font-size: 0.88em; text-align: center; outline: none; }
|
|
178
|
-
.page-image-caption[data-empty]::before { content: attr(data-placeholder); color: var(--page-ink-faint); }
|
|
179
163
|
.ProseMirror-focused .page-placeholder::before { content: attr(data-placeholder); float: left; height: 0; color: var(--page-ink-faint); pointer-events: none; }
|
|
180
|
-
.page-
|
|
181
|
-
.page-file-link { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border: 1px solid var(--page-rule); border-radius: 8px; color: var(--page-ink); text-decoration: none; }
|
|
182
|
-
.page-file-icon { color: var(--page-link, var(--page-accent)); }
|
|
183
|
-
.page-file-name { color: var(--page-link, var(--page-accent)); min-width: 0; overflow-wrap: anywhere; }
|
|
184
|
-
.page-file-meta { margin-inline-start: auto; color: var(--page-ink-faint); font-size: 0.88em; }
|
|
185
|
-
.page-upload { display: flex; align-items: center; gap: 10px; margin: var(--page-block-gap) 0; padding: 8px 12px; border: 1px dashed var(--page-rule); border-radius: 8px; color: var(--page-ink-soft); font-size: 0.88em; }
|
|
186
|
-
.page-upload-preview { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; opacity: 0.6; }
|
|
187
|
-
.page-upload-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
188
|
-
.page-upload-bar { flex: 1; height: 2px; border-radius: 1px; background: var(--page-rule); overflow: hidden; }
|
|
189
|
-
.page-upload-bar > span { display: block; height: 100%; background: var(--page-progress, var(--page-ink-soft)); transition: width 200ms; }
|
|
190
|
-
.page-upload[data-state="error"] .page-upload-msg, .page-upload[data-state="embed-choice"] .page-upload-msg { flex: 1; }
|
|
191
|
-
.page-upload[data-state="error"] .page-upload-msg { color: var(--page-danger, #9E3B2F); }
|
|
192
|
-
.page-upload-btn { border: 0; padding: 4px 8px; border-radius: 6px; background: none; color: inherit; font: inherit; cursor: pointer; }
|
|
193
|
-
.page-upload-btn:hover { background: var(--page-pop-hover); }
|
|
164
|
+
.page-editor .page-title-placeholder::before { content: attr(data-placeholder); float: left; height: 0; color: var(--page-ink-faint); pointer-events: none; }
|
|
194
165
|
.page-csv-error { margin: 8px 0; color: var(--page-danger); font-size: 14px; }
|
|
195
166
|
.page-editor .page-table { max-width: 100%; min-width: 0; border: 1px solid var(--page-rule); border-radius: 8px; }
|
|
196
167
|
.page-editor .page-chart { position: relative; max-width: 100%; min-width: 0; margin-inline: 0; color: var(--page-ink); }
|
|
@@ -198,14 +169,6 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
|
|
|
198
169
|
.page-chart figcaption { color: var(--page-ink-soft); font-size: 0.88em; text-align: center; }
|
|
199
170
|
.page-chart-settings { display: block; margin: 8px auto 0; padding: 4px 10px; border: 1px solid var(--page-rule); border-radius: 6px; background: var(--page-pop-bg); color: var(--page-ink); font: inherit; cursor: pointer; }
|
|
200
171
|
.page-chart-title-input { display: block; width: min(100%, 24em); box-sizing: border-box; margin: 6px auto; padding: 4px 8px; border: 1px solid var(--page-rule); border-radius: 6px; background: var(--page-pop-bg); color: var(--page-ink); font: inherit; }
|
|
201
|
-
.page-tool-block { position: relative; min-height: 48px; border: 1px solid var(--page-rule); border-radius: 8px; padding: 12px; }
|
|
202
|
-
.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; }
|
|
203
|
-
.page-tool-block:hover .page-tool-settings, .page-tool-block:focus-within .page-tool-settings { opacity: 1; }
|
|
204
|
-
.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); }
|
|
205
|
-
.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; }
|
|
206
|
-
.page-ai-actions { display: flex; gap: 8px; margin-top: 8px; }
|
|
207
|
-
.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; }
|
|
208
|
-
.page-ai-status, [data-tool-note], [data-tool-error] { color: var(--page-danger); font-size: 0.85em; }
|
|
209
172
|
.page-table-scroll { max-width: 100%; overflow-x: auto; }
|
|
210
173
|
.page-table table { border-collapse: collapse; min-width: max-content; width: 100%; table-layout: auto; }
|
|
211
174
|
.page-table th, .page-table td { min-width: 120px; padding: 6px 10px; border-inline-end: 1px solid var(--page-rule); border-block-end: 1px solid var(--page-rule); text-align: start; white-space: pre-wrap; }
|
|
@@ -223,18 +186,31 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
|
|
|
223
186
|
.page-check-popup { display: inline-flex; gap: 4px; padding: 4px; background: var(--page-pop-bg); color: var(--page-pop-ink); border: 1px solid var(--page-pop-line); border-radius: var(--page-pop-radius); box-shadow: var(--page-pop-shadow); }
|
|
224
187
|
.page-check-popup input { font: inherit; color: inherit; background: var(--page-pop-bg); border: 1px solid var(--page-pop-line); }
|
|
225
188
|
.page-check-popup.page-check-owner { display: inline-block; width: min(280px, 80vw); padding: 8px 0; vertical-align: top; }
|
|
189
|
+
.page-board-owners { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding-inline: 8px; }
|
|
190
|
+
.page-board-people { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
191
|
+
.page-board-person { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px 0 4px; border: 1px solid var(--page-rule); border-radius: 14px; background: var(--page-pop-bg); color: var(--page-pop-ink); font: inherit; font-size: 13px; cursor: pointer; transition: background-color var(--page-fade), border-color var(--page-fade); }
|
|
192
|
+
.page-board-person.is-none { padding-inline-start: 10px; color: var(--page-pop-muted); }
|
|
193
|
+
.page-board-person:hover { background: var(--page-pop-hover); }
|
|
194
|
+
.page-board-person[aria-pressed="true"] { border-color: var(--page-accent); background: var(--page-selection); color: var(--page-pop-ink); }
|
|
195
|
+
.page-board-owner-input { width: 100%; height: 32px; box-sizing: border-box; padding: 0 8px; border: 1px solid var(--page-pop-line); border-radius: 7px; background: var(--page-pop-bg); color: var(--page-pop-ink); font: inherit; font-size: 14px; }
|
|
196
|
+
.page-board-owner-input::placeholder { color: var(--page-ink-faint); }
|
|
197
|
+
.page-board-face { flex: none; display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--page-code-bg); box-shadow: inset 0 0 0 1px var(--page-rule); color: var(--page-ink-soft); font-size: 10.5px; font-weight: 600; line-height: 1; }
|
|
226
198
|
.page-scrim { display: none; }
|
|
227
199
|
.page-shell [hidden] { display: none !important; }
|
|
228
|
-
${TABLE_CSS}
|
|
229
|
-
${BOARD_CSS}
|
|
230
200
|
@media (max-width: 560px) {
|
|
231
|
-
|
|
201
|
+
/* One fixed bar over the visual viewport, with the four controls a phone keeps.
|
|
202
|
+
The bubble and the desktop-only tools stand down so nothing is doubled. */
|
|
203
|
+
.page-dock { display: flex; position: fixed; left: 0; top: 8px; width: 204px; min-width: 204px; box-sizing: border-box; align-items: center; justify-content: center; gap: 4px; margin: 0; padding: 8px; z-index: 3; }
|
|
204
|
+
.page-tool-desk { display: none; }
|
|
205
|
+
.page-dock .page-tool-more { display: inline-flex; }
|
|
206
|
+
.page-dock .page-tool { min-width: 44px; height: 44px; }
|
|
207
|
+
.page-shell:has(.page-bubble:not([hidden])) .page-dock { visibility: visible; }
|
|
208
|
+
.page-dock + .page-editor { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
|
|
232
209
|
.page-plus { position: fixed; top: auto; left: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); width: 44px; height: 44px; border-radius: 22px; }
|
|
233
|
-
.page-grip { position: fixed; top: auto; left: 68px; bottom: calc(16px + env(safe-area-inset-bottom)); width: 44px; height: 44px; border-radius: 22px; opacity: 1; color: var(--page-chrome-ink-rest); background: var(--page-chrome-bg-rest); border: 1px solid var(--page-chrome-line-rest); }
|
|
234
|
-
.page-bubble {
|
|
235
|
-
.page-bubble .page-tool { min-width: 44px; height: 44px; }
|
|
210
|
+
.page-grip { position: fixed; top: auto; left: 68px; bottom: calc(16px + var(--page-grip-lift, 0px) + env(safe-area-inset-bottom)); width: 44px; height: 44px; border-radius: 22px; opacity: 1; color: var(--page-chrome-ink-rest); background: var(--page-chrome-bg-rest); border: 1px solid var(--page-chrome-line-rest); }
|
|
211
|
+
.page-bubble { display: none; }
|
|
236
212
|
.page-scrim { display: block; position: fixed; inset: 0; z-index: 4; background: var(--page-scrim); }
|
|
237
|
-
.page-menu { position: fixed; left: 0;
|
|
213
|
+
.page-menu { position: fixed; left: 0; top: 0; width: var(--page-pop-width, min(280px, calc(100vw - 16px))); max-height: min(70vh, var(--page-pop-max, 70vh)); overflow-y: auto; transform: translate(var(--page-pop-x, 0px), var(--page-pop-y, 0px)); z-index: 5; }
|
|
238
214
|
.page-menu-item { min-height: 48px; font-size: 16px; }
|
|
239
215
|
.page-link { position: fixed; left: 0; right: 0; top: auto; bottom: 0; max-width: none; padding: 8px 16px calc(8px + env(safe-area-inset-bottom)); border-radius: 0; border-width: 1px 0 0; }
|
|
240
216
|
.page-link-input { min-width: 0; height: 44px; font-size: 16px; }
|
|
@@ -242,9 +218,11 @@ ${BOARD_CSS}
|
|
|
242
218
|
.page-image-frame { width: 100% !important; }
|
|
243
219
|
.page-image-handle { display: none; }
|
|
244
220
|
.page-table th button, .page-table-row-menu, .page-check-chip, .page-check-popup button, .page-menu-item { min-height: 44px; }
|
|
221
|
+
.page-board-owner-input { font-size: 16px; height: 44px; }
|
|
222
|
+
.page-board-person { height: 44px; border-radius: 22px; }
|
|
245
223
|
}
|
|
246
224
|
@media (prefers-reduced-motion: reduce) {
|
|
247
|
-
.page-dock, .page-plus, .page-grip, .page-toggle-button, .page-image-handle, .page-upload-bar > span { transition: none; }
|
|
225
|
+
.page-dock, .page-plus, .page-grip, .page-toggle-button, .page-image-handle, .page-upload-bar > span, .page-board-person { transition: none; }
|
|
248
226
|
}
|
|
249
227
|
}
|
|
250
228
|
`;
|
|
@@ -263,30 +241,253 @@ const editKeys = ({ history: useHistory = true, undo, redo } = {}) => ({
|
|
|
263
241
|
"Mod-i": commands.toggleMark(schema.marks.em),
|
|
264
242
|
});
|
|
265
243
|
|
|
244
|
+
const restoreShortcut = (st, dispatch, view) => {
|
|
245
|
+
if (composing(null, view)) return false;
|
|
246
|
+
return inputrules.undoInputRule(st, dispatch && ((tr) => dispatch(asStep(tr))));
|
|
247
|
+
};
|
|
248
|
+
|
|
266
249
|
const markKeys = {
|
|
267
|
-
Backspace: commands.chainCommands(
|
|
250
|
+
Backspace: commands.chainCommands(restoreShortcut, backspaceToParagraph),
|
|
268
251
|
"Mod-e": commands.toggleMark(schema.marks.code),
|
|
269
252
|
"Mod-Shift-s": commands.toggleMark(schema.marks.strike),
|
|
270
253
|
};
|
|
271
254
|
|
|
272
255
|
const defaultUpload = (file, options) => globalThis.clay?.upload?.(file, options) ?? Promise.resolve({ ok: false, code: "unavailable", msg: "File uploads are not available in this editor" });
|
|
273
256
|
|
|
274
|
-
|
|
257
|
+
const nodeViewRegistries = new WeakMap();
|
|
258
|
+
|
|
259
|
+
export const lazyViewsKey = new state.PluginKey("lazyViews");
|
|
260
|
+
|
|
261
|
+
// Stands in for a view part's node view until its type loads: the schema's own
|
|
262
|
+
// toDOM, and an update that asks to be recreated once the real view exists.
|
|
263
|
+
class PendingView {
|
|
264
|
+
constructor(node, registry) {
|
|
265
|
+
this.node = node;
|
|
266
|
+
this.type = node.type;
|
|
267
|
+
this.registry = registry;
|
|
268
|
+
const { dom, contentDOM } = model.DOMSerializer.renderSpec(document, node.type.spec.toDOM(node));
|
|
269
|
+
this.dom = dom;
|
|
270
|
+
this.contentDOM = contentDOM ?? null;
|
|
271
|
+
}
|
|
272
|
+
update(node) { return node.type === this.type && !this.registry.has(node.type.name); }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Recreates only the node views of the types that just loaded: one node
|
|
276
|
+
// decoration makes ProseMirror rebuild those nodes and no others.
|
|
277
|
+
export function lazyViewsPlugin() {
|
|
278
|
+
return new state.Plugin({
|
|
279
|
+
key: lazyViewsKey,
|
|
280
|
+
state: {
|
|
281
|
+
init: () => pmView.DecorationSet.empty,
|
|
282
|
+
apply(tr) {
|
|
283
|
+
const meta = tr.getMeta(lazyViewsKey);
|
|
284
|
+
if (!meta) return pmView.DecorationSet.empty;
|
|
285
|
+
const decorations = [];
|
|
286
|
+
tr.doc.forEach((node, offset) => {
|
|
287
|
+
if (!meta.types.includes(node.type.name)) return;
|
|
288
|
+
decorations.push(pmView.Decoration.node(offset, offset + node.nodeSize, {}, { lazyView: decorations.length + 1 }));
|
|
289
|
+
});
|
|
290
|
+
return pmView.DecorationSet.create(tr.doc, decorations);
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
props: { decorations: (state) => lazyViewsKey.getState(state) },
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Adds a view part to a running editor: its CSS at once, its node views into the
|
|
298
|
+
// editor's registry, and a transaction that recreates only those block types.
|
|
299
|
+
export function installPageFeature({ view, upload, toolHost, board = null, undo = null, redo = null, revise = null, highlight }, id, mod) {
|
|
300
|
+
const feature = mod.createFeature({ view, upload, toolHost, board, undo, redo, revise, highlight });
|
|
301
|
+
const registry = nodeViewRegistries.get(view);
|
|
302
|
+
const apply = () => {
|
|
303
|
+
if (feature.css) styleWithLegacyId(`page-feature-${id}`, feature.css, `page-feature-${id}`, ["editor-ui"]);
|
|
304
|
+
if (!feature.nodeViews) return;
|
|
305
|
+
const types = Object.keys(feature.nodeViews);
|
|
306
|
+
for (const type of types) registry.set(type, feature.nodeViews[type]);
|
|
307
|
+
view.dispatch(view.state.tr.setMeta(lazyViewsKey, { types }).setMeta("addToHistory", false));
|
|
308
|
+
};
|
|
309
|
+
const afterComposition = () => {
|
|
310
|
+
if (view.isDestroyed) return;
|
|
311
|
+
if (view.composing) setTimeout(afterComposition, 25);
|
|
312
|
+
else apply();
|
|
313
|
+
};
|
|
314
|
+
if (view.composing) view.dom.addEventListener("compositionend", () => setTimeout(afterComposition, 25), { once: true });
|
|
315
|
+
else apply();
|
|
316
|
+
return feature;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Loads the view part for a block type the editor shows but has no view for:
|
|
320
|
+
// the blocks that were there at boot, and any that arrive later.
|
|
321
|
+
function featureWatchPlugin(features) {
|
|
322
|
+
return new state.Plugin({
|
|
323
|
+
view(editorView) {
|
|
324
|
+
const failed = new Set();
|
|
325
|
+
const walk = () => {
|
|
326
|
+
for (const id of failed) if (features.has(id)) failed.delete(id);
|
|
327
|
+
editorView.state.doc.forEach((node) => {
|
|
328
|
+
const id = featureForType(node.type.name);
|
|
329
|
+
if (!id || features.has(id) || failed.has(id)) return;
|
|
330
|
+
features.ensure(id).catch(() => { failed.add(id); });
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
let doc = editorView.state.doc;
|
|
334
|
+
walk();
|
|
335
|
+
return {
|
|
336
|
+
update() {
|
|
337
|
+
if (editorView.state.doc === doc) return;
|
|
338
|
+
doc = editorView.state.doc;
|
|
339
|
+
walk();
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// The optional commands. A command module exports `createFeature(context)` returning
|
|
347
|
+
// `{ run, dispose }`; the editor fetches it on the first ask and installs it once, and
|
|
348
|
+
// nothing here is preloaded by the block-type scan. A command is asked for with the state
|
|
349
|
+
// it was asked in: the module is fetched, and the command runs only if the edit is still
|
|
350
|
+
// open, the document revision has not moved and the selection is where it was. A failed
|
|
351
|
+
// fetch is reported and asked for again on the next request; an exit disposes what was
|
|
352
|
+
// installed and aborts whatever was still arriving, and the next entry installs fresh
|
|
353
|
+
// against the module the browser already holds.
|
|
354
|
+
function createCommandHost({ load = () => Promise.reject(new Error("page: this editor has no command module")), context = () => ({}), reportError } = {}) {
|
|
355
|
+
const modules = new Map();
|
|
356
|
+
const waiters = new Set();
|
|
357
|
+
let view = null;
|
|
358
|
+
let loader = null;
|
|
359
|
+
let active = true;
|
|
360
|
+
let disposed = false;
|
|
361
|
+
let revision = 0;
|
|
362
|
+
|
|
363
|
+
// One fetch per command for the life of the host, whatever the entry: a module that
|
|
364
|
+
// failed is the one thing not kept, so the next ask loads it again.
|
|
365
|
+
const moduleFor = (id) => {
|
|
366
|
+
let fetched = modules.get(id);
|
|
367
|
+
if (!fetched) {
|
|
368
|
+
fetched = Promise.resolve(load(id));
|
|
369
|
+
modules.set(id, fetched);
|
|
370
|
+
fetched.catch(() => { if (modules.get(id) === fetched) modules.delete(id); });
|
|
371
|
+
}
|
|
372
|
+
return fetched;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const loaderFor = () => {
|
|
376
|
+
loader ??= createFeatureLoader({
|
|
377
|
+
load: moduleFor,
|
|
378
|
+
install: (id, mod) => mod.createFeature(context(view)),
|
|
379
|
+
reportError: reportError ?? ((error, id) => console.error(`clayjs: the ${id} command failed to load`, error)),
|
|
380
|
+
});
|
|
381
|
+
return loader;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// The document revision and the selection a command was asked for. A revision moves
|
|
385
|
+
// when any transaction changes the document, whoever wrote it.
|
|
386
|
+
const leaseOf = () => {
|
|
387
|
+
const at = revision;
|
|
388
|
+
const selection = view.state.selection;
|
|
389
|
+
return {
|
|
390
|
+
revision: at,
|
|
391
|
+
selection,
|
|
392
|
+
valid: () => active && !disposed && !view.isDestroyed && revision === at && view.state.selection.eq(selection),
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
function observe(state, prevState) {
|
|
397
|
+
if (state.doc !== prevState.doc) revision += 1;
|
|
398
|
+
for (const waiter of waiters) {
|
|
399
|
+
if (waiter.lease.valid()) continue;
|
|
400
|
+
waiters.delete(waiter);
|
|
401
|
+
waiter.controller.abort({ ok: false, code: "stale-target" });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function run(id, args) {
|
|
406
|
+
if (!active || disposed || !view || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
407
|
+
const waiter = { lease: leaseOf(), controller: new AbortController() };
|
|
408
|
+
waiters.add(waiter);
|
|
409
|
+
try {
|
|
410
|
+
const feature = await loaderFor().ensure(id, { signal: waiter.controller.signal });
|
|
411
|
+
if (!feature) return { ok: false, code: "not-editing" };
|
|
412
|
+
if (!waiter.lease.valid()) return { ok: false, code: "stale-target" };
|
|
413
|
+
return await feature.run(args);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
if (waiter.controller.signal.aborted) return waiter.controller.signal.reason;
|
|
416
|
+
return { ok: false, code: "unavailable", error };
|
|
417
|
+
} finally {
|
|
418
|
+
waiters.delete(waiter);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function leave() {
|
|
423
|
+
if (!active) return;
|
|
424
|
+
active = false;
|
|
425
|
+
for (const waiter of waiters) waiter.controller.abort({ ok: false, code: "not-editing" });
|
|
426
|
+
waiters.clear();
|
|
427
|
+
loader?.dispose();
|
|
428
|
+
loader = null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
attach(editorView) { view = editorView; },
|
|
433
|
+
observe,
|
|
434
|
+
run,
|
|
435
|
+
enter() { if (!disposed) active = true; },
|
|
436
|
+
leave,
|
|
437
|
+
dispose() {
|
|
438
|
+
leave();
|
|
439
|
+
disposed = true;
|
|
440
|
+
active = false;
|
|
441
|
+
modules.clear();
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const commandHosts = new WeakMap();
|
|
447
|
+
// A command asks for its module with the state it was asked in, so the host has to hear
|
|
448
|
+
// about every transaction: one that moves the document revision or the selection refuses
|
|
449
|
+
// a command whose module is still arriving rather than running it where the person has
|
|
450
|
+
// since left.
|
|
451
|
+
function commandWatchPlugin(host) {
|
|
452
|
+
return new state.Plugin({
|
|
453
|
+
view: () => ({ update: (editorView, prevState) => host.observe(editorView.state, prevState) }),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Runs an optional command on a mounted editor, by the id its row has in COMMAND_FEATURES.
|
|
458
|
+
// A command nobody offers, or an editor with no command host, refuses rather than throws.
|
|
459
|
+
export function runPageCommand(view, id, args) {
|
|
460
|
+
const host = view ? commandHosts.get(view) : null;
|
|
461
|
+
if (!host) return Promise.resolve({ ok: false, code: "not-editing" });
|
|
462
|
+
if (!Object.hasOwn(COMMAND_FEATURES, id)) return Promise.resolve({ ok: false, code: "unknown-command" });
|
|
463
|
+
return host.run(id, args);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Tools follow the tables a document holds; the host arrives with the tool view
|
|
467
|
+
// part on a document that had no tool at boot.
|
|
468
|
+
function toolFollowPlugin(toolHost) {
|
|
469
|
+
return new state.Plugin({
|
|
470
|
+
view(view) {
|
|
471
|
+
const changed = toolHost ? () => toolHost.tablesChanged() : () => toolViews.get(view)?.tablesChanged?.();
|
|
472
|
+
let previous = view.state.doc;
|
|
473
|
+
return { update(next) { if (next.state.doc !== previous) { previous = next.state.doc; changed(); } } };
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export function pagePlugins({ upload = defaultUpload, highlight, history: useHistory = true, undo, redo, onAiTool = null, toolHost = null, features = null, commandHost = null } = {}) {
|
|
275
479
|
return [
|
|
276
480
|
announcementPlugin(),
|
|
277
481
|
chromePlugin({ onAiTool }),
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
let previous = view.state.doc;
|
|
281
|
-
return { update(next) { if (next.state.doc !== previous) { previous = next.state.doc; toolHost.tablesChanged(); } } };
|
|
282
|
-
},
|
|
283
|
-
}),
|
|
482
|
+
slashRangePlugin(),
|
|
483
|
+
toolFollowPlugin(toolHost),
|
|
284
484
|
linkPlugin(),
|
|
285
485
|
tableSelectPlugin(),
|
|
286
486
|
blockSelectionPlugin(),
|
|
287
487
|
markdownPlugin(),
|
|
288
488
|
markdownKeys(),
|
|
289
489
|
placeholderPlugin(),
|
|
490
|
+
titlePlugin(),
|
|
290
491
|
keymap.keymap(markKeys),
|
|
291
492
|
tableKeys,
|
|
292
493
|
checklistKeys,
|
|
@@ -301,11 +502,15 @@ export function pagePlugins({ upload = defaultUpload, highlight, history: useHis
|
|
|
301
502
|
checklistPlugin(),
|
|
302
503
|
csvImportPlugin({ upload }),
|
|
303
504
|
collapsePlugin(),
|
|
304
|
-
mediaPlugin({ upload }),
|
|
305
|
-
|
|
505
|
+
mediaPlugin({ upload, features }),
|
|
506
|
+
highlightSlot(),
|
|
306
507
|
pastePlugin(),
|
|
508
|
+
historyIntentPlugin(),
|
|
307
509
|
useHistory ? history.history() : null,
|
|
308
510
|
gapcursor.gapCursor(),
|
|
511
|
+
lazyViewsPlugin(),
|
|
512
|
+
features ? featureWatchPlugin(features) : null,
|
|
513
|
+
commandHost ? commandWatchPlugin(commandHost) : null,
|
|
309
514
|
].filter(Boolean);
|
|
310
515
|
}
|
|
311
516
|
|
|
@@ -315,6 +520,29 @@ export function pageState(blocks, options = {}) {
|
|
|
315
520
|
return state.EditorState.create({ doc, plugins: pagePlugins(options) });
|
|
316
521
|
}
|
|
317
522
|
|
|
523
|
+
// The shell is built detached and moved into the article once it is complete, so
|
|
524
|
+
// the node views in it measured nothing while it was out of the document. One pass
|
|
525
|
+
// in place gives them a real box: the state is the same object it was, and no node
|
|
526
|
+
// view is rebuilt for it.
|
|
527
|
+
export function refreshMountedView(view) {
|
|
528
|
+
if (!view || view.isDestroyed) return;
|
|
529
|
+
view.updateState(view.state);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Admission on a mounted editor, in one place: the commit that enters edit opens it,
|
|
533
|
+
// and the exit that holds the session closed shuts it without touching a buffer.
|
|
534
|
+
export function setEditorActive(view, active) {
|
|
535
|
+
if (!view || view.isDestroyed) return false;
|
|
536
|
+
const open = active === true;
|
|
537
|
+
view.setProps({ editable: () => open });
|
|
538
|
+
// A refused exit closes admission without destroying the view, so the commands are told
|
|
539
|
+
// here: the exit disposes what they installed, and the next entry installs fresh.
|
|
540
|
+
const host = commandHosts.get(view);
|
|
541
|
+
if (open) host?.enter();
|
|
542
|
+
else host?.leave();
|
|
543
|
+
return open;
|
|
544
|
+
}
|
|
545
|
+
|
|
318
546
|
// Clicking a checklist box (drawn by ::before in the item's start gutter)
|
|
319
547
|
// toggles `checked`.
|
|
320
548
|
function checkboxClick(view, event) {
|
|
@@ -335,9 +563,8 @@ function checkboxClick(view, event) {
|
|
|
335
563
|
// Mounts the editor in `mount`, inside a .page-shell wrapper (the surface a
|
|
336
564
|
// theme styles). `onChange(record)` gets the block record (blockMap) after
|
|
337
565
|
// every transaction that changed the document.
|
|
338
|
-
export function createPageEditor(mount, { blocks = null, onChange = null, upload, highlight, doc: initialDoc, dispatch, history: useHistory = true, undo, redo, editable = true, toolHost
|
|
566
|
+
export function createPageEditor(mount, { blocks = null, onChange = null, upload, highlight, doc: initialDoc, dispatch, history: useHistory = true, undo, redo, editable = true, toolHost = null, onAiTool = null, reviseTool = null, board = null, features: providedFeatures = null } = {}) {
|
|
339
567
|
const doc = mount.ownerDocument;
|
|
340
|
-
const toolHost = providedToolHost ?? createToolHost({ mode: "edit" });
|
|
341
568
|
// Editor chrome, injected on every edit-mode boot and kept out of the saved file:
|
|
342
569
|
// the reader theme <link> styles the page for visitors.
|
|
343
570
|
styleWithLegacyId(STYLE_ID, PAGE_EDITOR_CSS, STYLE_ID, ["editor-ui"]);
|
|
@@ -347,20 +574,32 @@ export function createPageEditor(mount, { blocks = null, onChange = null, upload
|
|
|
347
574
|
shell.setAttribute("sap-ignore", "");
|
|
348
575
|
mount.appendChild(shell);
|
|
349
576
|
let afterStep = false;
|
|
350
|
-
|
|
351
|
-
|
|
577
|
+
let view = null;
|
|
578
|
+
const features = providedFeatures ?? createFeatureLoader({
|
|
579
|
+
load: (id) => FEATURES[id].load(),
|
|
580
|
+
install: (id, mod) => installPageFeature({ view, upload, toolHost, board, undo, redo, revise: reviseTool, highlight }, id, mod),
|
|
581
|
+
});
|
|
582
|
+
// Commands are their own loader: a document never fetches a picker to be read or typed
|
|
583
|
+
// in, and the host outlives a refused exit so a re-entry installs once more.
|
|
584
|
+
const commandHost = createCommandHost({
|
|
585
|
+
load: (id) => COMMAND_FEATURES[id].load(),
|
|
586
|
+
context: (editorView) => ({ view: editorView, upload }),
|
|
587
|
+
});
|
|
588
|
+
const nodeViewRegistry = new Map();
|
|
589
|
+
const nodeViews = {
|
|
590
|
+
chart: (node, v, getPos) => new ChartView(node, v, getPos),
|
|
591
|
+
};
|
|
592
|
+
for (const feature of Object.values(FEATURES)) for (const type of feature.views ?? feature.types) {
|
|
593
|
+
nodeViews[type] = (node, v, getPos, decorations, inner) => {
|
|
594
|
+
const make = nodeViewRegistry.get(node.type.name);
|
|
595
|
+
return make ? make(node, v, getPos, decorations, inner) : new PendingView(node, nodeViewRegistry);
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
view = new pmView.EditorView(shell, {
|
|
599
|
+
state: pageState(blocks, { upload, highlight, doc: initialDoc, history: useHistory, undo, redo, onAiTool, toolHost, features, commandHost }),
|
|
352
600
|
attributes: { class: "page-editor" },
|
|
353
601
|
editable: () => editable,
|
|
354
|
-
nodeViews
|
|
355
|
-
image: (node, v, getPos) => new ImageView(node, v, getPos),
|
|
356
|
-
file: (node, v) => new FileView(node, v),
|
|
357
|
-
code_block: (node, v, getPos) => new CodeBlockView(node, v, getPos),
|
|
358
|
-
table: (node, v, getPos) => new TableView(node, v, getPos),
|
|
359
|
-
chart: (node, v, getPos) => new ChartView(node, v, getPos),
|
|
360
|
-
tool: (node, v, getPos) => new ToolView(node, v, getPos, { host: toolHost, revise: reviseTool }),
|
|
361
|
-
table_cell: (node, v, getPos) => new TableCellView(node, v, getPos),
|
|
362
|
-
...(board ? { board: (node, v, getPos) => new BoardView(node, v, getPos, { commands: board, undo, redo, panel: cardPanel(v) }) } : {}),
|
|
363
|
-
},
|
|
602
|
+
nodeViews,
|
|
364
603
|
handleDOMEvents: {
|
|
365
604
|
mousedown: checkboxClick,
|
|
366
605
|
dragstart(v, event) {
|
|
@@ -382,5 +621,10 @@ export function createPageEditor(mount, { blocks = null, onChange = null, upload
|
|
|
382
621
|
if (onChange && view.state.doc !== before) onChange(blockMap(view.state.doc));
|
|
383
622
|
},
|
|
384
623
|
});
|
|
624
|
+
nodeViewRegistries.set(view, nodeViewRegistry);
|
|
625
|
+
commandHost.attach(view);
|
|
626
|
+
commandHosts.set(view, commandHost);
|
|
627
|
+
const destroy = view.destroy.bind(view);
|
|
628
|
+
view.destroy = () => { commandHost.dispose(); features.dispose(); destroy(); };
|
|
385
629
|
return view;
|
|
386
630
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Optional editor features load the first time a document needs them. One import per
|
|
2
|
+
// feature; a caller's abort stops only its own wait; dispose() makes late results inert.
|
|
3
|
+
export function createFeatureLoader({ load, install, reportError = (error) => console.error("clayjs: an editor feature failed to load", error) }) {
|
|
4
|
+
const installed = new Map();
|
|
5
|
+
const pending = new Map();
|
|
6
|
+
const prepared = new Map();
|
|
7
|
+
let disposed = false;
|
|
8
|
+
|
|
9
|
+
// A preparation fetches the module without installing it, so the boot can overlap
|
|
10
|
+
// that fetch with the room join. The install that follows consumes this same
|
|
11
|
+
// import: a feature is fetched once, and a failed fetch is that attempt. The next
|
|
12
|
+
// ensure after it loads again through the loader's reporting and the feature's
|
|
13
|
+
// Retry.
|
|
14
|
+
function prepare(id) {
|
|
15
|
+
if (disposed) return Promise.reject(new Error(`page: feature loader disposed before ${id}`));
|
|
16
|
+
let shared = prepared.get(id);
|
|
17
|
+
if (!shared) {
|
|
18
|
+
shared = Promise.resolve(load(id));
|
|
19
|
+
prepared.set(id, shared);
|
|
20
|
+
// The install reports the failure; this keeps an unconsumed preparation quiet.
|
|
21
|
+
shared.catch(() => {});
|
|
22
|
+
}
|
|
23
|
+
return shared;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadFor(id) {
|
|
27
|
+
const fetched = prepared.get(id);
|
|
28
|
+
if (!fetched) return load(id);
|
|
29
|
+
prepared.delete(id);
|
|
30
|
+
return fetched;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ensure(id, { signal } = {}) {
|
|
34
|
+
if (disposed) return Promise.reject(new Error(`page: feature loader disposed before ${id}`));
|
|
35
|
+
if (installed.has(id)) return Promise.resolve(installed.get(id));
|
|
36
|
+
let shared = pending.get(id);
|
|
37
|
+
if (!shared) {
|
|
38
|
+
shared = loadFor(id)
|
|
39
|
+
.then((mod) => (disposed ? null : Promise.resolve(install(id, mod))))
|
|
40
|
+
.then((feature) => {
|
|
41
|
+
if (disposed) {
|
|
42
|
+
feature?.dispose?.();
|
|
43
|
+
return feature;
|
|
44
|
+
}
|
|
45
|
+
installed.set(id, feature);
|
|
46
|
+
return feature;
|
|
47
|
+
});
|
|
48
|
+
shared.catch((error) => { if (!disposed) reportError(error, id); }).finally(() => pending.delete(id));
|
|
49
|
+
pending.set(id, shared);
|
|
50
|
+
}
|
|
51
|
+
if (!signal) return shared;
|
|
52
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const onAbort = () => reject(signal.reason);
|
|
55
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
56
|
+
shared.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
prepare,
|
|
62
|
+
ensure,
|
|
63
|
+
has: (id) => installed.has(id),
|
|
64
|
+
dispose() {
|
|
65
|
+
disposed = true;
|
|
66
|
+
for (const feature of installed.values()) feature?.dispose?.();
|
|
67
|
+
installed.clear();
|
|
68
|
+
pending.clear();
|
|
69
|
+
prepared.clear();
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BOARD_CSS } from "../board-style.js";
|
|
2
|
+
import { BoardView } from "../board-view.js";
|
|
3
|
+
import { cardPanel } from "../board-panel.js";
|
|
4
|
+
|
|
5
|
+
export function createFeature({ board = null, undo = null, redo = null } = {}) {
|
|
6
|
+
return {
|
|
7
|
+
id: "board",
|
|
8
|
+
nodeViews: board ? { board: (node, view, getPos) => new BoardView(node, view, getPos, { commands: board, undo, redo, panel: cardPanel(view) }) } : null,
|
|
9
|
+
css: board ? BOARD_CSS : null,
|
|
10
|
+
dispose() {},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Code blocks: their language picker and the syntax colours. Loaded the first
|
|
2
|
+
// time a document has or takes a code block.
|
|
3
|
+
import { CodeBlockView, startHighlighter } from "../highlight.js";
|
|
4
|
+
|
|
5
|
+
export function createFeature({ view = null, highlight = {} } = {}) {
|
|
6
|
+
const highlighter = view ? startHighlighter(view, highlight) : null;
|
|
7
|
+
return {
|
|
8
|
+
id: "code",
|
|
9
|
+
nodeViews: { code_block: (node, v, getPos) => new CodeBlockView(node, v, getPos) },
|
|
10
|
+
css: null,
|
|
11
|
+
dispose() { highlighter?.destroy(); },
|
|
12
|
+
};
|
|
13
|
+
}
|