@panphora/clayjs 1.15.2 → 1.17.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 +84291 -57611
- 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-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-336SGU5W.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3H6BZDNJ.js +6 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-3RUU4TPX.js +91 -0
- package/dist/m/c-3XIRD4NU.js +1 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4DFEJ2PO.js +3 -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-56QRZTZH.js +1 -0
- package/dist/m/c-5J4E7O4Y.js +4 -0
- package/dist/m/c-5LKDWQTL.js +19 -0
- package/dist/m/c-5QWQ7A5L.js +1 -0
- package/dist/m/c-5RJFTLSF.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-6AZOGAPE.js +1 -0
- package/dist/m/c-6CRUUXOX.js +5 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-6KEVEQ7L.js +1 -0
- package/dist/m/c-6NC5I2TV.js +1 -0
- package/dist/m/c-74MGYWZF.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7OLLJPWP.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-A3E3NIMC.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-AMSNPOCX.js +1 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/c-BQ6LOBEE.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-CJ3A7ZKE.js +1 -0
- package/dist/m/c-CQHPGKX2.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DCXOXBF3.js +1 -0
- package/dist/m/c-DFS4XZJV.js +2 -0
- package/dist/m/c-DNENWY4O.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-E4LIR5X5.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EVCQ3BKA.js +1 -0
- package/dist/m/c-EZU56NQV.js +34 -0
- package/dist/m/c-FDNHHZY5.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-FRLHSY7E.js +1 -0
- package/dist/m/c-FROJUZMV.js +1 -0
- package/dist/m/c-G2JPKCTP.js +1 -0
- package/dist/m/c-GB6TU7WV.js +1 -0
- package/dist/m/c-GFJJEBTU.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-GSFIGKOP.js +1 -0
- package/dist/m/c-GY422N3Q.js +1 -0
- package/dist/m/c-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HDDEEL77.js +1 -0
- package/dist/m/c-HFKUVACH.js +1 -0
- package/dist/m/c-HHDFCNHV.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-I2ZATQCD.js +2 -0
- package/dist/m/c-IEM4NTCS.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-JDCX26UP.js +25 -0
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-JU3NK25H.js +1 -0
- package/dist/m/c-KAU6E43D.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGVGROZT.js +1 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KQ6TYJAP.js +135 -0
- package/dist/m/c-KU3ZFADW.js +1 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LWLBHLVC.js +3 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-M2L47S2X.js +7 -0
- package/dist/m/c-MFP2ZWD4.js +1 -0
- package/dist/m/c-MKSQ4Y57.js +1 -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-NRPJEVJS.js +1 -0
- package/dist/m/c-NUFFJVVJ.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NUHSPT5P.js +1 -0
- package/dist/m/c-O72HVYSA.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-OW5QIGG3.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PGRCK73F.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PS5ARVWR.js +9 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-Q622NS2Y.js +1 -0
- package/dist/m/c-Q7NQD65V.js +1 -0
- package/dist/m/c-QH63LQOS.js +1 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QICP2YR5.js +1 -0
- package/dist/m/c-QJ4OM3G6.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-R3L7ZJGB.js +2 -0
- package/dist/m/c-R7IWVINQ.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RSMOODDU.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-RYUXUUBX.js +1 -0
- package/dist/m/c-S4ZEH46B.js +9 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SDAXDM2D.js +1 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SMBZG5X4.js +1 -0
- package/dist/m/c-SMGU7RT3.js +235 -0
- package/dist/m/c-SPIORRYS.js +1 -0
- package/dist/m/c-SWLRT66R.js +1 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TCJTVAFN.js +1 -0
- package/dist/m/c-TJ6WOJL3.js +67 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-TYLXX4NF.js +21 -0
- package/dist/m/c-U62OB4BT.js +1 -0
- package/dist/m/c-U6YWUAR3.js +8 -0
- package/dist/m/c-UDCSU5G5.js +2 -0
- package/dist/m/c-UDUUJPFZ.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VE6ZMYMC.js +1 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VHM5XQHK.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VI5RMSVY.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VRZN56XY.js +1 -0
- package/dist/m/c-W5MCHEGA.js +1 -0
- package/dist/m/c-WDC5GVKU.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-WWXIDH4O.js +28 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XCNTK76U.js +1 -0
- package/dist/m/c-XMS6DPBR.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y5HBBIQ4.js +1 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/c-ZVPVXTJF.js +4 -0
- package/dist/m/c-ZVRKXKM7.js +1 -0
- package/dist/m/c-ZWIOJGMT.js +1 -0
- package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
- package/dist/m/core/autosave-HRYGUWV6.js +1 -0
- package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
- package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
- package/dist/m/core/persist-JFYRHMRJ.js +3 -0
- package/dist/m/core/recovery-FLTCC2OC.js +1 -0
- package/dist/m/core/save-HQZ4S667.js +1 -0
- package/dist/m/core/save-core-KGQWFUNH.js +1 -0
- package/dist/m/core/snapshot-EGQZNT45.js +1 -0
- package/dist/m/core/unsaved-warning-IDJLD66H.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-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-PVXGPUDA.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-S4I7GKXR.js +1 -0
- package/dist/m/manifest-4C419D67.js +1 -0
- package/dist/m/manifest-edit-B077F288.js +1 -0
- package/dist/m/manifest.json +3988 -0
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-NNJA4OKD.js +202 -0
- package/dist/m/plugins/ai-edit-QN4PMMNL.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-RM5IR6AO.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
- package/dist/m/plugins/live-HFWD4OIH.js +1 -0
- package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-JOKB6XPR.js +2 -0
- package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
- package/dist/m/plugins/people-SGPJKANK.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-NRQDSWX6.js +517 -0
- package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
- package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
- package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
- package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
- package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
- package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
- package/dist/m/reader-core-DXYCURFF.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
- package/dist/m/ui/index-3JLVI27O.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 +4034 -0
- package/dist/page/themes/light-1.css +430 -0
- package/dist/server/core.cjs +23748 -22855
- 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/boot-phase.js +2 -1
- package/src/core/conflict-download.js +19 -23
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/download.js +32 -0
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +90 -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 +113 -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 +806 -119
- package/src/core/snapshot.js +159 -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 +140 -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 +113 -6
- 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 +3 -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/chart-tables.js +11 -0
- package/src/page/chart.js +6 -10
- package/src/page/checklist.js +1 -1
- package/src/page/checkpoint-proof.js +289 -0
- package/src/page/chrome.js +431 -63
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +52 -0
- package/src/page/document-title-runtime.js +90 -0
- package/src/page/document-title.js +12 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +399 -90
- package/src/page/emoji-data.js +162 -0
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +21 -0
- package/src/page/features/chart.js +11 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +28 -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-command.js +9 -0
- package/src/page/format-gate.js +179 -0
- package/src/page/format-host.js +54 -0
- package/src/page/format-level.js +157 -0
- package/src/page/format-native-commands.js +142 -0
- package/src/page/format-native.js +42 -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 +45 -28
- package/src/page/items.js +67 -14
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +112 -3
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +240 -51
- 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 +46 -4
- package/src/page/save-states.js +1 -1
- package/src/page/schema.js +47 -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/content.css +25 -1
- package/src/page/themes/light-1.css +163 -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-hooks.js +32 -0
- package/src/page/view.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +40 -5
- 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 +26 -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 +97 -24
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +488 -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 +319 -35
- 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 +1131 -270
- 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/intro.js +2 -1
- 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 +544 -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/core/snapshot.js
CHANGED
|
@@ -74,6 +74,98 @@ export function originalSnapshotNode(cloneNode) {
|
|
|
74
74
|
return snapshotProvenance.get(cloneNode) || null;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// The private artifact a save renderer may hand back for one capture: the model of
|
|
78
|
+
// the exact bytes it returned, paired with the tree it was given, so an accepted save
|
|
79
|
+
// can adopt it instead of parsing and pairing the accepted bytes again. It is keyed by
|
|
80
|
+
// the capture result object the caller gets, so it lives exactly as long as the save
|
|
81
|
+
// that produced it and there is no registry to grow.
|
|
82
|
+
const captureArtifacts = new WeakMap();
|
|
83
|
+
// The capture being serialized right now. Only a save capture has one; an inspection
|
|
84
|
+
// capture and a direct `captureForSave` have no request behind them to adopt anything.
|
|
85
|
+
let activeCapture = null;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The capture the save renderer is producing bytes for, or null.
|
|
89
|
+
*
|
|
90
|
+
* Read by the renderer to find out whose bytes these are, and to attach the artifact
|
|
91
|
+
* with attachCaptureArtifact. Per call, not global state: it is set for exactly the
|
|
92
|
+
* synchronous serialization of one save clone.
|
|
93
|
+
*/
|
|
94
|
+
export function currentCaptureContext() {
|
|
95
|
+
return activeCapture;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Attach the artifact to the capture being serialized.
|
|
100
|
+
*
|
|
101
|
+
* Ignored outside a save capture, because an artifact nobody can accept is a parse and
|
|
102
|
+
* a pairing paid for nothing.
|
|
103
|
+
*/
|
|
104
|
+
export function attachCaptureArtifact(artifact) {
|
|
105
|
+
if (activeCapture && artifact) activeCapture.artifact = artifact;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The page state of one save capture: the exact value its page tag carried when its
|
|
109
|
+
// bytes were serialized, copied so the edits that follow the capture can never reach it.
|
|
110
|
+
// Keyed by the capture id every accepted event carries, and dropped when the capture
|
|
111
|
+
// settles, so an accepted save reads the state it was captured from rather than the
|
|
112
|
+
// store's value at the moment the host answered.
|
|
113
|
+
let capturePageStateReader = null;
|
|
114
|
+
const capturePageStates = new Map();
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Register the reader that finds a page state in a final save clone.
|
|
118
|
+
*
|
|
119
|
+
* A page's own code registers it (page/save-projection.js), so a document without a
|
|
120
|
+
* page pays one null check per capture and nothing else. Read from the clone rather
|
|
121
|
+
* than from a store because that tag is what the file receives, after every transform
|
|
122
|
+
* and every authored save handler has run.
|
|
123
|
+
*/
|
|
124
|
+
export function setCapturePageState(reader) {
|
|
125
|
+
capturePageStateReader = reader;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The page state pinned to one capture, or null.
|
|
130
|
+
*
|
|
131
|
+
* @param {?number} captureId
|
|
132
|
+
* @returns {?Object}
|
|
133
|
+
*/
|
|
134
|
+
export function capturePageState(captureId) {
|
|
135
|
+
if (captureId === null || captureId === undefined) return null;
|
|
136
|
+
return capturePageStates.get(captureId) ?? null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The artifact this capture's renderer attached, or null. */
|
|
140
|
+
export function captureArtifact(result) {
|
|
141
|
+
return result ? captureArtifacts.get(result) || null : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Forget a capture's artifact: it was skipped, refused, or has now settled. */
|
|
145
|
+
export function releaseCaptureArtifact(result) {
|
|
146
|
+
if (!result) return;
|
|
147
|
+
captureArtifacts.delete(result);
|
|
148
|
+
capturePageStates.delete(result.captureId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Does this clone hold an authored save handler, template contents included?
|
|
153
|
+
*
|
|
154
|
+
* Those handlers are page JavaScript that can change the clone after the capture, so
|
|
155
|
+
* the accepted bytes may no longer describe the live page node for node; a pairing
|
|
156
|
+
* rebuilt by walking the two together would be describing the handler's tree. The
|
|
157
|
+
* inspection path stays for those pages.
|
|
158
|
+
*/
|
|
159
|
+
function hasAuthoredSaveHandlers(root) {
|
|
160
|
+
const visit = (el) => {
|
|
161
|
+
if (el.hasAttribute('onbeforesave') || el.hasAttribute('onbeforesnapshot')) return true;
|
|
162
|
+
const kids = el.localName === 'template' && el.content ? el.content : el;
|
|
163
|
+
for (const child of kids.children) if (visit(child)) return true;
|
|
164
|
+
return false;
|
|
165
|
+
};
|
|
166
|
+
return root.nodeType === 1 ? visit(root) : false;
|
|
167
|
+
}
|
|
168
|
+
|
|
77
169
|
function stripSnapshotRegions(clone) {
|
|
78
170
|
for (const el of clone.querySelectorAll(SNAPSHOT_REMOVE_SELECTOR)) el.remove();
|
|
79
171
|
}
|
|
@@ -103,6 +195,19 @@ function runAuthoredHandlers(clone, attr) {
|
|
|
103
195
|
}
|
|
104
196
|
}
|
|
105
197
|
|
|
198
|
+
let cloneCanonicalizer = null;
|
|
199
|
+
|
|
200
|
+
export function setCloneCanonicalizer(canonicalize) {
|
|
201
|
+
cloneCanonicalizer = canonicalize;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// A document parsed from received bytes gets the same canonical URLs a capture has, so a merge
|
|
205
|
+
// compares like with like. A no-op until save-core registers the canonicalizer.
|
|
206
|
+
export function canonicalizeReceived(doc) {
|
|
207
|
+
cloneCanonicalizer?.(doc);
|
|
208
|
+
return doc;
|
|
209
|
+
}
|
|
210
|
+
|
|
106
211
|
/**
|
|
107
212
|
* Register a hook that runs on EVERY snapshot (save AND sync).
|
|
108
213
|
* Use for: syncing form values to the clone.
|
|
@@ -190,14 +295,23 @@ function serializeClone(clone) {
|
|
|
190
295
|
* document reprinted; this one is the guarantee that no bug in an optional plugin can
|
|
191
296
|
* cost somebody a save.
|
|
192
297
|
*/
|
|
193
|
-
function serializeSaveClone(clone) {
|
|
298
|
+
function serializeSaveClone(clone, context = null) {
|
|
299
|
+
if (context && capturePageStateReader) {
|
|
300
|
+
let state = null;
|
|
301
|
+
try { state = capturePageStateReader(clone); } catch (_) {}
|
|
302
|
+
if (state) capturePageStates.set(context.captureId, state);
|
|
303
|
+
}
|
|
194
304
|
const today = serializeClone(clone);
|
|
195
305
|
if (!saveRenderer) return today;
|
|
306
|
+
const previous = activeCapture;
|
|
307
|
+
activeCapture = context;
|
|
196
308
|
try {
|
|
197
309
|
return saveRenderer(clone, today);
|
|
198
310
|
} catch (err) {
|
|
199
311
|
console.error('clayjs: save renderer failed, sending the full serialization', err);
|
|
200
312
|
return today;
|
|
313
|
+
} finally {
|
|
314
|
+
activeCapture = previous;
|
|
201
315
|
}
|
|
202
316
|
}
|
|
203
317
|
|
|
@@ -236,7 +350,7 @@ function clonePreventingOnclone(node, deep = true) {
|
|
|
236
350
|
finally { window.__preventOnclone = prev; }
|
|
237
351
|
}
|
|
238
352
|
|
|
239
|
-
export function captureSnapshot({ flushUndo = true, authored = true, forSave = false, requestRefresh = false } = {}) {
|
|
353
|
+
export function captureSnapshot({ flushUndo = true, authored = true, forSave = false, requestRefresh = false, context = null } = {}) {
|
|
240
354
|
// Force-close any pending undo idle batch BEFORE cloning the DOM, so the
|
|
241
355
|
// snapshot reflects a clean undo boundary. Without this, a save that fires
|
|
242
356
|
// mid-typing would leave the idle batch open across the save boundary, and
|
|
@@ -265,7 +379,14 @@ export function captureSnapshot({ flushUndo = true, authored = true, forSave = f
|
|
|
265
379
|
// authored handler sees the same URLs the file will.
|
|
266
380
|
restoreAuthoredUrls(clone);
|
|
267
381
|
|
|
382
|
+
cloneCanonicalizer?.(clone);
|
|
383
|
+
|
|
268
384
|
stripSnapshotRegions(clone);
|
|
385
|
+
// Whether the page authors its own save handlers, read here because this is the
|
|
386
|
+
// snapshot BEFORE either of them runs: a handler can add the other one, and it is the
|
|
387
|
+
// page as captured that decides whether the save can be paired without an inspection.
|
|
388
|
+
if (context) context.authoredHandlers = hasAuthoredSaveHandlers(clone);
|
|
389
|
+
|
|
269
390
|
// authored: false is for a capture that is not a save and not a frame the page will
|
|
270
391
|
// see. The attributes hold page-author JavaScript, so running them is a side effect
|
|
271
392
|
// the author asked for once per save, not once per capture.
|
|
@@ -479,12 +600,22 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
|
|
|
479
600
|
*
|
|
480
601
|
* @param {Object} options
|
|
481
602
|
* @param {boolean} options.emitForSync - Whether to emit snapshot-ready event (default: true)
|
|
603
|
+
* @param {boolean} options.forRequest - These bytes go to a host, so the renderer may
|
|
604
|
+
* attach the verified model they came from, for the accepted save to adopt
|
|
482
605
|
* @returns {{ forSave: string, forComparison: string, forDirty: string, captureId: number }}
|
|
483
606
|
*/
|
|
484
|
-
export function captureForSaveAndComparison({ emitForSync = true, flushUndo = true, requestRefresh = false } = {}) {
|
|
607
|
+
export function captureForSaveAndComparison({ emitForSync = true, flushUndo = true, requestRefresh = false, forRequest = false } = {}) {
|
|
485
608
|
if (!Number.isSafeInteger(captureCounter + 1)) throw new Error('Save capture identity exhausted');
|
|
486
609
|
const captureId = ++captureCounter;
|
|
487
|
-
|
|
610
|
+
// The context this capture's renderer may attach a private artifact to, and the
|
|
611
|
+
// record of whether the page has its own save handlers. It never reaches the object
|
|
612
|
+
// this function returns: that keeps its four public fields.
|
|
613
|
+
//
|
|
614
|
+
// Only for a capture whose bytes go to a host. A capture taken for anything else (a
|
|
615
|
+
// live-sync disk base, a download, an inspection) has no request to adopt it, so it
|
|
616
|
+
// pays neither the retained parse nor the pairing.
|
|
617
|
+
const context = forRequest ? { captureId, authoredHandlers: false, artifact: null } : null;
|
|
618
|
+
const clone = captureSnapshot({ flushUndo, forSave: emitForSync, requestRefresh, context });
|
|
488
619
|
|
|
489
620
|
// Emit for live-sync before any stripping.
|
|
490
621
|
//
|
|
@@ -525,7 +656,7 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
|
|
|
525
656
|
el.remove();
|
|
526
657
|
}
|
|
527
658
|
stripDiagnosticBeacons(clone);
|
|
528
|
-
const forSave = serializeSaveClone(clone);
|
|
659
|
+
const forSave = serializeSaveClone(clone, context);
|
|
529
660
|
|
|
530
661
|
// Compare clone: strip every autosave-off region, then run hooks
|
|
531
662
|
for (const el of compareClone.querySelectorAll(STRIP_FROM_COMPARISON)) {
|
|
@@ -552,7 +683,9 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
|
|
|
552
683
|
forDirty = serializeForCompare(dirtyClone);
|
|
553
684
|
}
|
|
554
685
|
|
|
555
|
-
|
|
686
|
+
const result = { forSave, forComparison, forDirty, captureId };
|
|
687
|
+
if (context && context.artifact) captureArtifacts.set(result, context.artifact);
|
|
688
|
+
return result;
|
|
556
689
|
}
|
|
557
690
|
|
|
558
691
|
/**
|
|
@@ -704,13 +837,28 @@ export function captureBodyForSync() {
|
|
|
704
837
|
}
|
|
705
838
|
|
|
706
839
|
/**
|
|
707
|
-
*
|
|
708
|
-
* Does NOT emit snapshot-ready event (safe for comparison).
|
|
840
|
+
* The bytes a save would send, without saving anything.
|
|
709
841
|
*
|
|
710
|
-
*
|
|
842
|
+
* Emits no snapshot-ready event (safe for comparison), and with no options it is
|
|
843
|
+
* exactly `captureForSave({ emitForSync: false })`.
|
|
844
|
+
*
|
|
845
|
+
* `transform(root)` edits the detached clone and nothing else, and runs before
|
|
846
|
+
* prepareCloneForSave so the authored hooks, the transforms and the [no-save] strip
|
|
847
|
+
* still have their say. It must be synchronous: a preview that finished later would
|
|
848
|
+
* describe a page that has moved on, so a thenable is refused rather than awaited.
|
|
849
|
+
* There is no global registration, so no other save ever sees these edits, and the
|
|
850
|
+
* renderer runs without a capture context, so nothing can adopt these bytes as a model.
|
|
711
851
|
*/
|
|
712
|
-
export function getPageContents() {
|
|
713
|
-
return captureForSave({ emitForSync: false });
|
|
852
|
+
export function getPageContents({ transform = null, flushUndo = true } = {}) {
|
|
853
|
+
if (!transform && flushUndo) return captureForSave({ emitForSync: false });
|
|
854
|
+
const clone = captureSnapshot({ flushUndo });
|
|
855
|
+
if (transform) {
|
|
856
|
+
const pending = transform(clone);
|
|
857
|
+
if (pending && typeof pending.then === 'function') {
|
|
858
|
+
throw new TypeError('getHTML: the transform must be synchronous');
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return serializeSaveClone(prepareCloneForSave(clone));
|
|
714
862
|
}
|
|
715
863
|
|
|
716
864
|
/**
|
package/src/core/source-map.js
CHANGED
|
@@ -63,6 +63,22 @@ export function model(src) {
|
|
|
63
63
|
sourceCodeLocationInfo: true,
|
|
64
64
|
onParseError: (e) => parseErrors.set(e.code, (parseErrors.get(e.code) || 0) + 1),
|
|
65
65
|
});
|
|
66
|
+
return modelFromParsed(src, doc, parseErrors);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The model for bytes that have already been parsed, by this module or by the verifier.
|
|
71
|
+
*
|
|
72
|
+
* The split exists so a verified render costs one parse instead of two: the verifier
|
|
73
|
+
* parses the bytes with locations to count the errors the render introduced, and that
|
|
74
|
+
* tree is the model of exactly the bytes it verified. Everything below the parse is
|
|
75
|
+
* here verbatim; nothing here assumes who called the parser.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} src - the bytes the tree was parsed from, which the ranges index
|
|
78
|
+
* @param {Object} doc - parse5 document, parsed with sourceCodeLocationInfo
|
|
79
|
+
* @param {Map} parseErrors - that parse's error counts, by code
|
|
80
|
+
*/
|
|
81
|
+
function modelFromParsed(src, doc, parseErrors) {
|
|
66
82
|
const html = doc.childNodes.find((n) => n.nodeName === 'html');
|
|
67
83
|
const outside = doc.childNodes.filter((n) => n !== html).map((n) => n.nodeName === '#documentType'
|
|
68
84
|
? { kind: 'doctype', name: n.name || '', publicId: n.publicId || '', systemId: n.systemId || '' }
|
|
@@ -296,11 +312,20 @@ function outsideOf(doc) {
|
|
|
296
312
|
|
|
297
313
|
const UNIT_SEP = '\u0000';
|
|
298
314
|
|
|
315
|
+
// The two steps pairCaptured exists to skip. Its whole claim is that a structurally
|
|
316
|
+
// identical capture needs neither a signature tree nor an alignment, and a test that
|
|
317
|
+
// only checks the resulting map cannot see the difference: hashing and aligning the same
|
|
318
|
+
// two identical trees produces the same map. Read by tests and by the save meter; the
|
|
319
|
+
// save path itself never reads it.
|
|
320
|
+
export const pairingCounts = { align: 0, hashKey: 0 };
|
|
321
|
+
export function resetPairingCounts() { pairingCounts.align = 0; pairingCounts.hashKey = 0; }
|
|
322
|
+
|
|
299
323
|
function mix(h, s) {
|
|
300
324
|
for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); }
|
|
301
325
|
return h >>> 0;
|
|
302
326
|
}
|
|
303
327
|
function hashKey(parts) {
|
|
328
|
+
pairingCounts.hashKey++;
|
|
304
329
|
let a = 2166136261, b = 5381;
|
|
305
330
|
for (const p of parts) { a = mix(a, p); b = (Math.imul(b, 33) ^ mix(a ^ 0x9e3779b9, p)) >>> 0; }
|
|
306
331
|
return a.toString(36) + '.' + b.toString(36);
|
|
@@ -356,6 +381,7 @@ const DP_CELLS = 4096;
|
|
|
356
381
|
* 4.7 ms, and 32,000 siblings in 15.5 ms.
|
|
357
382
|
*/
|
|
358
383
|
export function align(LK, SK) {
|
|
384
|
+
pairingCounts.align++;
|
|
359
385
|
const pairs = [];
|
|
360
386
|
const LI = LK.map((_, i) => i), SI = SK.map((_, j) => j);
|
|
361
387
|
stage(LI, SI, 0);
|
|
@@ -500,6 +526,84 @@ function bind(node, loc, map, keyMap, stats, path, resolve, gen) {
|
|
|
500
526
|
for (let j = 0; j < S.length; j++) if (!usedS.has(j)) stats.unmatchedSource.push({ path, node: describeLoc(S[j]) });
|
|
501
527
|
}
|
|
502
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Pair a capture against the model built from the bytes that capture verified as.
|
|
531
|
+
*
|
|
532
|
+
* The map has to exist either way: it is what says where the live page is in the file.
|
|
533
|
+
* What this skips is the SEARCH for it. `pair` computes four signature keys per node and
|
|
534
|
+
* aligns child lists by them, because it is handed a page and a file that may have
|
|
535
|
+
* diverged arbitrarily. A verified capture has not diverged: the verifier compared the
|
|
536
|
+
* captured tree against today's bytes, node for node, so the model of those bytes has the
|
|
537
|
+
* captured tree, in order, with the same children. Walking the two together is the whole
|
|
538
|
+
* pairing.
|
|
539
|
+
*
|
|
540
|
+
* Refusing matters more than succeeding. Anything that differs returns null and the caller
|
|
541
|
+
* takes `pair`; a walk that guessed instead would pair one tree's locations to another
|
|
542
|
+
* tree's nodes and print the wrong bytes into the file. There is no partial result here.
|
|
543
|
+
*
|
|
544
|
+
* @param {Node} root - the prepared save clone's document element
|
|
545
|
+
* @param {Object} m - the model of the bytes this clone was verified to render as
|
|
546
|
+
* @param {Function} [provenance] - clone node -> the identity to key the map by
|
|
547
|
+
* @returns {?{map: WeakMap, stats: Object}} null when the trees are not the same shape
|
|
548
|
+
*/
|
|
549
|
+
export function pairCaptured(root, m, provenance = (n) => n) {
|
|
550
|
+
if (!m.root) return null;
|
|
551
|
+
const found = [];
|
|
552
|
+
const unresolved = matchCaptured(root, m.root, provenance, found);
|
|
553
|
+
if (unresolved < 0) return null;
|
|
554
|
+
const map = new WeakMap();
|
|
555
|
+
// One token for this pass, set on the model and on every bound loc, exactly as `pair`
|
|
556
|
+
// does it: render slices a loc only when the two still agree.
|
|
557
|
+
const gen = {};
|
|
558
|
+
m.gen = gen;
|
|
559
|
+
let paired = 0;
|
|
560
|
+
// A clone node a transform created has no live original and stays unpaired, so it is
|
|
561
|
+
// printed: the same thing `pair` does with it.
|
|
562
|
+
for (const [key, loc] of found) if (key) { map.set(key, loc); loc.gen = gen; paired++; }
|
|
563
|
+
return {
|
|
564
|
+
map,
|
|
565
|
+
stats: { paired, unmatchedLive: [], unmatchedSource: [], attrDiffs: [], textDiffs: 0, unresolved },
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** @returns {number} unresolved nodes below this pair, or -1 when the two differ at all */
|
|
570
|
+
function matchCaptured(node, loc, provenance, found) {
|
|
571
|
+
if (loc.kind === 'text') {
|
|
572
|
+
if (node.nodeType !== 3 || node.data !== loc.value) return -1;
|
|
573
|
+
} else if (loc.kind === 'comment') {
|
|
574
|
+
if (node.nodeType !== 8 || node.data !== loc.value) return -1;
|
|
575
|
+
} else {
|
|
576
|
+
if (node.nodeType !== 1 || node.localName !== loc.tag || node.namespaceURI !== loc.node.namespaceURI) return -1;
|
|
577
|
+
if (capturedAttrs(node) !== capturedLocAttrs(loc.node)) return -1;
|
|
578
|
+
}
|
|
579
|
+
const key = provenance(node);
|
|
580
|
+
found.push([key, loc]);
|
|
581
|
+
let unresolved = key ? 0 : 1;
|
|
582
|
+
if (loc.kind !== 'element') return unresolved;
|
|
583
|
+
const L = capturedKids(node), S = loc.children;
|
|
584
|
+
if (L.length !== S.length) return -1;
|
|
585
|
+
for (let i = 0; i < L.length; i++) {
|
|
586
|
+
const n = matchCaptured(L[i], S[i], provenance, found);
|
|
587
|
+
if (n < 0) return -1;
|
|
588
|
+
unresolved += n;
|
|
589
|
+
}
|
|
590
|
+
return unresolved;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const attrKey = (a) => JSON.stringify(a);
|
|
594
|
+
/** Qualified name, namespace and value, sorted: two attribute sets are equal as sets. */
|
|
595
|
+
const capturedAttrs = (el) => Array.from(el.attributes, (a) => attrKey([a.namespaceURI || '', a.name, a.value])).sort().join(UNIT_SEP);
|
|
596
|
+
// parse5 adjusts a foreign attribute in the tree (`xlink:href` is `{ prefix: 'xlink', name: 'href' }`)
|
|
597
|
+
// and keeps the location map keyed by the name as written, so the qualified name is rebuilt here.
|
|
598
|
+
const capturedLocAttrs = (node) => node.attrs.map((a) => attrKey([a.namespace || '', a.prefix ? a.prefix + ':' + a.name : a.name, a.value])).sort().join(UNIT_SEP);
|
|
599
|
+
|
|
600
|
+
function capturedKids(el) {
|
|
601
|
+
const list = el.nodeType === 1 && el.tagName === 'TEMPLATE' && el.content ? el.content.childNodes : el.childNodes;
|
|
602
|
+
const out = [];
|
|
603
|
+
for (const n of list) if (n.nodeType === 1 || n.nodeType === 3 || n.nodeType === 8) out.push(n);
|
|
604
|
+
return out;
|
|
605
|
+
}
|
|
606
|
+
|
|
503
607
|
// =============================================================================
|
|
504
608
|
// RENDER
|
|
505
609
|
// =============================================================================
|
|
@@ -923,6 +1027,27 @@ function lis(vals) {
|
|
|
923
1027
|
// the right one.
|
|
924
1028
|
|
|
925
1029
|
export function verify(rendered, today, liveDocument, clone = null, sourceErrors = null) {
|
|
1030
|
+
return verifyRendered(rendered, today, liveDocument, clone, sourceErrors, false);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* verify, keeping the parse it already paid for.
|
|
1035
|
+
*
|
|
1036
|
+
* The parse-error check below is a parse5 parse of the rendered bytes, and it is thrown
|
|
1037
|
+
* away today: an accepted save then parses the accepted bytes a second time to model them.
|
|
1038
|
+
* This returns the same verdict with that tree as `model`, so the save that verified can
|
|
1039
|
+
* be modelled from the tree that verified it. `model` is present only when the verdict is
|
|
1040
|
+
* ok AND the candidate is still this document (checkSource), which is what a caller needs
|
|
1041
|
+
* before the candidate may be adopted; a fallback keeps today's path.
|
|
1042
|
+
*
|
|
1043
|
+
* A renderer must not verify its own output, so this is not a second verifier: it is the
|
|
1044
|
+
* same walk, told to retain what it parsed.
|
|
1045
|
+
*/
|
|
1046
|
+
export function verifyForSave(rendered, today, liveDocument, clone = null, sourceErrors = null) {
|
|
1047
|
+
return verifyRendered(rendered, today, liveDocument, clone, sourceErrors, true);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function verifyRendered(rendered, today, liveDocument, clone, sourceErrors, retain) {
|
|
926
1051
|
const P = new DOMParser();
|
|
927
1052
|
const A = P.parseFromString(rendered, 'text/html');
|
|
928
1053
|
const outsideA = vOutside(A), outsideLive = vOutside(liveDocument);
|
|
@@ -937,14 +1062,20 @@ export function verify(rendered, today, liveDocument, clone = null, sourceErrors
|
|
|
937
1062
|
at = [];
|
|
938
1063
|
cloneDiff = vDiff(A.documentElement, clone, 'html', at);
|
|
939
1064
|
if (!cloneDiff) {
|
|
940
|
-
const w = vParseErrors(rendered, sourceErrors);
|
|
941
|
-
|
|
1065
|
+
const w = vParseErrors(rendered, sourceErrors, retain);
|
|
1066
|
+
if (w.diff) return { ok: false, diff: w.diff };
|
|
1067
|
+
return retain ? withCandidate({ ok: true, diff: null, oracle: 'clone' }, w, rendered, liveDocument)
|
|
1068
|
+
: { ok: true, diff: null, oracle: 'clone' };
|
|
942
1069
|
}
|
|
943
1070
|
}
|
|
944
1071
|
const B = P.parseFromString(today, 'text/html');
|
|
945
1072
|
const diff = vDiff(A.documentElement, B.documentElement, 'html');
|
|
946
|
-
const worse = diff ? null : vParseErrors(rendered, sourceErrors);
|
|
947
|
-
if (!diff)
|
|
1073
|
+
const worse = diff ? null : vParseErrors(rendered, sourceErrors, retain);
|
|
1074
|
+
if (!diff) {
|
|
1075
|
+
if (worse.diff) return { ok: false, diff: worse.diff };
|
|
1076
|
+
return retain ? withCandidate({ ok: true, diff: null, oracle: 'today' }, worse, rendered, liveDocument)
|
|
1077
|
+
: { ok: true, diff: null, oracle: 'today' };
|
|
1078
|
+
}
|
|
948
1079
|
// `at` is the index path, in the clone, of the node where the render first differs,
|
|
949
1080
|
// so a caller can print that element and try again instead of discarding the whole
|
|
950
1081
|
// render. It is valid in the clone because every earlier sibling at every level on
|
|
@@ -983,15 +1114,32 @@ export function nodeAt(root, at) {
|
|
|
983
1114
|
* represent, such as a duplicated element, change the tree, and the comparison above
|
|
984
1115
|
* is what catches those.
|
|
985
1116
|
*/
|
|
986
|
-
function vParseErrors(rendered, sourceErrors) {
|
|
987
|
-
if (!sourceErrors) return null;
|
|
1117
|
+
function vParseErrors(rendered, sourceErrors, retain) {
|
|
1118
|
+
if (!sourceErrors) return { diff: null };
|
|
988
1119
|
const seen = new Map();
|
|
989
|
-
|
|
1120
|
+
const doc = parse(rendered, retain
|
|
1121
|
+
? { sourceCodeLocationInfo: true, onParseError: (e) => seen.set(e.code, (seen.get(e.code) || 0) + 1) }
|
|
1122
|
+
: { onParseError: (e) => seen.set(e.code, (seen.get(e.code) || 0) + 1) });
|
|
990
1123
|
for (const [code, n] of seen) {
|
|
991
1124
|
const was = sourceErrors.get(code) || 0;
|
|
992
|
-
if (n > was) return `the render introduced parse errors the source does not have: ${code} ${was} -> ${n}
|
|
1125
|
+
if (n > was) return { diff: `the render introduced parse errors the source does not have: ${code} ${was} -> ${n}` };
|
|
993
1126
|
}
|
|
994
|
-
return null;
|
|
1127
|
+
return retain ? { diff: null, doc, parseErrors: seen } : { diff: null };
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Attach the model of the bytes just verified, or leave the verdict as it is.
|
|
1132
|
+
*
|
|
1133
|
+
* checkSource first: the tree comparison cannot see anything outside <html>, and a model
|
|
1134
|
+
* that fails it is not this document no matter what the render verified as. A failed check
|
|
1135
|
+
* is not a refusal of the save; the caller keeps the fallback path it has today.
|
|
1136
|
+
*/
|
|
1137
|
+
function withCandidate(verdict, errors, rendered, liveDocument) {
|
|
1138
|
+
if (!errors.doc) return verdict;
|
|
1139
|
+
const candidate = modelFromParsed(rendered, errors.doc, errors.parseErrors);
|
|
1140
|
+
if (checkSource(candidate, liveDocument)) return verdict;
|
|
1141
|
+
verdict.model = candidate;
|
|
1142
|
+
return verdict;
|
|
995
1143
|
}
|
|
996
1144
|
function vOutside(doc) {
|
|
997
1145
|
const out = [];
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
import { isEditMode, isDraftMode } from "./is-edit-mode.js";
|
|
19
19
|
import { draftStoreFailed, flushDraft } from "./draft-mode.js";
|
|
20
20
|
import { captureForDirtyCheck } from "./snapshot.js";
|
|
21
|
-
import { getLastSavedDirty } from "./save.js";
|
|
22
|
-
import { logUnloadDiffSync, preloadIfEnabled } from "../lib/autosave-debug.js";
|
|
21
|
+
import { getLastSavedDirty, sessionParked, styleAdoptionPending } from "./save.js";
|
|
22
|
+
import { logUnloadDiffSync, preloadIfEnabled } from "../lib/autosave-debug-gate.js";
|
|
23
23
|
import { hasUnsavedState } from "../lib/unsaved-state.js";
|
|
24
24
|
|
|
25
25
|
// One beforeunload let through, for the reload a person confirmed with two presses
|
|
@@ -48,6 +48,10 @@ preloadIfEnabled();
|
|
|
48
48
|
// The same predicate is exposed as `clay.hasUnsavedChanges`, which a host asks
|
|
49
49
|
// synchronously before deciding whether a save is worth asking for.
|
|
50
50
|
export function hasUnsavedChanges() {
|
|
51
|
+
// Bytes a save session parked before its exit were never written, and whatever mode the
|
|
52
|
+
// page reports by then, they are still only here. Asked first, because the mode is a
|
|
53
|
+
// claim about the page and this is a claim about the host.
|
|
54
|
+
if (sessionParked()) return true;
|
|
51
55
|
if (!isEditMode) return false;
|
|
52
56
|
|
|
53
57
|
// Work outside the DOM first: it needs no capture, and a capture that throws
|
|
@@ -72,14 +76,20 @@ export function hasUnsavedChanges() {
|
|
|
72
76
|
export function bytesUnsaved() {
|
|
73
77
|
if (!isEditMode) return false;
|
|
74
78
|
try {
|
|
75
|
-
|
|
79
|
+
const current = captureForDirtyCheck();
|
|
80
|
+
// A style adoption is not work: the page is only waiting for the stripper that
|
|
81
|
+
// takes its own owned tag out of the stored bytes. The capture above still runs,
|
|
82
|
+
// so a throw is still an unsaved page, and any edit since the adoption was
|
|
83
|
+
// queued turns this off.
|
|
84
|
+
if (styleAdoptionPending(current)) return false;
|
|
85
|
+
return current !== getLastSavedDirty();
|
|
76
86
|
} catch {
|
|
77
87
|
return true;
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
window.addEventListener('beforeunload', (event) => {
|
|
82
|
-
if (!isEditMode) return;
|
|
92
|
+
if (!isEditMode && !sessionParked()) return;
|
|
83
93
|
|
|
84
94
|
if (discardPermit) {
|
|
85
95
|
discardPermit = false;
|