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