@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/editor.js
CHANGED
|
@@ -11,18 +11,18 @@ import { schema } from "./schema.js";
|
|
|
11
11
|
import { identityPlugin, docFromBlocks, blockMap, keyBetween, newId } from "./model.js";
|
|
12
12
|
import { blockKeys, structurePlugin, collapsePlugin, pastePlugin, BlockSelection, STEP } from "./blocks.js";
|
|
13
13
|
import { chromePlugin, slashRangePlugin } from "./chrome.js";
|
|
14
|
-
import { announcementPlugin } from "./announce.js";
|
|
14
|
+
import { announcementPlugin, announce } from "./announce.js";
|
|
15
15
|
import { linkPlugin } from "./link.js";
|
|
16
16
|
import { blockSelectionPlugin, asStep } from "./blocksel.js";
|
|
17
17
|
import { markdownPlugin, markdownKeys, backspaceToParagraph, composing } from "./markdown.js";
|
|
18
|
+
import { markable } from "./format-native.js";
|
|
18
19
|
import { placeholderPlugin } from "./placeholder.js";
|
|
19
20
|
import { titlePlugin } from "./document-title-runtime.js";
|
|
20
21
|
import { mediaPlugin } from "./media.js";
|
|
21
22
|
import { highlightSlot } from "./highlight-slot.js";
|
|
22
23
|
import { tableKeys, tablePlugin } from "./table.js";
|
|
23
24
|
import { tableSelectPlugin } from "./table-select.js";
|
|
24
|
-
import {
|
|
25
|
-
import { boardPlugin } from "./board-views.js";
|
|
25
|
+
import { viewFeatures, viewHookPlugin } from "./view-hooks.js";
|
|
26
26
|
import { checklistPlugin, checklistKeys } from "./checklist.js";
|
|
27
27
|
import { csvImportPlugin } from "./csv-import.js";
|
|
28
28
|
import { styleWithLegacyId } from "../lib/style.js";
|
|
@@ -39,6 +39,11 @@ const LIGHT = `
|
|
|
39
39
|
--page-rule: rgba(35, 34, 31, 0.10); --page-accent: #2F5E9E; --page-link: #164583; --page-selection: rgba(88, 120, 170, 0.20);
|
|
40
40
|
--page-check-border: rgba(35, 34, 31, 0.35); --page-check-fill: #23221F; --page-check-ink: #F7F6F2;
|
|
41
41
|
--page-callout-bg: rgba(35, 34, 31, 0.04); --page-code-bg: rgba(35, 34, 31, 0.05);
|
|
42
|
+
--page-color-gray: #5B5953; --page-color-brown: #8A5A1E; --page-color-orange: #B4531A; --page-color-yellow: #7A6210; --page-color-green: #3E6A28;
|
|
43
|
+
--page-color-blue: #1F5A8A; --page-color-purple: #5E4596; --page-color-pink: #A23E6E; --page-color-red: #9E3B2F;
|
|
44
|
+
--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);
|
|
45
|
+
--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);
|
|
46
|
+
--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);
|
|
42
47
|
--page-code-token-keyword: #8A3B6E; --page-code-token-constant: #1F5A8A; --page-code-token-string: #3E6A28; --page-code-token-string-expression: #3E6A28; --page-code-token-comment: #8B877E; --page-code-token-function: #5E4596; --page-code-token-parameter: #8A5A1E; --page-code-token-punctuation: var(--page-ink-soft); --page-code-token-link: var(--page-link);
|
|
43
48
|
--page-chrome-ink-rest: rgba(35, 34, 31, 0.20); --page-chrome-ink-moved: rgba(35, 34, 31, 0.34);
|
|
44
49
|
--page-chrome-bg-rest: rgba(35, 34, 31, 0.02); --page-chrome-bg-moved: rgba(35, 34, 31, 0.04);
|
|
@@ -53,6 +58,11 @@ const DARK = `
|
|
|
53
58
|
--page-rule: rgba(231, 228, 220, 0.10); --page-accent: #8DB0E6; --page-link: #8DB0E6; --page-selection: rgba(120, 155, 210, 0.28);
|
|
54
59
|
--page-check-border: rgba(231, 228, 220, 0.35); --page-check-fill: #E7E4DC; --page-check-ink: #1A1A18;
|
|
55
60
|
--page-callout-bg: rgba(231, 228, 220, 0.05); --page-code-bg: rgba(231, 228, 220, 0.06);
|
|
61
|
+
--page-color-gray: #A3A098; --page-color-brown: #E0B47A; --page-color-orange: #E8A177; --page-color-yellow: #DEC97A; --page-color-green: #A9C98B;
|
|
62
|
+
--page-color-blue: #8DB0E6; --page-color-purple: #BBA6E6; --page-color-pink: #E3A3C4; --page-color-red: #E08A7A;
|
|
63
|
+
--page-mark-bg-gray: rgba(231, 228, 220, 0.14); --page-mark-bg-brown: rgba(224, 180, 122, 0.22); --page-mark-bg-orange: rgba(232, 161, 119, 0.22);
|
|
64
|
+
--page-mark-bg-yellow: rgba(222, 201, 122, 0.24); --page-mark-bg-green: rgba(169, 201, 139, 0.22); --page-mark-bg-blue: rgba(141, 176, 230, 0.22);
|
|
65
|
+
--page-mark-bg-purple: rgba(187, 166, 230, 0.22); --page-mark-bg-pink: rgba(227, 163, 196, 0.22); --page-mark-bg-red: rgba(224, 138, 122, 0.22);
|
|
56
66
|
--page-code-token-keyword: #D9A0C8; --page-code-token-constant: #8DB0E6; --page-code-token-string: #A9C98B; --page-code-token-string-expression: #A9C98B; --page-code-token-comment: #75726B; --page-code-token-function: #BBA6E6; --page-code-token-parameter: #E0B47A; --page-code-token-punctuation: var(--page-ink-soft); --page-code-token-link: var(--page-link);
|
|
57
67
|
--page-chrome-ink-rest: rgba(231, 228, 220, 0.18); --page-chrome-ink-moved: rgba(231, 228, 220, 0.30);
|
|
58
68
|
--page-chrome-bg-rest: rgba(231, 228, 220, 0.02); --page-chrome-bg-moved: rgba(231, 228, 220, 0.04);
|
|
@@ -100,6 +110,9 @@ export const PAGE_EDITOR_CSS = `
|
|
|
100
110
|
.page-editor pre:hover .page-code-lang, .page-editor pre:focus-within .page-code-lang, .page-code-lang:focus-visible, .page-editor pre[data-lang] .page-code-lang { opacity: 1; }
|
|
101
111
|
.page-code-lang:hover, .page-code-lang:focus-visible { background: var(--page-pop-hover); color: var(--page-ink-soft); }
|
|
102
112
|
:where(article[data-page], .page-editor) .page-callout { background: var(--page-callout-bg); border: 1px solid var(--page-callout-line); border-radius: 8px; padding: 10px 14px; }
|
|
113
|
+
/* The four floor-1 formats. A quote is set apart by its own inset and ink, with
|
|
114
|
+
no left-border accent; the palette's keys are fixed tokens, in both themes. */
|
|
115
|
+
:where(article[data-page], .page-editor) blockquote { padding-inline-start: var(--page-indent); color: var(--page-ink-soft); }
|
|
103
116
|
:where(article[data-page], .page-editor) hr { border: 0; border-top: 1px solid var(--page-rule); margin-block: 1.2em; }
|
|
104
117
|
:where(article[data-page], .page-editor) .page-hidden { display: none; }
|
|
105
118
|
:where(article[data-page], .page-editor) .page-li { position: relative; --li-indent: 0; padding-inline-start: calc((var(--li-indent) + 1) * var(--page-indent)); }
|
|
@@ -232,6 +245,19 @@ const insertHardBreak = (st, dispatch) => {
|
|
|
232
245
|
return true;
|
|
233
246
|
};
|
|
234
247
|
|
|
248
|
+
// Mod-u belongs to an editable page selection only: anywhere else the browser
|
|
249
|
+
// keeps its own meaning for the key. The write itself goes through the format
|
|
250
|
+
// gate, so a refused activation leaves the document exactly as it was.
|
|
251
|
+
const underlineShortcut = (st, dispatch, view) => {
|
|
252
|
+
if (!dispatch || !view?.editable || !markable(st, schema.marks.underline)) return false;
|
|
253
|
+
import("./format-native-commands.js")
|
|
254
|
+
.then(({ formatMark }) => formatMark(view, "underline"), () => null)
|
|
255
|
+
.then((result) => {
|
|
256
|
+
if (result?.status !== "applied" && !view.isDestroyed) announce(view, result?.message ?? "That format isn\u2019t available here.");
|
|
257
|
+
});
|
|
258
|
+
return true;
|
|
259
|
+
};
|
|
260
|
+
|
|
235
261
|
const editKeys = ({ history: useHistory = true, undo, redo } = {}) => ({
|
|
236
262
|
"Mod-z": useHistory ? history.undo : () => { undo?.(); return true; },
|
|
237
263
|
"Shift-Mod-z": useHistory ? history.redo : () => { redo?.(); return true; },
|
|
@@ -239,6 +265,7 @@ const editKeys = ({ history: useHistory = true, undo, redo } = {}) => ({
|
|
|
239
265
|
"Shift-Enter": insertHardBreak,
|
|
240
266
|
"Mod-b": commands.toggleMark(schema.marks.strong),
|
|
241
267
|
"Mod-i": commands.toggleMark(schema.marks.em),
|
|
268
|
+
"Mod-u": underlineShortcut,
|
|
242
269
|
});
|
|
243
270
|
|
|
244
271
|
const restoreShortcut = (st, dispatch, view) => {
|
|
@@ -447,10 +474,19 @@ const commandHosts = new WeakMap();
|
|
|
447
474
|
// A command asks for its module with the state it was asked in, so the host has to hear
|
|
448
475
|
// about every transaction: one that moves the document revision or the selection refuses
|
|
449
476
|
// a command whose module is still arriving rather than running it where the person has
|
|
450
|
-
// since left.
|
|
477
|
+
// since left. It also holds the one typed key a command owns: `@` asks for the mention
|
|
478
|
+
// picker, and the host types the character itself so the command is handed the bookmark it
|
|
479
|
+
// belongs to, with everything else about that picker left to the module that arrives for
|
|
480
|
+
// it. It is asked before the plugin that records ordinary typing, since it types it.
|
|
451
481
|
function commandWatchPlugin(host) {
|
|
452
482
|
return new state.Plugin({
|
|
453
483
|
view: () => ({ update: (editorView, prevState) => host.observe(editorView.state, prevState) }),
|
|
484
|
+
props: { handleTextInput(v, from, to, text) {
|
|
485
|
+
if (text !== "@" || v.composing || !Object.hasOwn(COMMAND_FEATURES, "mentions")) return false;
|
|
486
|
+
v.dispatch(v.state.tr.insertText("@", from, to));
|
|
487
|
+
host.run("mentions", { from, to: from + 1 });
|
|
488
|
+
return true;
|
|
489
|
+
} },
|
|
454
490
|
});
|
|
455
491
|
}
|
|
456
492
|
|
|
@@ -479,6 +515,7 @@ export function pagePlugins({ upload = defaultUpload, highlight, history: useHis
|
|
|
479
515
|
return [
|
|
480
516
|
announcementPlugin(),
|
|
481
517
|
chromePlugin({ onAiTool }),
|
|
518
|
+
commandHost ? commandWatchPlugin(commandHost) : null,
|
|
482
519
|
slashRangePlugin(),
|
|
483
520
|
toolFollowPlugin(toolHost),
|
|
484
521
|
linkPlugin(),
|
|
@@ -497,8 +534,7 @@ export function pagePlugins({ upload = defaultUpload, highlight, history: useHis
|
|
|
497
534
|
structurePlugin(),
|
|
498
535
|
identityPlugin(),
|
|
499
536
|
tablePlugin(),
|
|
500
|
-
|
|
501
|
-
boardPlugin(),
|
|
537
|
+
viewHookPlugin(),
|
|
502
538
|
checklistPlugin(),
|
|
503
539
|
csvImportPlugin({ upload }),
|
|
504
540
|
collapsePlugin(),
|
|
@@ -510,7 +546,6 @@ export function pagePlugins({ upload = defaultUpload, highlight, history: useHis
|
|
|
510
546
|
gapcursor.gapCursor(),
|
|
511
547
|
lazyViewsPlugin(),
|
|
512
548
|
features ? featureWatchPlugin(features) : null,
|
|
513
|
-
commandHost ? commandWatchPlugin(commandHost) : null,
|
|
514
549
|
].filter(Boolean);
|
|
515
550
|
}
|
|
516
551
|
|
|
@@ -563,7 +598,7 @@ function checkboxClick(view, event) {
|
|
|
563
598
|
// Mounts the editor in `mount`, inside a .page-shell wrapper (the surface a
|
|
564
599
|
// theme styles). `onChange(record)` gets the block record (blockMap) after
|
|
565
600
|
// every transaction that changed the document.
|
|
566
|
-
export function createPageEditor(mount, { blocks = null, onChange = null, upload, highlight, doc: initialDoc, dispatch, history: useHistory = true, undo, redo, editable = true, toolHost = null, onAiTool = null, reviseTool = null, board = null, features: providedFeatures = null } = {}) {
|
|
601
|
+
export function createPageEditor(mount, { blocks = null, onChange = null, upload, highlight, doc: initialDoc, dispatch, history: useHistory = true, undo, redo, editable = true, toolHost = null, onAiTool = null, reviseTool = null, board = null, features: providedFeatures = null, formatGate = null } = {}) {
|
|
567
602
|
const doc = mount.ownerDocument;
|
|
568
603
|
// Editor chrome, injected on every edit-mode boot and kept out of the saved file:
|
|
569
604
|
// the reader theme <link> styles the page for visitors.
|
|
@@ -586,9 +621,7 @@ export function createPageEditor(mount, { blocks = null, onChange = null, upload
|
|
|
586
621
|
context: (editorView) => ({ view: editorView, upload }),
|
|
587
622
|
});
|
|
588
623
|
const nodeViewRegistry = new Map();
|
|
589
|
-
const nodeViews = {
|
|
590
|
-
chart: (node, v, getPos) => new ChartView(node, v, getPos),
|
|
591
|
-
};
|
|
624
|
+
const nodeViews = {};
|
|
592
625
|
for (const feature of Object.values(FEATURES)) for (const type of feature.views ?? feature.types) {
|
|
593
626
|
nodeViews[type] = (node, v, getPos, decorations, inner) => {
|
|
594
627
|
const make = nodeViewRegistry.get(node.type.name);
|
|
@@ -622,9 +655,41 @@ export function createPageEditor(mount, { blocks = null, onChange = null, upload
|
|
|
622
655
|
},
|
|
623
656
|
});
|
|
624
657
|
nodeViewRegistries.set(view, nodeViewRegistry);
|
|
658
|
+
viewFeatures.set(view, features);
|
|
625
659
|
commandHost.attach(view);
|
|
626
660
|
commandHosts.set(view, commandHost);
|
|
627
661
|
const destroy = view.destroy.bind(view);
|
|
628
|
-
|
|
662
|
+
// A new-format command asks the format gate, which the page plugin hands over as a
|
|
663
|
+
// loader of its own: neither the gate nor its host adapter is on the editor's ready
|
|
664
|
+
// path, so a reader who never uses a newer format never fetches them. The gate is
|
|
665
|
+
// created once, on the first command, and belongs to this view.
|
|
666
|
+
let gateHeld = null;
|
|
667
|
+
let disposed = false;
|
|
668
|
+
const heldGate = () => {
|
|
669
|
+
if (!gateHeld) {
|
|
670
|
+
gateHeld = Promise.resolve().then(() => formatGate()).catch((error) => {
|
|
671
|
+
gateHeld = null;
|
|
672
|
+
throw error;
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
return gateHeld;
|
|
676
|
+
};
|
|
677
|
+
view.runFormatCommand = async (apply) => {
|
|
678
|
+
if (disposed) return { status: "disposed", retry: false };
|
|
679
|
+
if (!formatGate) return { status: "unsupported-host", code: "page-format-unsupported", retry: false };
|
|
680
|
+
const gate = await heldGate();
|
|
681
|
+
if (disposed) {
|
|
682
|
+
gate.dispose?.();
|
|
683
|
+
return { status: "disposed", retry: false };
|
|
684
|
+
}
|
|
685
|
+
return gate.run({ apply });
|
|
686
|
+
};
|
|
687
|
+
view.destroy = () => {
|
|
688
|
+
disposed = true;
|
|
689
|
+
if (gateHeld) gateHeld.then((gate) => gate?.dispose?.(), () => {});
|
|
690
|
+
commandHost.dispose();
|
|
691
|
+
features.dispose();
|
|
692
|
+
destroy();
|
|
693
|
+
};
|
|
629
694
|
return view;
|
|
630
695
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// The emoji a picker offers: a small curated set, grouped the way somebody looks for one.
|
|
2
|
+
// Each row is [glyph, name, keywords], and the glyph is the whole record: a variation
|
|
3
|
+
// selector, a skin tone, a flag's regional pair, a keycap or a joined family is written as
|
|
4
|
+
// the one grapheme a person sees, and nothing here is a font, a sprite, an image or a URL.
|
|
5
|
+
// It is fetched by `emoji.js` the first time the picker opens, never by a reader and never
|
|
6
|
+
// with the editor.
|
|
7
|
+
export const EMOJI = {
|
|
8
|
+
"Smileys": [
|
|
9
|
+
["😀", "grinning", "face smile happy"],
|
|
10
|
+
["😃", "grinning big eyes", "face smile happy"],
|
|
11
|
+
["😄", "grinning smiling eyes", "face laugh happy"],
|
|
12
|
+
["😁", "beaming", "face grin happy"],
|
|
13
|
+
["😆", "laughing", "face happy haha"],
|
|
14
|
+
["😅", "sweat smile", "face relief nervous"],
|
|
15
|
+
["😂", "joy", "face tears laugh lol"],
|
|
16
|
+
["🙂", "slight smile", "face ok"],
|
|
17
|
+
["🙃", "upside down", "face silly"],
|
|
18
|
+
["😉", "wink", "face flirt"],
|
|
19
|
+
["😊", "blush", "face smile warm"],
|
|
20
|
+
["😇", "halo", "face angel innocent"],
|
|
21
|
+
["🥰", "smiling hearts", "face love"],
|
|
22
|
+
["😍", "heart eyes", "face love crush"],
|
|
23
|
+
["🤩", "star struck", "face wow excited"],
|
|
24
|
+
["😎", "cool", "face sunglasses"],
|
|
25
|
+
["🤔", "thinking", "face hmm question"],
|
|
26
|
+
["😐", "neutral", "face flat"],
|
|
27
|
+
["😴", "sleeping", "face tired zzz"],
|
|
28
|
+
["😢", "crying", "face sad tear"],
|
|
29
|
+
["😭", "sobbing", "face sad tears"],
|
|
30
|
+
["😡", "angry", "face mad red"],
|
|
31
|
+
["😱", "scream", "face fear shock"],
|
|
32
|
+
["🤯", "exploding head", "face mind blown"],
|
|
33
|
+
["🥳", "party face", "celebration birthday"],
|
|
34
|
+
["😷", "mask", "face sick medical"],
|
|
35
|
+
["🤒", "thermometer", "face sick ill"],
|
|
36
|
+
],
|
|
37
|
+
"Gestures and people": [
|
|
38
|
+
["👋", "wave", "hand hello hi bye"],
|
|
39
|
+
["👋🏽", "wave medium skin tone", "hand hello hi"],
|
|
40
|
+
["👍", "thumbs up", "yes like approve"],
|
|
41
|
+
["👍🏿", "thumbs up dark skin tone", "yes like approve"],
|
|
42
|
+
["👎", "thumbs down", "no dislike"],
|
|
43
|
+
["👏", "clapping", "hands applause bravo"],
|
|
44
|
+
["🙌", "raised hands", "hooray praise"],
|
|
45
|
+
["🙏", "folded hands", "please thanks pray"],
|
|
46
|
+
["💪", "flexed arm", "strong muscle"],
|
|
47
|
+
["🤝", "handshake", "deal agreement"],
|
|
48
|
+
["👨👩👧👦", "family", "people children parents"],
|
|
49
|
+
["🧑💻", "technologist", "person laptop work code"],
|
|
50
|
+
["🏃", "runner", "person sport hurry"],
|
|
51
|
+
["🧘", "lotus", "person meditation yoga"],
|
|
52
|
+
],
|
|
53
|
+
"Animals and nature": [
|
|
54
|
+
["🐶", "dog", "animal pet puppy"],
|
|
55
|
+
["🐱", "cat", "animal pet kitten"],
|
|
56
|
+
["🐭", "mouse", "animal rodent"],
|
|
57
|
+
["🐰", "rabbit", "animal bunny"],
|
|
58
|
+
["🦊", "fox", "animal"],
|
|
59
|
+
["🐻", "bear", "animal"],
|
|
60
|
+
["🐼", "panda", "animal"],
|
|
61
|
+
["🐨", "koala", "animal"],
|
|
62
|
+
["🐯", "tiger", "animal"],
|
|
63
|
+
["🦁", "lion", "animal"],
|
|
64
|
+
["🐮", "cow", "animal"],
|
|
65
|
+
["🐷", "pig", "animal"],
|
|
66
|
+
["🐸", "frog", "animal"],
|
|
67
|
+
["🐵", "monkey", "animal"],
|
|
68
|
+
["🐔", "chicken", "animal bird"],
|
|
69
|
+
["🐧", "penguin", "animal bird"],
|
|
70
|
+
["🐦", "bird", "animal"],
|
|
71
|
+
["🦄", "unicorn", "animal magic"],
|
|
72
|
+
["🐝", "bee", "animal insect honey"],
|
|
73
|
+
["🦋", "butterfly", "animal insect"],
|
|
74
|
+
["🌸", "cherry blossom", "flower plant spring"],
|
|
75
|
+
["🌻", "sunflower", "flower plant summer"],
|
|
76
|
+
["🌈", "rainbow", "weather sky"],
|
|
77
|
+
["🌲", "evergreen tree", "plant forest"],
|
|
78
|
+
["🌵", "cactus", "plant desert"],
|
|
79
|
+
["🍁", "maple leaf", "plant autumn"],
|
|
80
|
+
],
|
|
81
|
+
"Food and drink": [
|
|
82
|
+
["☕", "coffee", "drink cup hot"],
|
|
83
|
+
["🍵", "tea", "drink cup green"],
|
|
84
|
+
["🍺", "beer", "drink mug"],
|
|
85
|
+
["🍷", "wine", "drink glass"],
|
|
86
|
+
["🍎", "apple", "fruit food red"],
|
|
87
|
+
["🍌", "banana", "fruit food"],
|
|
88
|
+
["🍇", "grapes", "fruit food"],
|
|
89
|
+
["🍓", "strawberry", "fruit food"],
|
|
90
|
+
["🍑", "peach", "fruit food"],
|
|
91
|
+
["🍕", "pizza", "food slice"],
|
|
92
|
+
["🍔", "burger", "food"],
|
|
93
|
+
["🍟", "fries", "food"],
|
|
94
|
+
["🌮", "taco", "food"],
|
|
95
|
+
["🍣", "sushi", "food fish"],
|
|
96
|
+
["🍜", "noodles", "food ramen"],
|
|
97
|
+
["🍰", "cake", "food dessert"],
|
|
98
|
+
["🍪", "cookie", "food sweet"],
|
|
99
|
+
["🍫", "chocolate", "food sweet"],
|
|
100
|
+
["🥑", "avocado", "food fruit"],
|
|
101
|
+
["🥕", "carrot", "food vegetable"],
|
|
102
|
+
["🍞", "bread", "food loaf"],
|
|
103
|
+
],
|
|
104
|
+
"Travel and places": [
|
|
105
|
+
["✈️", "airplane", "travel flight"],
|
|
106
|
+
["🚗", "car", "travel drive"],
|
|
107
|
+
["🚲", "bicycle", "travel bike"],
|
|
108
|
+
["🚂", "train", "travel locomotive"],
|
|
109
|
+
["🚀", "rocket", "travel launch space"],
|
|
110
|
+
["⛵", "sailboat", "travel boat"],
|
|
111
|
+
["🏠", "house", "place home"],
|
|
112
|
+
["🏢", "office", "place building work"],
|
|
113
|
+
["🗽", "statue of liberty", "place new york"],
|
|
114
|
+
["🗼", "tower", "place tokyo"],
|
|
115
|
+
["🏔", "mountain", "place snow"],
|
|
116
|
+
["🏖", "beach", "place holiday sea"],
|
|
117
|
+
["🌍", "globe", "world earth"],
|
|
118
|
+
["🗺", "map", "place world"],
|
|
119
|
+
["🚦", "traffic light", "place street"],
|
|
120
|
+
],
|
|
121
|
+
"Objects": [
|
|
122
|
+
["💡", "bulb", "idea light"],
|
|
123
|
+
["🔥", "fire", "hot flame"],
|
|
124
|
+
["⭐", "star", "favourite rating"],
|
|
125
|
+
["💯", "hundred", "score perfect"],
|
|
126
|
+
["✅", "check", "done yes"],
|
|
127
|
+
["❌", "cross", "no wrong"],
|
|
128
|
+
["⚠️", "warning", "caution alert"],
|
|
129
|
+
["📌", "pushpin", "pin note"],
|
|
130
|
+
["📎", "paperclip", "attach"],
|
|
131
|
+
["📅", "calendar", "date schedule"],
|
|
132
|
+
["📈", "chart up", "graph growth"],
|
|
133
|
+
["📉", "chart down", "graph loss"],
|
|
134
|
+
["💰", "money bag", "cash"],
|
|
135
|
+
["🔑", "key", "access"],
|
|
136
|
+
["🔒", "lock", "secure private"],
|
|
137
|
+
["🔔", "bell", "notify alert"],
|
|
138
|
+
["🎉", "party popper", "celebrate"],
|
|
139
|
+
["🎁", "gift", "present"],
|
|
140
|
+
["🏆", "trophy", "win award"],
|
|
141
|
+
["🎯", "target", "goal aim"],
|
|
142
|
+
["⏰", "alarm clock", "time"],
|
|
143
|
+
["⌛", "hourglass", "time wait"],
|
|
144
|
+
],
|
|
145
|
+
"Symbols": [
|
|
146
|
+
["❤️", "red heart", "love"],
|
|
147
|
+
["💔", "broken heart", "love sad"],
|
|
148
|
+
["✨", "sparkles", "magic new"],
|
|
149
|
+
["1️⃣", "keycap one", "number"],
|
|
150
|
+
["2️⃣", "keycap two", "number"],
|
|
151
|
+
["#️⃣", "keycap hash", "number channel"],
|
|
152
|
+
["🇫🇷", "flag france", "country"],
|
|
153
|
+
["🇺🇸", "flag united states", "country"],
|
|
154
|
+
["🇯🇵", "flag japan", "country"],
|
|
155
|
+
["♻️", "recycle", "reuse"],
|
|
156
|
+
["➡️", "arrow right", "direction next"],
|
|
157
|
+
["⬅️", "arrow left", "direction back"],
|
|
158
|
+
["🔁", "repeat", "loop again"],
|
|
159
|
+
["🔀", "shuffle", "random"],
|
|
160
|
+
["🆗", "ok button", "yes"],
|
|
161
|
+
],
|
|
162
|
+
};
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { BOARD_CSS } from "../board-style.js";
|
|
2
2
|
import { BoardView } from "../board-view.js";
|
|
3
3
|
import { cardPanel } from "../board-panel.js";
|
|
4
|
+
import { cardPanels } from "../board-views.js";
|
|
5
|
+
import { addViewHook } from "../view-hooks.js";
|
|
4
6
|
|
|
5
|
-
export function createFeature({ board = null, undo = null, redo = null } = {}) {
|
|
7
|
+
export function createFeature({ view = null, board = null, undo = null, redo = null } = {}) {
|
|
8
|
+
const off = view ? addViewHook(view, {
|
|
9
|
+
update: (editorView) => cardPanels.get(editorView)?.sync(),
|
|
10
|
+
destroy: (editorView) => {
|
|
11
|
+
cardPanels.get(editorView)?.hide(false, false);
|
|
12
|
+
cardPanels.delete(editorView);
|
|
13
|
+
},
|
|
14
|
+
}) : () => {};
|
|
6
15
|
return {
|
|
7
16
|
id: "board",
|
|
8
|
-
nodeViews: board ? { board: (node,
|
|
17
|
+
nodeViews: board ? { board: (node, editorView, getPos) => new BoardView(node, editorView, getPos, { commands: board, undo, redo, panel: cardPanel(editorView) }) } : null,
|
|
9
18
|
css: board ? BOARD_CSS : null,
|
|
10
|
-
dispose() {},
|
|
19
|
+
dispose() { off(); },
|
|
11
20
|
};
|
|
12
21
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ChartView, refreshCharts } from "../chart.js";
|
|
2
|
+
import { addViewHook } from "../view-hooks.js";
|
|
3
|
+
|
|
4
|
+
export function createFeature({ view = null } = {}) {
|
|
5
|
+
const off = view ? addViewHook(view, { update: refreshCharts }) : () => {};
|
|
6
|
+
return {
|
|
7
|
+
id: "chart",
|
|
8
|
+
nodeViews: { chart: (node, editorView, getPos) => new ChartView(node, editorView, getPos) },
|
|
9
|
+
dispose() { off(); },
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Date mentions: the Date action offers Today and Tomorrow, and choosing one writes that
|
|
2
|
+
// calendar day into the document as ordinary ISO text with the `date` mark over it. Both
|
|
3
|
+
// days are resolved once, when the command runs, from the local calendar rather than from a
|
|
4
|
+
// UTC instant, so the text a person reads is the day they picked; nothing here stores a
|
|
5
|
+
// time, a zone, a relative word or a reminder, and nothing speaks to the network. Loaded on
|
|
6
|
+
// the first Date action, and disposed with the exit.
|
|
7
|
+
import { state } from "../../vendor/prosemirror.vendor.js";
|
|
8
|
+
import { schema } from "../schema.js";
|
|
9
|
+
import { asStep } from "../blocksel.js";
|
|
10
|
+
import { composing } from "../markdown.js";
|
|
11
|
+
import { createMentionPicker } from "../mention-picker.js";
|
|
12
|
+
import { runFormatted } from "../format-command.js";
|
|
13
|
+
|
|
14
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
15
|
+
|
|
16
|
+
// A calendar date, as the record stores it: no year padding games, no leap second, no
|
|
17
|
+
// zone. A day that does not exist is not a date, so 2023-02-29 is refused.
|
|
18
|
+
export function isISODate(value) {
|
|
19
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
20
|
+
const parsed = new Date(`${value}T12:00:00Z`);
|
|
21
|
+
return Number.isFinite(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Today, from the local calendar: the fields a person reads on their own wall clock, never
|
|
25
|
+
// `toISOString()` of a local time, which is tomorrow or yesterday for part of the day.
|
|
26
|
+
export const localISODate = (now = new Date()) => `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
27
|
+
|
|
28
|
+
// A day after (or before) a date, by calendar arithmetic on the date alone: noon UTC keeps
|
|
29
|
+
// a daylight-saving shift from landing on the day before.
|
|
30
|
+
const dayAfter = (date, days) => {
|
|
31
|
+
const moved = new Date(`${date}T12:00:00Z`);
|
|
32
|
+
moved.setUTCDate(moved.getUTCDate() + days);
|
|
33
|
+
return moved.toISOString().slice(0, 10);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// What the picker lists: two days, both resolved here and once, labelled with the text each
|
|
37
|
+
// one writes. Nothing is stored and no day is invented later.
|
|
38
|
+
export const earlyDays = (today) => {
|
|
39
|
+
const tomorrow = dayAfter(today, 1);
|
|
40
|
+
return [
|
|
41
|
+
{ id: "today", name: `Today (${today})`, date: today },
|
|
42
|
+
{ id: "tomorrow", name: `Tomorrow (${tomorrow})`, date: tomorrow },
|
|
43
|
+
];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function createFeature({ view = null } = {}) {
|
|
47
|
+
if (!view) return { run: () => ({ ok: false, code: "not-editing" }), dispose() {} };
|
|
48
|
+
const doc = view.dom.ownerDocument;
|
|
49
|
+
let session = null;
|
|
50
|
+
|
|
51
|
+
// The caret the command belongs to, read from the document again at the commit: the
|
|
52
|
+
// picker holds no focus and writes nothing, so a caret that moved, a selection that is no
|
|
53
|
+
// longer empty or a block that refuses marks all mean the bookmark no longer holds and the
|
|
54
|
+
// command refuses rather than writing the date somewhere else.
|
|
55
|
+
function bookmark(s) {
|
|
56
|
+
const st = view.state;
|
|
57
|
+
if (!st.selection.empty || st.selection.from !== s.from) return null;
|
|
58
|
+
if (s.from < 0 || s.from > st.doc.content.size) return null;
|
|
59
|
+
const $from = st.doc.resolve(s.from);
|
|
60
|
+
if (!$from.parent.isTextblock || $from.parent.type.spec.code) return null;
|
|
61
|
+
if (!$from.parent.type.allowsMarkType(schema.marks.date)) return null;
|
|
62
|
+
return s.from;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Where the list hangs, in the page's own placement variables. A view that cannot measure
|
|
66
|
+
// the caret (one outside a laid-out document) still gets its list, just not positioned.
|
|
67
|
+
function place(s) {
|
|
68
|
+
try {
|
|
69
|
+
const box = view.dom.parentElement?.getBoundingClientRect?.() ?? { left: 0, top: 0 };
|
|
70
|
+
const at = view.coordsAtPos(s.from);
|
|
71
|
+
return { x: Math.max(8, at.left - box.left), y: at.bottom + 6 - box.top };
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function anchor(s) {
|
|
78
|
+
const id = s.picker.activeId();
|
|
79
|
+
if (id) view.dom.setAttribute("aria-activedescendant", id);
|
|
80
|
+
else if (view.dom.getAttribute("aria-activedescendant")?.startsWith(s.picker.el.id)) view.dom.removeAttribute("aria-activedescendant");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function close() {
|
|
84
|
+
const s = session;
|
|
85
|
+
if (!s) return;
|
|
86
|
+
session = null;
|
|
87
|
+
for (const [target, type, fn, options] of s.listen) target.removeEventListener(type, fn, options);
|
|
88
|
+
if (view.dom.getAttribute("aria-activedescendant")?.startsWith(`${s.picker.el.id}-`)) view.dom.removeAttribute("aria-activedescendant");
|
|
89
|
+
s.picker.destroy();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The one place a date reaches the document. Document 14 Step 8a wraps this call in the
|
|
93
|
+
// format gate's `runFormatCommand`; nothing else in this module writes.
|
|
94
|
+
function insertDateMention(at, date) {
|
|
95
|
+
const label = String(date);
|
|
96
|
+
// One transaction: the ISO text and its mark, then one unmarked space so typing at the
|
|
97
|
+
// edge of a date stays plain text.
|
|
98
|
+
const tr = view.state.tr.insertText(label, at, at);
|
|
99
|
+
tr.addMark(at, at + label.length, schema.marks.date.create({ date: label }));
|
|
100
|
+
tr.insertText(" ", at + label.length);
|
|
101
|
+
tr.setSelection(state.TextSelection.create(tr.doc, at + label.length + 1));
|
|
102
|
+
view.dispatch(asStep(tr));
|
|
103
|
+
view.focus();
|
|
104
|
+
return { ok: true, date: label };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function choose(index) {
|
|
108
|
+
const s = session;
|
|
109
|
+
if (!s) return { ok: false, code: "not-editing" };
|
|
110
|
+
const entry = s.picker.personAt(index);
|
|
111
|
+
const at = bookmark(s);
|
|
112
|
+
if (at === null || !entry || !isISODate(entry.date)) {
|
|
113
|
+
close();
|
|
114
|
+
if (!entry) return { ok: false, code: "no-date" };
|
|
115
|
+
if (at === null) return { ok: false, code: "stale-target" };
|
|
116
|
+
return { ok: false, code: "invalid-date" };
|
|
117
|
+
}
|
|
118
|
+
close();
|
|
119
|
+
// The gate awaits, so the caret is read again here: one that moved, a selection that is
|
|
120
|
+
// no longer a caret or a block that refuses marks means the target no longer holds, and
|
|
121
|
+
// the date is not written anywhere.
|
|
122
|
+
return runFormatted(view, () => {
|
|
123
|
+
const now = bookmark(s);
|
|
124
|
+
if (now === null) return { ok: false, code: "stale-target" };
|
|
125
|
+
return insertDateMention(now, entry.date);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function key(event) {
|
|
130
|
+
const s = session;
|
|
131
|
+
if (!s || composing(event, view)) return;
|
|
132
|
+
const stop = () => { event.preventDefault(); event.stopPropagation(); };
|
|
133
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
134
|
+
stop();
|
|
135
|
+
s.picker.move(event.key === "ArrowDown" ? 1 : -1);
|
|
136
|
+
anchor(s);
|
|
137
|
+
} else if (event.key === "Enter") {
|
|
138
|
+
stop();
|
|
139
|
+
choose(s.picker.index());
|
|
140
|
+
} else if (event.key === "Escape" || event.key === "Tab") {
|
|
141
|
+
stop();
|
|
142
|
+
close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function open() {
|
|
147
|
+
close();
|
|
148
|
+
// Today is read once, here: a picker left open across midnight still writes the day the
|
|
149
|
+
// command was asked for, and choosing Tomorrow never reads the clock again.
|
|
150
|
+
const s = { from: view.state.selection.from, days: earlyDays(localISODate()), picker: null, listen: [] };
|
|
151
|
+
s.picker = createMentionPicker({ document: doc, label: "Insert a date", empty: "No date to insert", onPick: (index) => choose(index) });
|
|
152
|
+
session = s;
|
|
153
|
+
const listen = (target, type, fn, options) => {
|
|
154
|
+
target.addEventListener(type, fn, options);
|
|
155
|
+
s.listen.push([target, type, fn, options]);
|
|
156
|
+
};
|
|
157
|
+
listen(doc, "keydown", (event) => key(event), true);
|
|
158
|
+
// Typing closes the list: the caret has moved on, and the command that opened it has
|
|
159
|
+
// nothing to write where it no longer holds.
|
|
160
|
+
listen(view.dom, "input", () => close());
|
|
161
|
+
listen(doc, "mousedown", (event) => { if (!s.picker.el.contains(event.target)) close(); }, true);
|
|
162
|
+
(view.dom.parentElement ?? doc.body).appendChild(s.picker.el);
|
|
163
|
+
if (bookmark(s) === null) {
|
|
164
|
+
close();
|
|
165
|
+
return { ok: false, code: "stale-target" };
|
|
166
|
+
}
|
|
167
|
+
s.picker.show(s.days, place(s));
|
|
168
|
+
anchor(s);
|
|
169
|
+
return { ok: true, picker: true };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
// The Date action: the picker at the caret, offering the two days resolved now. It
|
|
174
|
+
// writes nothing until one is chosen.
|
|
175
|
+
async run() {
|
|
176
|
+
return open();
|
|
177
|
+
},
|
|
178
|
+
dispose() { close(); },
|
|
179
|
+
};
|
|
180
|
+
}
|