@panphora/clayjs 1.16.0 → 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/dist/all.js +1 -1
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +7917 -5621
- 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-2YYDMLNZ.js +1 -0
- package/dist/m/c-336SGU5W.js +1 -0
- package/dist/m/c-3H6BZDNJ.js +6 -0
- package/dist/m/{c-LR4CGEPT.js → c-3RUU4TPX.js} +1 -1
- package/dist/m/c-3XIRD4NU.js +1 -0
- package/dist/m/c-4DFEJ2PO.js +3 -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-6AZOGAPE.js +1 -0
- package/dist/m/{c-Y6USGTS5.js → c-6CRUUXOX.js} +5 -5
- package/dist/m/{c-54RCDCBU.js → c-6KEVEQ7L.js} +1 -1
- package/dist/m/{c-LTGMXXKO.js → c-6NC5I2TV.js} +1 -1
- package/dist/m/{c-YDH7Q3NY.js → c-74MGYWZF.js} +1 -1
- package/dist/m/{c-Z36J63PP.js → c-7OLLJPWP.js} +1 -1
- package/dist/m/c-A3E3NIMC.js +1 -0
- package/dist/m/{c-5R4PTRG2.js → c-AMSNPOCX.js} +1 -1
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/c-BQ6LOBEE.js +1 -0
- package/dist/m/c-CJ3A7ZKE.js +1 -0
- package/dist/m/c-CQHPGKX2.js +1 -0
- package/dist/m/c-DCXOXBF3.js +1 -0
- package/dist/m/c-DFS4XZJV.js +2 -0
- package/dist/m/{c-F3CKJTJS.js → c-DNENWY4O.js} +1 -1
- package/dist/m/c-E4LIR5X5.js +1 -0
- package/dist/m/c-EVCQ3BKA.js +1 -0
- package/dist/m/{c-5Y6TNX5W.js → c-EZU56NQV.js} +2 -2
- package/dist/m/c-FDNHHZY5.js +1 -0
- package/dist/m/{c-SWRJFCNN.js → c-FRLHSY7E.js} +1 -1
- 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-GB3JTV6E.js → c-GFJJEBTU.js} +1 -1
- package/dist/m/{c-J7CA54IP.js → c-GSFIGKOP.js} +1 -1
- package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/{c-33HUYEDI.js → c-HDDEEL77.js} +1 -1
- package/dist/m/c-HFKUVACH.js +1 -0
- package/dist/m/c-HHDFCNHV.js +1 -0
- package/dist/m/c-I2ZATQCD.js +2 -0
- package/dist/m/c-IEM4NTCS.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/{c-SGCCC4FW.js → c-JDCX26UP.js} +2 -2
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-JU3NK25H.js +1 -0
- package/dist/m/{c-VFX45IZQ.js → c-KAU6E43D.js} +1 -1
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGVGROZT.js +1 -0
- package/dist/m/{c-YG2NJYS6.js → c-KQ6TYJAP.js} +2 -2
- package/dist/m/{c-2JL6E75N.js → c-KU3ZFADW.js} +1 -1
- package/dist/m/c-LWLBHLVC.js +3 -0
- package/dist/m/c-M2L47S2X.js +7 -0
- package/dist/m/c-MFP2ZWD4.js +1 -0
- package/dist/m/{c-4YSCRICO.js → c-MKSQ4Y57.js} +1 -1
- package/dist/m/{c-2M73252O.js → c-NUFFJVVJ.js} +1 -1
- package/dist/m/c-NUHSPT5P.js +1 -0
- package/dist/m/c-O72HVYSA.js +1 -0
- package/dist/m/c-OW5QIGG3.js +1 -0
- package/dist/m/c-PGRCK73F.js +1 -0
- package/dist/m/{c-4DRMS4QG.js → c-PS5ARVWR.js} +1 -1
- 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-TQZ2JYHD.js → c-QICP2YR5.js} +1 -1
- package/dist/m/{c-7VGZ7RU5.js → c-QJ4OM3G6.js} +1 -1
- package/dist/m/{c-MMUIUUZG.js → c-R3L7ZJGB.js} +1 -1
- package/dist/m/c-R7IWVINQ.js +1 -0
- package/dist/m/{c-HVYXIS72.js → c-RSMOODDU.js} +1 -1
- package/dist/m/{c-KKWLDOPM.js → c-RYUXUUBX.js} +1 -1
- package/dist/m/c-S4ZEH46B.js +9 -0
- package/dist/m/c-SDAXDM2D.js +1 -0
- package/dist/m/c-SMBZG5X4.js +1 -0
- package/dist/m/{c-UXBMRDZB.js → c-SMGU7RT3.js} +1 -1
- 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-TCJTVAFN.js +1 -0
- package/dist/m/{c-HRDCD3WN.js → c-TJ6WOJL3.js} +1 -1
- package/dist/m/c-TYLXX4NF.js +21 -0
- package/dist/m/c-U62OB4BT.js +1 -0
- package/dist/m/{c-NMJRXKPC.js → c-U6YWUAR3.js} +1 -1
- package/dist/m/c-UDCSU5G5.js +2 -0
- package/dist/m/{c-NZMNXVF6.js → c-UDUUJPFZ.js} +1 -1
- 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-VI5RMSVY.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-WWXIDH4O.js +28 -0
- package/dist/m/{c-QIX5BJYN.js → c-XCNTK76U.js} +1 -1
- package/dist/m/c-XMS6DPBR.js +1 -0
- package/dist/m/c-Y5HBBIQ4.js +1 -0
- package/dist/m/{c-5M4YEKCX.js → c-ZVPVXTJF.js} +1 -1
- package/dist/m/c-ZVRKXKM7.js +1 -0
- package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
- 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/host-permission-FNXVEFNE.js +1 -0
- package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
- package/dist/m/core/{persist-HKHA66IY.js → persist-JFYRHMRJ.js} +1 -1
- 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/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/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 +1555 -1058
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/{editor-XUTB2HB2.js → editor-NNJA4OKD.js} +21 -8
- package/dist/m/plugins/{ai-edit-ZQ4CGWIP.js → ai-edit-QN4PMMNL.js} +1 -1
- package/dist/m/plugins/demo-RM5IR6AO.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-HCKO3DSP.js → menu-3HIEQSI6.js} +2 -2
- 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/{richclay-RC3L2F6X.js → richclay-NRQDSWX6.js} +2 -2
- 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/{upgrade-ZNYODP5Y.js → upgrade-W7FG5VFG.js} +1 -1
- 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-ILWLXVMP.js → reader-core-DXYCURFF.js} +2 -2
- package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
- package/dist/m/ui/{index-EVHKMKBL.js → index-3JLVI27O.js} +1 -1
- package/dist/offline-manifest.json +857 -623
- package/dist/page/themes/light-1.css +25 -1
- package/dist/server/core.cjs +115 -24
- package/package.json +1 -1
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +3 -29
- package/src/core/download.js +32 -0
- package/src/core/host-meta.js +18 -0
- package/src/core/save-core.js +8 -4
- package/src/core/save.js +4 -4
- package/src/core/snapshot.js +39 -1
- package/src/lib/autosave-scheduler.js +3 -4
- package/src/page/blocks.js +34 -1
- package/src/page/board-views.js +2 -16
- package/src/page/chart-tables.js +11 -0
- package/src/page/chart.js +6 -10
- package/src/page/checkpoint-proof.js +289 -0
- package/src/page/chrome.js +24 -6
- package/src/page/document-title-dom.js +12 -0
- package/src/page/document-title-runtime.js +15 -1
- package/src/page/document-title.js +4 -1
- package/src/page/editor.js +77 -12
- package/src/page/emoji-data.js +162 -0
- package/src/page/features/board.js +12 -3
- package/src/page/features/chart.js +11 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +6 -1
- package/src/page/features/table.js +2 -2
- package/src/page/format-command.js +9 -0
- package/src/page/format-host.js +54 -0
- package/src/page/format-native-commands.js +142 -0
- package/src/page/format-native.js +42 -0
- package/src/page/inline.js +32 -8
- package/src/page/items.js +15 -3
- package/src/page/markdown.js +26 -2
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/projection.js +58 -15
- package/src/page/save-projection.js +39 -0
- package/src/page/save-states.js +1 -1
- package/src/page/schema.js +33 -0
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +25 -1
- package/src/page/view-hooks.js +32 -0
- package/src/page/ybind/binding.js +31 -4
- package/src/page/ybind/marks.js +2 -1
- package/src/page/ybind/provider.js +13 -2
- package/src/page/ybind/recovery-replay.js +362 -4
- package/src/plugins/live.js +37 -1
- package/src/plugins/page.js +117 -3
- package/src/plugins/visitor-drafts/intro.js +2 -1
- package/src/server/checkpoint/index.js +2 -2
- package/src/sync/saved-stream.js +17 -8
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
- package/dist/m/c-35NBGMZB.js +0 -1
- package/dist/m/c-3IQRECYS.js +0 -1
- package/dist/m/c-4B6H4G5V.js +0 -1
- package/dist/m/c-4FFHZM32.js +0 -1
- package/dist/m/c-66O32MV3.js +0 -28
- package/dist/m/c-7O6AFR65.js +0 -1
- package/dist/m/c-ANQTJ3B5.js +0 -2
- package/dist/m/c-AYVOREBE.js +0 -1
- package/dist/m/c-B2ZXPOS3.js +0 -3
- package/dist/m/c-BMIZKMPV.js +0 -1
- package/dist/m/c-CJZSLMVQ.js +0 -1
- package/dist/m/c-DBE6ORSI.js +0 -1
- package/dist/m/c-EDHUR4MX.js +0 -1
- package/dist/m/c-EY6UOM54.js +0 -1
- package/dist/m/c-F7Y6FLPY.js +0 -1
- package/dist/m/c-FA4DZVTC.js +0 -1
- package/dist/m/c-G2BUZITR.js +0 -1
- package/dist/m/c-GBGWXTX4.js +0 -1
- package/dist/m/c-GHBCCVG4.js +0 -1
- package/dist/m/c-IB52R237.js +0 -1
- package/dist/m/c-IKWNE4R4.js +0 -1
- package/dist/m/c-IU3IYEU6.js +0 -1
- package/dist/m/c-IZ2EBRAJ.js +0 -1
- package/dist/m/c-KR4UW4KW.js +0 -1
- package/dist/m/c-KY3VJGUA.js +0 -8
- package/dist/m/c-LXENEBV7.js +0 -1
- package/dist/m/c-PPOSCAQD.js +0 -1
- package/dist/m/c-QQHEX6HA.js +0 -1
- package/dist/m/c-R7RMANJI.js +0 -9
- package/dist/m/c-RI4NT75Y.js +0 -1
- package/dist/m/c-RUJKSUQW.js +0 -1
- package/dist/m/c-T4VO4OCB.js +0 -1
- package/dist/m/c-TR6RMDQ7.js +0 -3
- package/dist/m/c-UKHTLWUW.js +0 -1
- package/dist/m/c-UNN7LWCT.js +0 -1
- package/dist/m/c-V7PZZRG7.js +0 -1
- package/dist/m/c-VL52ADDR.js +0 -1
- package/dist/m/c-WWDBYXLK.js +0 -1
- package/dist/m/c-XNNW2CWK.js +0 -1
- package/dist/m/c-Z4VRC7GU.js +0 -7
- package/dist/m/c-Z6K52OLP.js +0 -19
- package/dist/m/c-ZDY2MDAA.js +0 -21
- package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
- package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
- package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
- package/dist/m/core/recovery-TBBSICE2.js +0 -1
- package/dist/m/core/save-WUCHTQBE.js +0 -1
- package/dist/m/core/save-core-FJVHT5M3.js +0 -1
- package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
- package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
- package/dist/m/events/index-23DPTUT4.js +0 -1
- package/dist/m/internals/index-WOVAP3RU.js +0 -1
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
- package/dist/m/loader-HXX6YNH5.js +0 -1
- package/dist/m/manifest-F22DEDAC.js +0 -1
- package/dist/m/manifest-edit-C3C7376B.js +0 -1
- package/dist/m/options/options-2LH76JQ3.js +0 -1
- package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
- package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
- package/dist/m/plugins/live-NUTTXSBS.js +0 -1
- package/dist/m/plugins/page-KSUH7G6J.js +0 -2
- package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
- package/dist/m/plugins/people-AJJ32YCG.js +0 -1
- package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
- package/dist/m/plugins/source-KHK5UUSK.js +0 -1
- package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
- package/dist/m/plugins/upload-XSGNILUY.js +0 -1
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
- package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
- package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
- /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-NRQDSWX6.js.LEGAL.txt} +0 -0
package/src/page/inline.js
CHANGED
|
@@ -14,28 +14,41 @@
|
|
|
14
14
|
// encodeForMerge (replay.js) gives each one a code of its own.
|
|
15
15
|
// A mark this schema has no type for keeps its token or named descriptor
|
|
16
16
|
// instead of throwing (format-codec.js): the unit's key is written back exactly
|
|
17
|
-
// as it was read.
|
|
17
|
+
// as it was read. A mark of this schema with no compact code of its own is
|
|
18
|
+
// written as a named descriptor too, x[name, attrs], so a new mark never takes a
|
|
19
|
+
// letter an older writer already reads.
|
|
18
20
|
import { model } from "../vendor/prosemirror.vendor.js";
|
|
19
21
|
import { descriptors, entriesOf, isOpaque, keyOf, opaqueMark } from "./format-codec.js";
|
|
22
|
+
import { nativeAttrs } from "./format-native.js";
|
|
20
23
|
|
|
21
24
|
export const ATOM = "";
|
|
22
25
|
export const CODES = { strong: "b", em: "i", strike: "s", code: "c", link: "a" };
|
|
26
|
+
const NATIVE = new Set(["underline", "text_color", "highlight"]);
|
|
23
27
|
const NAMES = Object.fromEntries(Object.entries(CODES).map(([name, code]) => [code, name]));
|
|
24
28
|
|
|
25
29
|
export function markKey(marks) {
|
|
26
30
|
let key = "";
|
|
31
|
+
const named = [];
|
|
27
32
|
for (const mark of marks) {
|
|
28
33
|
if (isOpaque(mark)) {
|
|
29
34
|
key += keyOf(mark.attrs.entries);
|
|
30
35
|
continue;
|
|
31
36
|
}
|
|
32
37
|
const code = CODES[mark.type.name];
|
|
33
|
-
if (!code)
|
|
38
|
+
if (!code) {
|
|
39
|
+
// A mark with no compact letter travels as a named descriptor, after the codes
|
|
40
|
+
// and in the codec's order: one of the four formats only with attrs it writes,
|
|
41
|
+
// any other (a mention, a date) with its own attrs.
|
|
42
|
+
const native = nativeAttrs(mark.type.name, mark.attrs);
|
|
43
|
+
if (!native && NATIVE.has(mark.type.name)) throw new TypeError(`page: the mark ${mark.type.name} has no key code`);
|
|
44
|
+
named.push({ name: mark.type.name, attrs: native ?? mark.attrs });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
34
47
|
key += code;
|
|
35
48
|
const names = Object.keys(mark.type.attrs);
|
|
36
49
|
if (names.length) key += JSON.stringify(names.map((name) => mark.attrs[name]));
|
|
37
50
|
}
|
|
38
|
-
return key;
|
|
51
|
+
return named.length ? key + keyOf(named) : key;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
const parsed = new WeakMap();
|
|
@@ -49,15 +62,26 @@ export function marksOf(schema, key) {
|
|
|
49
62
|
marks = [];
|
|
50
63
|
const opaque = [];
|
|
51
64
|
for (const desc of descriptors(key)) {
|
|
52
|
-
|
|
65
|
+
// A named descriptor this schema does have a type for is that mark, not an opaque
|
|
66
|
+
// entry: one of the four formats only with attrs it writes, any other mark only
|
|
67
|
+
// with attributes its type declares. Anything else stays opaque, so nothing is lost.
|
|
68
|
+
if (desc.code === undefined) {
|
|
69
|
+
const native = nativeAttrs(desc.name, desc.attrs);
|
|
70
|
+
const type = schema.marks[desc.name];
|
|
71
|
+
if (native) marks = type.create(native).addToSet(marks);
|
|
72
|
+
else if (type && !NATIVE.has(desc.name) && Object.keys(desc.attrs ?? {}).every((field) => Object.hasOwn(type.attrs, field))) marks = type.create(desc.attrs).addToSet(marks);
|
|
73
|
+
else opaque.push(desc);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const name = NAMES[desc.code];
|
|
53
77
|
if (!name) {
|
|
54
78
|
opaque.push(desc);
|
|
55
79
|
continue;
|
|
56
80
|
}
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
-
const attrs = desc.attrs ? Object.fromEntries(Object.keys(
|
|
60
|
-
marks =
|
|
81
|
+
const coded = schema.marks[name];
|
|
82
|
+
if (!coded) throw new TypeError(`page: unknown mark code ${desc.code} in ${key}`);
|
|
83
|
+
const attrs = desc.attrs ? Object.fromEntries(Object.keys(coded.attrs).map((field, j) => [field, desc.attrs[j]])) : null;
|
|
84
|
+
marks = coded.create(attrs).addToSet(marks);
|
|
61
85
|
}
|
|
62
86
|
if (opaque.length) marks = opaqueMark(entriesOf(opaque)).addToSet(marks);
|
|
63
87
|
cache.set(key, marks);
|
package/src/page/items.js
CHANGED
|
@@ -12,7 +12,8 @@ import { pickFiles, hostPlace } from "./media.js";
|
|
|
12
12
|
import { makeTable, insertTableNode } from "./table.js";
|
|
13
13
|
import { pickCsv } from "./csv-import.js";
|
|
14
14
|
import { makeBoard, insertBoardNode } from "./board.js";
|
|
15
|
-
import {
|
|
15
|
+
import { chartTables } from "./chart-tables.js";
|
|
16
|
+
import { viewFeatures } from "./view-hooks.js";
|
|
16
17
|
|
|
17
18
|
const { TextSelection } = state;
|
|
18
19
|
const N = schema.nodes;
|
|
@@ -27,6 +28,7 @@ export const BLOCK_TYPES = [
|
|
|
27
28
|
{ key: "check", label: "Checklist", description: "Tasks to tick off", icon: "☐", type: "list_item", attrs: { kind: "check" }, hint: "[]", aliases: ["todo", "to-do", "task", "check", "checkbox"] },
|
|
28
29
|
{ key: "toggle", label: "Toggle", description: "Hide details under a line", icon: "▸", type: "toggle", attrs: { open: true }, hint: ">", aliases: ["collapse", "details", "fold"] },
|
|
29
30
|
{ key: "callout", label: "Callout", description: "Make a note stand out", icon: "!", type: "callout", aliases: ["note", "aside", "info"] },
|
|
31
|
+
{ key: "quote", label: "Quote", description: "Set text apart as a quotation", icon: "\u201c", type: "quote", hint: "\"", aliases: ["blockquote", "quotation", "cite"] },
|
|
30
32
|
{ key: "code", label: "Code", description: "Code with highlighting", icon: "</>", type: "code_block", hint: "```", aliases: ["pre", "snippet"] },
|
|
31
33
|
];
|
|
32
34
|
|
|
@@ -41,6 +43,10 @@ export const INSERT_ITEMS = [
|
|
|
41
43
|
|
|
42
44
|
export const CHART_ITEM = { key: "chart", label: "Chart", description: "A bar or line chart from a table", icon: "📊", aliases: ["graph", "plot", "bar", "line"], insert: "chart" };
|
|
43
45
|
|
|
46
|
+
// The chart module and its view part load together, before the block draws: a
|
|
47
|
+
// document that never takes a chart never fetches either.
|
|
48
|
+
export const loadChartPart = (view) => (viewFeatures.get(view)?.ensure("chart") ?? Promise.resolve()).catch(() => {}).then(() => import("./chart.js"));
|
|
49
|
+
|
|
44
50
|
export const MENU_ITEMS = [...BLOCK_TYPES, ...INSERT_ITEMS];
|
|
45
51
|
|
|
46
52
|
export const insertItemsFor = (st, items = MENU_ITEMS) => (chartTables(st.doc).length ? [...items, CHART_ITEM] : items);
|
|
@@ -176,14 +182,20 @@ export function rankItems(items, query) {
|
|
|
176
182
|
|
|
177
183
|
// What a pick does in the insert menu for the block at `index`. A divider and
|
|
178
184
|
// the file pickers have their own placement; a block type retypes an empty line
|
|
179
|
-
// and otherwise goes into a new block below the line's whole subtree.
|
|
185
|
+
// and otherwise goes into a new block below the line's whole subtree. A block
|
|
186
|
+
// type from a later format asks the format gate before it writes anything.
|
|
180
187
|
export function runItem(view, item, index, remove = null) {
|
|
188
|
+
if (item.type !== "quote") return insertItem(view, item, index, remove);
|
|
189
|
+
return import("./format-native-commands.js").then(({ runFormat }) => runFormat(view, () => insertItem(view, item, index, remove)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function insertItem(view, item, index, remove = null) {
|
|
181
193
|
const st = view.state;
|
|
182
194
|
if (item.insert === "image" || item.insert === "file") return pickFiles(view, item.insert === "image" ? "image/*" : "", hostPlace(st, index), remove);
|
|
183
195
|
if (item.insert === "csv") return pickCsv(view, remove);
|
|
184
196
|
if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove, null, index);
|
|
185
197
|
if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove, index);
|
|
186
|
-
if (item.insert === "chart") return chartFromSlash(view, index, remove);
|
|
198
|
+
if (item.insert === "chart") return loadChartPart(view).then(({ chartFromSlash }) => chartFromSlash(view, index, remove));
|
|
187
199
|
let tr = st.tr;
|
|
188
200
|
if (remove) tr.delete(remove.from, remove.to);
|
|
189
201
|
const node = tr.doc.child(index);
|
package/src/page/markdown.js
CHANGED
|
@@ -124,7 +124,31 @@ export const MARKDOWN_RULES = [
|
|
|
124
124
|
...INLINE_RULES,
|
|
125
125
|
];
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
// The shortest shortcut that quotes a line: `" ` typed at the start of a
|
|
128
|
+
// paragraph. It writes a floor-1 block, so it asks the format gate first and
|
|
129
|
+
// converts only when the gate answered applied; the transaction it dispatches is
|
|
130
|
+
// the input-rule one, recorded on this plugin so Backspace puts the literal `" `
|
|
131
|
+
// back and undo keeps the conversion whole. The typed space is never swallowed:
|
|
132
|
+
// when the format cannot be written, the space is inserted as itself.
|
|
133
|
+
function quoteShortcut(plugin, view, from, to, text) {
|
|
134
|
+
if (text !== " " || from !== to) return false;
|
|
135
|
+
const st = view.state;
|
|
136
|
+
const $from = st.doc.resolve(from);
|
|
137
|
+
const node = $from.parent;
|
|
138
|
+
if ($from.depth !== 1 || node.type !== N.paragraph || $from.parentOffset !== 1 || node.textContent !== '"') return false;
|
|
139
|
+
if (!view.editable || typeof view.runFormatCommand !== "function") return false;
|
|
140
|
+
const plain = () => { if (!view.isDestroyed) view.dispatch(st.tr.insertText(" ", from, to)); };
|
|
141
|
+
const at = $from.before(1);
|
|
142
|
+
import("./format-native-commands.js").then(({ quoteFromSpace }) => quoteFromSpace(plugin, view, { at, from, to }, plain), plain);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const markdownPlugin = () => {
|
|
147
|
+
const plugin = inputrules.inputRules({ rules: MARKDOWN_RULES });
|
|
148
|
+
const rules = plugin.props.handleTextInput;
|
|
149
|
+
plugin.props.handleTextInput = (view, from, to, text) => quoteShortcut(plugin, view, from, to, text) || rules(view, from, to, text);
|
|
150
|
+
return plugin;
|
|
151
|
+
};
|
|
128
152
|
|
|
129
153
|
// "| Name | Owner |" then Enter at the end of a paragraph: the paragraph becomes
|
|
130
154
|
// a table with those column names and one empty row, the caret in its first cell.
|
|
@@ -150,7 +174,7 @@ export function backspaceToParagraph(st, dispatch) {
|
|
|
150
174
|
const { $from, empty } = st.selection;
|
|
151
175
|
if (!empty || $from.depth !== 1 || $from.parentOffset !== 0) return false;
|
|
152
176
|
const n = $from.parent;
|
|
153
|
-
if (!(n.type === N.heading || n.type === N.callout || (n.type === N.code_block && n.content.size === 0))) return false;
|
|
177
|
+
if (!(n.type === N.heading || n.type === N.callout || n.type === N.quote || (n.type === N.code_block && n.content.size === 0))) return false;
|
|
154
178
|
if (dispatch) dispatch(asStep(st.tr.setNodeMarkup($from.before(), N.paragraph, { id: n.attrs.id, pos: n.attrs.pos, parent: n.attrs.parent })));
|
|
155
179
|
return true;
|
|
156
180
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The people picker the @ command draws: one listbox at the caret, outside the saved
|
|
2
|
+
// article and never in it. It is built from the editor's own menu markup, so it needs no
|
|
3
|
+
// stylesheet of its own, and it takes no focus: the caret and the typed query stay in the
|
|
4
|
+
// document, and the active row is announced through the editor's aria-activedescendant as
|
|
5
|
+
// the slash menu does. A name is only ever text.
|
|
6
|
+
export const PICKER = "page-mention-picker";
|
|
7
|
+
export const OPTION = "page-menu-item";
|
|
8
|
+
const EMPTY = "No people to mention";
|
|
9
|
+
|
|
10
|
+
let count = 0;
|
|
11
|
+
|
|
12
|
+
// The people a picker may offer: the ones the document already names, with the host's live
|
|
13
|
+
// list laid over them by id, so a renamed person is shown under the name the host answers
|
|
14
|
+
// with. Nothing is invented when the host has no list.
|
|
15
|
+
export function peopleFrom(saved = [], allowed = null) {
|
|
16
|
+
const byId = new Map();
|
|
17
|
+
for (const person of saved ?? []) if (person && typeof person.id === "string") byId.set(person.id, person);
|
|
18
|
+
for (const person of Array.isArray(allowed) ? allowed : []) if (person && typeof person.id === "string") byId.set(person.id, person);
|
|
19
|
+
return [...byId.values()];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const filterPeople = (people, query) => {
|
|
23
|
+
const q = String(query ?? "").trim().toLowerCase();
|
|
24
|
+
if (!q) return people;
|
|
25
|
+
return people.filter((person) => String(person.name ?? "").toLowerCase().includes(q));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// The shell takes the words a command shows, so a date command reuses the same listbox:
|
|
29
|
+
// same placement, same rows, same keyboard, and its own name for what it lists.
|
|
30
|
+
export function createMentionPicker({ document: doc = globalThis.document, onPick = () => {}, label = "Mention a person", empty = EMPTY } = {}) {
|
|
31
|
+
const id = `page-mentions-${++count}`;
|
|
32
|
+
const el = doc.createElement("div");
|
|
33
|
+
el.className = `page-menu ${PICKER}`;
|
|
34
|
+
el.id = id;
|
|
35
|
+
el.setAttribute("role", "listbox");
|
|
36
|
+
el.setAttribute("aria-label", label);
|
|
37
|
+
el.hidden = true;
|
|
38
|
+
let people = [];
|
|
39
|
+
let active = 0;
|
|
40
|
+
|
|
41
|
+
const draw = () => {
|
|
42
|
+
if (!people.length) {
|
|
43
|
+
const none = doc.createElement("div");
|
|
44
|
+
none.className = "page-menu-title";
|
|
45
|
+
none.textContent = empty;
|
|
46
|
+
el.replaceChildren(none);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
el.replaceChildren(...people.map((person, i) => {
|
|
50
|
+
const row = doc.createElement("button");
|
|
51
|
+
row.type = "button";
|
|
52
|
+
row.className = `${OPTION}${i === active ? " is-active" : ""}`;
|
|
53
|
+
row.id = `${id}-${i}`;
|
|
54
|
+
row.dataset.index = String(i);
|
|
55
|
+
row.setAttribute("role", "option");
|
|
56
|
+
row.setAttribute("aria-selected", String(i === active));
|
|
57
|
+
row.textContent = String(person.name ?? "");
|
|
58
|
+
row.addEventListener("mousedown", (event) => event.preventDefault());
|
|
59
|
+
row.addEventListener("click", () => onPick(i));
|
|
60
|
+
return row;
|
|
61
|
+
}));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// The caret the list hangs from, in the page's own placement variables: the same ones
|
|
65
|
+
// the block menu writes, so the existing menu stylesheet paints and lifts it.
|
|
66
|
+
const place = (at) => {
|
|
67
|
+
el.style.setProperty("--page-pop-x", `${at.x}px`);
|
|
68
|
+
el.style.setProperty("--page-pop-y", `${at.y}px`);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
el,
|
|
73
|
+
show: (next, at = null) => {
|
|
74
|
+
people = next;
|
|
75
|
+
active = 0;
|
|
76
|
+
draw();
|
|
77
|
+
if (at) place(at);
|
|
78
|
+
el.hidden = false;
|
|
79
|
+
},
|
|
80
|
+
move: (step) => {
|
|
81
|
+
if (!people.length) return;
|
|
82
|
+
active = (active + step + people.length) % people.length;
|
|
83
|
+
draw();
|
|
84
|
+
},
|
|
85
|
+
activeId: () => (people.length ? `${id}-${active}` : null),
|
|
86
|
+
index: () => active,
|
|
87
|
+
personAt: (i) => people[i] ?? null,
|
|
88
|
+
hide: () => {
|
|
89
|
+
el.hidden = true;
|
|
90
|
+
people = [];
|
|
91
|
+
active = 0;
|
|
92
|
+
el.replaceChildren();
|
|
93
|
+
},
|
|
94
|
+
destroy() {
|
|
95
|
+
this.hide();
|
|
96
|
+
el.remove();
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
package/src/page/merge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canon, renderModel, parseModel, fragmentCleaner } from "./projection.js";
|
|
2
2
|
|
|
3
|
-
const TEXT = new Set(["paragraph", "heading", "list_item", "callout", "code_block", "toggle"]);
|
|
3
|
+
const TEXT = new Set(["paragraph", "heading", "list_item", "callout", "quote", "code_block", "toggle"]);
|
|
4
4
|
const same = (a, b) => canon(a) === canon(b);
|
|
5
5
|
|
|
6
6
|
function inlineOf(item, doc) {
|
package/src/page/projection.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { chartModel, renderChartSvg } from "./rich/chart.js";
|
|
12
12
|
import { cellText } from "./rich/cell.js";
|
|
13
13
|
import { bounded, descriptors, entriesOf, keyOf, normalizeEntries } from "./format-codec.js";
|
|
14
|
+
import { nativeAttrs } from "./format-native.js";
|
|
14
15
|
|
|
15
16
|
export const ATOM = "";
|
|
16
17
|
export const BREAK = '@{"type":"hard_break"}';
|
|
@@ -26,6 +27,13 @@ const KNOWN = new Set(CODES);
|
|
|
26
27
|
export const OPAQUE = "data-clay-page-opaque";
|
|
27
28
|
export const VALUE = "data-clay-page-value";
|
|
28
29
|
export const MARKS = "data-clay-page-marks";
|
|
30
|
+
// A person mention's span states its id plainly beside the envelope that carries it:
|
|
31
|
+
// the label is the text, and a reader without the editor shows a name, never a link.
|
|
32
|
+
export const MENTION = "data-page-mention";
|
|
33
|
+
// A date mention reads as a `time` element: the text is the ISO date and `datetime`
|
|
34
|
+
// repeats it, so a reader without the editor shows the day and no zone or reminder.
|
|
35
|
+
export const DATE = "data-page-date";
|
|
36
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
29
37
|
const ENVELOPE = "1";
|
|
30
38
|
const STRUCTURAL = new Set(["id", "t", "parent", "c", "m"]);
|
|
31
39
|
const knownType = (t) => Object.hasOwn(DEFAULTS, t);
|
|
@@ -36,6 +44,7 @@ export const DEFAULTS = {
|
|
|
36
44
|
heading: { parent: null, level: 2 },
|
|
37
45
|
list_item: { parent: null, kind: "bullet", indent: 0, checked: false, owner: null, due: null },
|
|
38
46
|
callout: { parent: null },
|
|
47
|
+
quote: { parent: null },
|
|
39
48
|
code_block: { parent: null, lang: "" },
|
|
40
49
|
toggle: { parent: null, open: false },
|
|
41
50
|
divider: { parent: null },
|
|
@@ -47,7 +56,7 @@ export const DEFAULTS = {
|
|
|
47
56
|
tool: { parent: null, artifact: null, rev: null, state: {} },
|
|
48
57
|
preserved: { parent: null, html: "" },
|
|
49
58
|
};
|
|
50
|
-
const TEXT = new Set(["paragraph", "heading", "list_item", "callout", "code_block", "toggle"]);
|
|
59
|
+
const TEXT = new Set(["paragraph", "heading", "list_item", "callout", "quote", "code_block", "toggle"]);
|
|
51
60
|
|
|
52
61
|
const same = (a, b) => canon(a) === canon(b);
|
|
53
62
|
|
|
@@ -146,18 +155,25 @@ export function runsOf(units) {
|
|
|
146
155
|
return out;
|
|
147
156
|
}
|
|
148
157
|
|
|
149
|
-
// A mark key as { marks: { b: true, a: ["href"] ... }, entries: [...] }
|
|
150
|
-
//
|
|
151
|
-
// the
|
|
158
|
+
// A mark key as { marks: { b: true, a: ["href"] ... }, native: [...], entries: [...] }:
|
|
159
|
+
// the codes this runtime knows, the named descriptors of the four formats it
|
|
160
|
+
// writes (format-native.js), and the entries of every mark it has no code for,
|
|
161
|
+
// each in the codec's canonical order.
|
|
152
162
|
function marksOfKey(key) {
|
|
153
|
-
const marks = {};
|
|
154
|
-
if (!key || key[0] === "@") return { marks, entries: [] };
|
|
163
|
+
const marks = {}, native = [];
|
|
164
|
+
if (!key || key[0] === "@") return { marks, native: [], entries: [] };
|
|
155
165
|
const opaque = [];
|
|
156
166
|
for (const desc of descriptors(key)) {
|
|
157
|
-
if (desc.name === undefined
|
|
167
|
+
if (desc.name === undefined) {
|
|
168
|
+
if (KNOWN.has(desc.code)) marks[desc.code] = desc.attrs ?? true;
|
|
169
|
+
else opaque.push(desc);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const attrs = nativeAttrs(desc.name, desc.attrs);
|
|
173
|
+
if (attrs) native.push({ name: desc.name, attrs });
|
|
158
174
|
else opaque.push(desc);
|
|
159
175
|
}
|
|
160
|
-
return { marks, entries: opaque.length ? entriesOf(opaque) : [] };
|
|
176
|
+
return { marks, native, entries: opaque.length ? entriesOf(opaque) : [] };
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
// One unit's key, as the record keeps it: the codes this runtime knows, then
|
|
@@ -165,8 +181,8 @@ function marksOfKey(key) {
|
|
|
165
181
|
const unitKey = (key, text, i) => {
|
|
166
182
|
if (!key) return "";
|
|
167
183
|
if (key[0] === "@") return key === BREAK && text[i] === ATOM ? key : "";
|
|
168
|
-
const { marks, entries } = marksOfKey(key);
|
|
169
|
-
return keyOfMarks(safeMarks(marks), entries);
|
|
184
|
+
const { marks, native, entries } = marksOfKey(key);
|
|
185
|
+
return keyOfMarks(safeMarks(marks), native.length ? [...entries, ...entriesOf(native)] : entries);
|
|
170
186
|
};
|
|
171
187
|
|
|
172
188
|
// A link to a script URL is never kept: it would run on the page's origin when a
|
|
@@ -410,6 +426,10 @@ const escText = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>
|
|
|
410
426
|
const escAttr = (s) => escText(String(s)).replace(/"/g, """);
|
|
411
427
|
const attr = (name, value) => (value === null || value === undefined ? "" : ` ${name}="${escAttr(value)}"`);
|
|
412
428
|
|
|
429
|
+
// The element each of the four formats is read as in the saved article; the
|
|
430
|
+
// text color and the highlight carry their palette key as data-color.
|
|
431
|
+
const NATIVE_TAGS = { underline: "u", text_color: "span", highlight: "mark" };
|
|
432
|
+
|
|
413
433
|
export function renderInline(c, m, marksEnvelopes = true) {
|
|
414
434
|
const units = unitsOf(m, c.length) || [];
|
|
415
435
|
let html = "";
|
|
@@ -425,13 +445,22 @@ export function renderInline(c, m, marksEnvelopes = true) {
|
|
|
425
445
|
}
|
|
426
446
|
let j = i + 1;
|
|
427
447
|
while (j < c.length && (units[j] || "") === key && !(c[j] === ATOM && key[0] === "@")) j++;
|
|
428
|
-
const { marks, entries } = marksOfKey(key);
|
|
448
|
+
const { marks, native, entries } = marksOfKey(key);
|
|
429
449
|
const want = NEST.filter((code) => marks[code]).map((code) => ({ code, sig: code + JSON.stringify(marks[code]), tag: code === "a" ? "a" : TAGS[code], href: code === "a" ? marks.a[0] : null }));
|
|
430
|
-
|
|
450
|
+
for (const n of native) want.push({ sig: `native${n.name}${canon(n.attrs)}`, tag: NATIVE_TAGS[n.name], color: n.attrs.color ?? null });
|
|
451
|
+
const mention = entries.find((entry) => entry.name === "mention");
|
|
452
|
+
const date = entries.find((entry) => entry.name === "date");
|
|
453
|
+
const day = date && DAY.test(String(date.attrs.date)) ? String(date.attrs.date) : null;
|
|
454
|
+
if (entries.length && marksEnvelopes) want.push({ sig: `marks${canon(entries)}`, tag: day ? "time" : "span", entries, mention: mention?.attrs.id, day });
|
|
431
455
|
let keep = 0;
|
|
432
456
|
while (keep < open.length && keep < want.length && open[keep].sig === want[keep].sig) keep++;
|
|
433
457
|
closeTo(keep);
|
|
434
|
-
for (const w of want.slice(keep)) {
|
|
458
|
+
for (const w of want.slice(keep)) {
|
|
459
|
+
if (w.entries) html += w.day ? `<time${attr("datetime", w.day)}${attr(DATE, "")}${marksEnvelope(w.entries)}>` : `<span${marksEnvelope(w.entries)}${attr(MENTION, w.mention)}>`;
|
|
460
|
+
else if (w.tag === "a") html += `<a${attr("href", w.href)}>`;
|
|
461
|
+
else html += w.color ? `<${w.tag}${attr("data-color", w.color)}>` : `<${w.tag}>`;
|
|
462
|
+
open.push(w);
|
|
463
|
+
}
|
|
435
464
|
html += escText(c.slice(i, j));
|
|
436
465
|
i = j;
|
|
437
466
|
}
|
|
@@ -454,6 +483,7 @@ function renderLeaf(item, clean, tables) {
|
|
|
454
483
|
case "paragraph": return `<p${at}>${inlineOf(item)}</p>`;
|
|
455
484
|
case "heading": { const h = `h${item.level ?? 2}`; return `<${h}${at}>${inlineOf(item)}</${h}>`; }
|
|
456
485
|
case "callout": return `<aside${at} class="page-callout">${inlineOf(item)}</aside>`;
|
|
486
|
+
case "quote": return `<blockquote${at}>${inlineOf(item)}</blockquote>`;
|
|
457
487
|
case "code_block": return `<pre${at}${attr("data-lang", item.lang || null)}><code>${escText(item.c ?? "")}</code></pre>`;
|
|
458
488
|
case "divider": return `<hr${at}>`;
|
|
459
489
|
case "image": return `<figure${at} data-type="image"${attr("data-align", item.align ?? null)}><img${attr("src", item.src ?? "")}${attr("alt", item.alt ?? "")}${attr("width", item.width ?? null)}>${item.caption ? `<figcaption>${escText(item.caption)}</figcaption>` : ""}</figure>`;
|
|
@@ -561,6 +591,17 @@ export function renderBlocks(blocks, opts) {
|
|
|
561
591
|
const DROP_TAGS = new Set(["SCRIPT", "STYLE", "TEMPLATE", "NOSCRIPT"]);
|
|
562
592
|
const INLINE_TAGS = new Set(["A", "ABBR", "B", "BDI", "BDO", "BR", "CITE", "CODE", "DATA", "DEL", "DFN", "EM", "FONT", "I", "INS", "KBD", "LABEL", "MARK", "Q", "S", "SAMP", "SMALL", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "TIME", "U", "VAR", "WBR"]);
|
|
563
593
|
|
|
594
|
+
// The named descriptor an inline element stands for, when it is one of the four
|
|
595
|
+
// formats: <u>, <mark> and a <span data-color> with a palette key. Anything else
|
|
596
|
+
// is not a format this runtime reads natively.
|
|
597
|
+
function nativeOfEl(tag, el) {
|
|
598
|
+
if (tag === "u") return { name: "underline", attrs: {} };
|
|
599
|
+
if (tag !== "mark" && tag !== "span") return null;
|
|
600
|
+
if (tag === "span" && !el.hasAttribute("data-color")) return null;
|
|
601
|
+
const name = tag === "mark" ? "highlight" : "text_color";
|
|
602
|
+
return { name, attrs: { color: el.getAttribute("data-color") } };
|
|
603
|
+
}
|
|
604
|
+
|
|
564
605
|
// Inline content of el as { c, units }, minus the children skip(child) names.
|
|
565
606
|
function inlineOfEl(el, skip = () => false) {
|
|
566
607
|
let c = "";
|
|
@@ -579,7 +620,7 @@ function inlineOfEl(el, skip = () => false) {
|
|
|
579
620
|
if (tag === "br") { c += ATOM; units.push(BREAK); continue; }
|
|
580
621
|
if (tag === "span" && child.hasAttribute("data-atom")) { c += ATOM; units.push(child.getAttribute("data-atom")); continue; }
|
|
581
622
|
const next = { ...marks };
|
|
582
|
-
if (tag === "span" && child.hasAttribute(MARKS)) {
|
|
623
|
+
if ((tag === "span" || tag === "time") && child.hasAttribute(MARKS)) {
|
|
583
624
|
walk(child, next, normalizeEntries([...entries, ...entriesOfEnvelope(child.getAttribute(MARKS))]));
|
|
584
625
|
continue;
|
|
585
626
|
}
|
|
@@ -588,7 +629,8 @@ function inlineOfEl(el, skip = () => false) {
|
|
|
588
629
|
else if (tag === "s" || tag === "del" || tag === "strike") next.s = true;
|
|
589
630
|
else if (tag === "code") next.c = true;
|
|
590
631
|
else if (tag === "a" && child.hasAttribute("href")) next.a = [child.getAttribute("href")];
|
|
591
|
-
|
|
632
|
+
const named = nativeOfEl(tag, child);
|
|
633
|
+
walk(child, next, named ? [...entries, named] : entries);
|
|
592
634
|
}
|
|
593
635
|
};
|
|
594
636
|
walk(el, {}, []);
|
|
@@ -672,6 +714,7 @@ export function parseArticle(article) {
|
|
|
672
714
|
if (tag === "P" && !onlyImage) return push(el, "paragraph", parent, { ...omitDefaults("paragraph", P), ...tf(el) });
|
|
673
715
|
if (/^H[1-6]$/.test(tag)) return push(el, "heading", parent, { ...omitDefaults("heading", { ...P, level: +tag[1] }), ...tf(el) });
|
|
674
716
|
if (tag === "ASIDE") return push(el, "callout", parent, { ...omitDefaults("callout", P), ...tf(el) });
|
|
717
|
+
if (tag === "BLOCKQUOTE") return push(el, "quote", parent, { ...omitDefaults("quote", P), ...tf(el) });
|
|
675
718
|
if (tag === "PRE") {
|
|
676
719
|
const code = el.querySelector(":scope > code");
|
|
677
720
|
return push(el, "code_block", parent, { ...omitDefaults("code_block", { ...P, lang: el.getAttribute("data-lang") || "" }), c: (code || el).textContent });
|
|
@@ -53,4 +53,43 @@ export function projectInto(clone) {
|
|
|
53
53
|
if (!customTitle(clone)) writeTitle(clone, title);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The page state a final save clone carries, or null.
|
|
58
|
+
*
|
|
59
|
+
* One page tag is one page state: a Yjs page never has two, a tag holding plain block
|
|
60
|
+
* data is a page's own content rather than one, and a tag that does not parse is not one
|
|
61
|
+
* either. The value is copied, because the store keeps writing to its own after the
|
|
62
|
+
* capture and a checkpoint must prove the bytes this capture sent.
|
|
63
|
+
*
|
|
64
|
+
* Registered with core/snapshot only by the recovery integration, so a page that
|
|
65
|
+
* journals nothing never reads it.
|
|
66
|
+
*
|
|
67
|
+
* @param {HTMLElement} root - the save clone, after every transform
|
|
68
|
+
* @returns {?Object}
|
|
69
|
+
*/
|
|
70
|
+
export function pageStateOfClone(root) {
|
|
71
|
+
const tags = pageTags(root);
|
|
72
|
+
if (tags.length !== 1) return null;
|
|
73
|
+
let value;
|
|
74
|
+
try { value = parseJsonRelaxed(tags[0].textContent); }
|
|
75
|
+
catch { return null; }
|
|
76
|
+
const { v, g, y } = value ?? {};
|
|
77
|
+
if (v !== 1 || !y || typeof y !== "object" || Array.isArray(y)) return null;
|
|
78
|
+
return { v, g, y: { ...y } };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The canonical article one page state projects to.
|
|
83
|
+
*
|
|
84
|
+
* The same projection the save writes into the capture's article, run over the state a
|
|
85
|
+
* capture pinned or a host handed back. A state whose projection is not the article
|
|
86
|
+
* that was accepted is not a proof of those bytes, whichever transform changed it.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object|string} state - a page value, or the page tag text holding one
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
export function articleOfPageState(state) {
|
|
92
|
+
return renderFromTags([typeof state === "string" ? state : JSON.stringify(state)]).html;
|
|
93
|
+
}
|
|
94
|
+
|
|
56
95
|
addDocumentTransform(projectInto);
|
package/src/page/save-states.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { captureForSaveAndComparison } from "../core/snapshot.js";
|
|
2
|
-
import { downloadHtml, documentName } from "../core/
|
|
2
|
+
import { downloadHtml, documentName } from "../core/download.js";
|
|
3
3
|
|
|
4
4
|
// One plain state per save refusal code, so the indicator, the toast and the
|
|
5
5
|
// page plugin all read the same answer. `saved` is the only happy one; every
|
package/src/page/schema.js
CHANGED
|
@@ -50,6 +50,12 @@ export const schema = new model.Schema({
|
|
|
50
50
|
"data-checked": n.attrs.checked ? "true" : "false",
|
|
51
51
|
}), 0],
|
|
52
52
|
},
|
|
53
|
+
quote: {
|
|
54
|
+
group: "block",
|
|
55
|
+
content: "inline*",
|
|
56
|
+
attrs: block,
|
|
57
|
+
toDOM: (n) => ["blockquote", ref(n), 0],
|
|
58
|
+
},
|
|
53
59
|
callout: {
|
|
54
60
|
group: "block",
|
|
55
61
|
content: "inline*",
|
|
@@ -174,5 +180,32 @@ export const schema = new model.Schema({
|
|
|
174
180
|
inclusive: false,
|
|
175
181
|
toDOM: (m) => ["a", { href: safeHref(m.attrs.href) }, 0],
|
|
176
182
|
},
|
|
183
|
+
// A person mention is ordinary text with this mark over it: the name is the label
|
|
184
|
+
// and `id` is the people registry's reference. It links nowhere and notifies nobody.
|
|
185
|
+
mention: {
|
|
186
|
+
attrs: { id: {} },
|
|
187
|
+
inclusive: false,
|
|
188
|
+
excludes: "mention date link",
|
|
189
|
+
toDOM: (m) => ["span", { "data-page-mention": m.attrs.id }, 0],
|
|
190
|
+
},
|
|
191
|
+
// A date mention is the calendar date as ordinary ISO text with this mark over it:
|
|
192
|
+
// `date` is YYYY-MM-DD, and no time, zone, reminder or relative word is stored.
|
|
193
|
+
date: {
|
|
194
|
+
attrs: { date: {} },
|
|
195
|
+
inclusive: false,
|
|
196
|
+
excludes: "mention date link",
|
|
197
|
+
toDOM: (m) => ["time", { datetime: m.attrs.date, "data-page-date": "" }, 0],
|
|
198
|
+
},
|
|
199
|
+
// The floor-1 formats (format-native.js): written as named descriptors,
|
|
200
|
+
// drawn as the elements the saved article keeps for them.
|
|
201
|
+
underline: { toDOM: () => ["u", 0] },
|
|
202
|
+
text_color: {
|
|
203
|
+
attrs: { color: {} },
|
|
204
|
+
toDOM: (m) => ["span", { "data-color": m.attrs.color }, 0],
|
|
205
|
+
},
|
|
206
|
+
highlight: {
|
|
207
|
+
attrs: { color: {} },
|
|
208
|
+
toDOM: (m) => ["mark", { "data-color": m.attrs.color }, 0],
|
|
209
|
+
},
|
|
177
210
|
},
|
|
178
211
|
});
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
article[data-page] {
|
|
3
3
|
--page-link: #164583;
|
|
4
4
|
--page-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
5
|
+
--page-color-gray: #5B5953; --page-color-brown: #8A5A1E; --page-color-orange: #B4531A; --page-color-yellow: #7A6210; --page-color-green: #3E6A28;
|
|
6
|
+
--page-color-blue: #1F5A8A; --page-color-purple: #5E4596; --page-color-pink: #A23E6E; --page-color-red: #9E3B2F;
|
|
7
|
+
--page-mark-bg-gray: rgba(35, 34, 31, 0.12); --page-mark-bg-brown: rgba(138, 90, 30, 0.20); --page-mark-bg-orange: rgba(180, 83, 26, 0.20);
|
|
8
|
+
--page-mark-bg-yellow: rgba(122, 98, 16, 0.22); --page-mark-bg-green: rgba(62, 106, 40, 0.20); --page-mark-bg-blue: rgba(31, 90, 138, 0.18);
|
|
9
|
+
--page-mark-bg-purple: rgba(94, 69, 150, 0.18); --page-mark-bg-pink: rgba(162, 62, 110, 0.18); --page-mark-bg-red: rgba(158, 59, 47, 0.18);
|
|
5
10
|
min-height: 100vh;
|
|
6
11
|
box-sizing: border-box;
|
|
7
12
|
padding: var(--page-pad-top) 16px 30vh;
|
|
@@ -42,5 +47,24 @@
|
|
|
42
47
|
article[data-page] th, article[data-page] td { min-width: 120px; padding: 6px 10px; border-inline-end: 1px solid var(--page-rule); border-block-end: 1px solid var(--page-rule); text-align: start; white-space: pre-wrap; }
|
|
43
48
|
article[data-page] th { background: var(--page-code-bg); color: var(--page-ink-soft); font-weight: 600; }
|
|
44
49
|
article[data-page] th[data-kind="number"], article[data-page] td[data-kind="number"] { text-align: end; }
|
|
45
|
-
article[data-page] blockquote { color: var(--page-ink-soft);
|
|
50
|
+
article[data-page] blockquote { color: var(--page-ink-soft); padding-inline-start: var(--page-indent); }
|
|
51
|
+
article[data-page] span[data-color="gray"] { color: var(--page-color-gray); }
|
|
52
|
+
article[data-page] span[data-color="brown"] { color: var(--page-color-brown); }
|
|
53
|
+
article[data-page] span[data-color="orange"] { color: var(--page-color-orange); }
|
|
54
|
+
article[data-page] span[data-color="yellow"] { color: var(--page-color-yellow); }
|
|
55
|
+
article[data-page] span[data-color="green"] { color: var(--page-color-green); }
|
|
56
|
+
article[data-page] span[data-color="blue"] { color: var(--page-color-blue); }
|
|
57
|
+
article[data-page] span[data-color="purple"] { color: var(--page-color-purple); }
|
|
58
|
+
article[data-page] span[data-color="pink"] { color: var(--page-color-pink); }
|
|
59
|
+
article[data-page] span[data-color="red"] { color: var(--page-color-red); }
|
|
60
|
+
article[data-page] mark[data-color="gray"] { background: var(--page-mark-bg-gray); }
|
|
61
|
+
article[data-page] mark[data-color="brown"] { background: var(--page-mark-bg-brown); }
|
|
62
|
+
article[data-page] mark[data-color="orange"] { background: var(--page-mark-bg-orange); }
|
|
63
|
+
article[data-page] mark[data-color="yellow"] { background: var(--page-mark-bg-yellow); }
|
|
64
|
+
article[data-page] mark[data-color="green"] { background: var(--page-mark-bg-green); }
|
|
65
|
+
article[data-page] mark[data-color="blue"] { background: var(--page-mark-bg-blue); }
|
|
66
|
+
article[data-page] mark[data-color="purple"] { background: var(--page-mark-bg-purple); }
|
|
67
|
+
article[data-page] mark[data-color="pink"] { background: var(--page-mark-bg-pink); }
|
|
68
|
+
article[data-page] mark[data-color="red"] { background: var(--page-mark-bg-red); }
|
|
69
|
+
article[data-page] mark { color: inherit; }
|
|
46
70
|
}
|
|
@@ -95,6 +95,11 @@
|
|
|
95
95
|
article[data-page] {
|
|
96
96
|
--page-link: #164583;
|
|
97
97
|
--page-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
98
|
+
--page-color-gray: #5B5953; --page-color-brown: #8A5A1E; --page-color-orange: #B4531A; --page-color-yellow: #7A6210; --page-color-green: #3E6A28;
|
|
99
|
+
--page-color-blue: #1F5A8A; --page-color-purple: #5E4596; --page-color-pink: #A23E6E; --page-color-red: #9E3B2F;
|
|
100
|
+
--page-mark-bg-gray: rgba(35, 34, 31, 0.12); --page-mark-bg-brown: rgba(138, 90, 30, 0.20); --page-mark-bg-orange: rgba(180, 83, 26, 0.20);
|
|
101
|
+
--page-mark-bg-yellow: rgba(122, 98, 16, 0.22); --page-mark-bg-green: rgba(62, 106, 40, 0.20); --page-mark-bg-blue: rgba(31, 90, 138, 0.18);
|
|
102
|
+
--page-mark-bg-purple: rgba(94, 69, 150, 0.18); --page-mark-bg-pink: rgba(162, 62, 110, 0.18); --page-mark-bg-red: rgba(158, 59, 47, 0.18);
|
|
98
103
|
min-height: 100vh;
|
|
99
104
|
box-sizing: border-box;
|
|
100
105
|
padding: var(--page-pad-top) 16px 30vh;
|
|
@@ -135,7 +140,26 @@
|
|
|
135
140
|
article[data-page] th, article[data-page] td { min-width: 120px; padding: 6px 10px; border-inline-end: 1px solid var(--page-rule); border-block-end: 1px solid var(--page-rule); text-align: start; white-space: pre-wrap; }
|
|
136
141
|
article[data-page] th { background: var(--page-code-bg); color: var(--page-ink-soft); font-weight: 600; }
|
|
137
142
|
article[data-page] th[data-kind="number"], article[data-page] td[data-kind="number"] { text-align: end; }
|
|
138
|
-
article[data-page] blockquote { color: var(--page-ink-soft);
|
|
143
|
+
article[data-page] blockquote { color: var(--page-ink-soft); padding-inline-start: var(--page-indent); }
|
|
144
|
+
article[data-page] span[data-color="gray"] { color: var(--page-color-gray); }
|
|
145
|
+
article[data-page] span[data-color="brown"] { color: var(--page-color-brown); }
|
|
146
|
+
article[data-page] span[data-color="orange"] { color: var(--page-color-orange); }
|
|
147
|
+
article[data-page] span[data-color="yellow"] { color: var(--page-color-yellow); }
|
|
148
|
+
article[data-page] span[data-color="green"] { color: var(--page-color-green); }
|
|
149
|
+
article[data-page] span[data-color="blue"] { color: var(--page-color-blue); }
|
|
150
|
+
article[data-page] span[data-color="purple"] { color: var(--page-color-purple); }
|
|
151
|
+
article[data-page] span[data-color="pink"] { color: var(--page-color-pink); }
|
|
152
|
+
article[data-page] span[data-color="red"] { color: var(--page-color-red); }
|
|
153
|
+
article[data-page] mark[data-color="gray"] { background: var(--page-mark-bg-gray); }
|
|
154
|
+
article[data-page] mark[data-color="brown"] { background: var(--page-mark-bg-brown); }
|
|
155
|
+
article[data-page] mark[data-color="orange"] { background: var(--page-mark-bg-orange); }
|
|
156
|
+
article[data-page] mark[data-color="yellow"] { background: var(--page-mark-bg-yellow); }
|
|
157
|
+
article[data-page] mark[data-color="green"] { background: var(--page-mark-bg-green); }
|
|
158
|
+
article[data-page] mark[data-color="blue"] { background: var(--page-mark-bg-blue); }
|
|
159
|
+
article[data-page] mark[data-color="purple"] { background: var(--page-mark-bg-purple); }
|
|
160
|
+
article[data-page] mark[data-color="pink"] { background: var(--page-mark-bg-pink); }
|
|
161
|
+
article[data-page] mark[data-color="red"] { background: var(--page-mark-bg-red); }
|
|
162
|
+
article[data-page] mark { color: inherit; }
|
|
139
163
|
}
|
|
140
164
|
|
|
141
165
|
|