@heycys/gallery 0.1.0 → 0.1.1
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/THIRD-PARTY-NOTICES.md +51 -1
- package/browser/editor/components/callout/index.mjs +40 -15
- package/browser/editor/components/columns/page.mjs +41 -28
- package/browser/editor/components/details/index.mjs +2 -1
- package/browser/editor/components/dialog/index.mjs +2 -1
- package/browser/editor/components/dialog/page.mjs +0 -18
- package/browser/editor/components/divider/index.mjs +1 -1
- package/browser/editor/components/figure/page.mjs +10 -1
- package/browser/editor/components/file/page.mjs +2 -2
- package/browser/editor/components/flowchart/add.mjs +2 -2
- package/browser/editor/components/heading/index.mjs +2 -0
- package/browser/editor/components/heading/page.mjs +6 -0
- package/browser/editor/components/highlight/index.mjs +4 -20
- package/browser/editor/components/icons.mjs +5 -2
- package/browser/editor/components/image/page.mjs +2 -2
- package/browser/editor/components/installed/index.mjs +71 -25
- package/browser/editor/components/installed/page.mjs +122 -21
- package/browser/editor/components/kit.mjs +3 -0
- package/browser/editor/components/media/page.mjs +44 -14
- package/browser/editor/components/pages.mjs +2 -2
- package/browser/editor/components/registry.mjs +56 -30
- package/browser/editor/components/section/index.mjs +2 -1
- package/browser/editor/components/set-apart.mjs +24 -0
- package/browser/editor/components/table/index.mjs +5 -3
- package/browser/editor/components/table/page.mjs +29 -18
- package/browser/editor/page/annotate/anchors.mjs +4 -3
- package/browser/editor/page/annotate/index.mjs +61 -34
- package/browser/editor/page/autosave.mjs +50 -4
- package/browser/editor/page/beside.mjs +5 -5
- package/browser/editor/page/block-menu/colors.mjs +3 -1
- package/browser/editor/page/block-menu/index.mjs +8 -6
- package/browser/editor/page/blocks.mjs +16 -7
- package/browser/editor/page/caret.mjs +4 -1
- package/browser/editor/page/changes/bar.mjs +102 -0
- package/browser/editor/page/changes/ghost.mjs +206 -0
- package/browser/editor/page/changes/gone-words.mjs +94 -0
- package/browser/editor/page/changes/index.mjs +447 -0
- package/browser/editor/page/changes/words.mjs +27 -0
- package/browser/editor/page/clipboard/import/colors.mjs +3 -3
- package/browser/editor/page/clipboard/index.mjs +32 -13
- package/browser/editor/page/clipboard/own/blocks.mjs +14 -5
- package/browser/editor/page/clipboard/own/describe.mjs +16 -7
- package/browser/editor/page/clipboard/own/export.mjs +6 -6
- package/browser/editor/page/clipboard/own/selection.mjs +2 -2
- package/browser/editor/page/clipboard/own/writers.mjs +0 -11
- package/browser/editor/page/columns/index.mjs +1 -1
- package/browser/editor/page/commit.mjs +19 -4
- package/browser/editor/page/conflicts.mjs +6 -26
- package/browser/editor/page/convert.mjs +8 -7
- package/browser/editor/page/core.mjs +55 -17
- package/browser/editor/page/drag/index.mjs +40 -24
- package/browser/editor/page/drag/preview.mjs +9 -4
- package/browser/editor/page/editor.mjs +70 -15
- package/browser/editor/page/emoji-picker/emoji-tab.mjs +22 -13
- package/browser/editor/page/emoji-picker/grid.mjs +7 -5
- package/browser/editor/page/emoji-picker/icon-tab.mjs +4 -4
- package/browser/editor/page/emoji-picker/icons.mjs +0 -1
- package/browser/editor/page/emoji-picker/index.mjs +11 -9
- package/browser/editor/page/emoji-picker/styles.mjs +161 -2
- package/browser/editor/page/emoji-picker/support.mjs +6 -4
- package/browser/editor/page/emoji-picker/upload.mjs +12 -11
- package/browser/editor/page/fills.mjs +5 -3
- package/browser/editor/page/fold/index.mjs +348 -0
- package/browser/editor/page/fold/rule.mjs +125 -0
- package/browser/editor/page/folded.mjs +23 -0
- package/browser/editor/page/format-bar/bar.mjs +2 -1
- package/browser/editor/page/format-bar/index.mjs +1 -1
- package/browser/editor/page/geometry.mjs +8 -6
- package/browser/editor/page/handle/index.mjs +17 -6
- package/browser/editor/page/hints/index.mjs +1 -1
- package/browser/editor/page/insert/index.mjs +30 -17
- package/browser/editor/page/long-runs.mjs +75 -0
- package/browser/editor/page/look.mjs +15 -3
- package/browser/editor/page/margin.mjs +45 -24
- package/browser/editor/page/menu.mjs +69 -25
- package/browser/editor/page/merge.mjs +24 -0
- package/browser/editor/page/notices.mjs +1 -1
- package/browser/editor/page/offered.mjs +1 -1
- package/browser/editor/page/outline/folds.mjs +11 -10
- package/browser/editor/page/outline/index.mjs +131 -45
- package/browser/editor/page/outline/list.mjs +23 -11
- package/browser/editor/page/outline/read.mjs +1 -1
- package/browser/editor/page/outline/strip.mjs +7 -1
- package/browser/editor/page/panel.mjs +5 -4
- package/browser/editor/page/resize/index.mjs +1 -1
- package/browser/editor/page/selection/delete.mjs +8 -6
- package/browser/editor/page/selection/index.mjs +10 -3
- package/browser/editor/page/session.mjs +54 -3
- package/browser/editor/page/table/cell-bar.mjs +4 -1
- package/browser/editor/page/table/cells.mjs +1 -1
- package/browser/editor/page/table/icons.mjs +1 -1
- package/browser/editor/page/table/index.mjs +13 -8
- package/browser/editor/page/table/widths.mjs +9 -4
- package/browser/editor/page/text-select/index.mjs +18 -3
- package/browser/editor/page/tooltip.mjs +1 -1
- package/browser/editor/page/type-grid.mjs +4 -3
- package/browser/editor/page/typing/code-edges.mjs +63 -17
- package/browser/editor/page/typing/index.mjs +12 -6
- package/browser/editor/page/typing/leave.mjs +1 -1
- package/browser/shared/annotations.mjs +8 -10
- package/browser/shared/code-folds.mjs +5 -1
- package/browser/shared/colors.mjs +15 -13
- package/browser/shared/component-catalog.mjs +24 -10
- package/browser/shared/components/callout.mjs +4 -2
- package/browser/shared/components/index.mjs +61 -7
- package/browser/shared/components/installed.mjs +75 -5
- package/browser/shared/components/page.mjs +10 -1
- package/browser/shared/components/table.mjs +1 -1
- package/browser/shared/components/text.mjs +2 -2
- package/browser/shared/document-look.mjs +178 -0
- package/browser/shared/emoji/data/LICENSE-lucide.txt +43 -0
- package/browser/shared/emoji/data/README.md +4 -2
- package/browser/shared/emoji/data/categories.json +1 -1
- package/browser/shared/emoji/data/keywords-en.json +1 -1
- package/browser/shared/emoji/data/keywords-zh.json +1 -1
- package/browser/shared/emoji/data/names.json +1 -1
- package/browser/shared/emoji/data/positions.json +1 -1
- package/browser/shared/emoji/data/twemoji-index.json +1 -1
- package/browser/shared/emoji/data/twemoji-svg.bin +0 -0
- package/browser/shared/emoji/data.mjs +10 -12
- package/browser/shared/emoji/emoji.mjs +8 -7
- package/browser/shared/emoji/icon-value.mjs +4 -3
- package/browser/shared/html-tree.mjs +11 -5
- package/browser/shared/i18n/en.mjs +67 -8
- package/browser/shared/long-runs.mjs +39 -0
- package/browser/shared/new-names.mjs +5 -2
- package/browser/shared/outline.mjs +194 -0
- package/browser/shared/preferences.mjs +10 -4
- package/browser/shared/session.mjs +5 -4
- package/browser/shared/workspace-look.mjs +91 -0
- package/browser/shared/zoom.mjs +5 -5
- package/dist/chunk-3HIRPVLU.mjs +2 -0
- package/dist/chunk-4FS7Z7WR.mjs +2 -0
- package/dist/{chunk-LHTFLRB6.mjs → chunk-5MHMDLZD.mjs} +2 -2
- package/dist/{chunk-YXAUQPVK.mjs → chunk-5ZKJ3H4P.mjs} +3 -3
- package/dist/{chunk-ZBIM6ARY.mjs → chunk-64K7IPLO.mjs} +55 -16
- package/dist/{chunk-QYKCJ7FG.mjs → chunk-6QXGZM36.mjs} +1 -1
- package/dist/chunk-6RGJOMJP.mjs +16 -0
- package/dist/{chunk-EUX7IC5J.mjs → chunk-7DTINW6U.mjs} +2 -2
- package/dist/{chunk-6CYG3Z4V.mjs → chunk-AJ73XJF3.mjs} +1 -1
- package/dist/chunk-AUZGSZJH.mjs +13 -0
- package/dist/{chunk-7QQBR7PB.mjs → chunk-BFE45AKL.mjs} +1 -1
- package/dist/chunk-BGGDRNYQ.mjs +10 -0
- package/dist/{chunk-L4DVONOG.mjs → chunk-BXO2ZY5P.mjs} +1 -1
- package/dist/chunk-CEH4MRCC.mjs +2 -0
- package/dist/chunk-CY2FNNAB.mjs +16 -0
- package/dist/{chunk-LC7YNQFG.mjs → chunk-DFUDV4LX.mjs} +1 -1
- package/dist/chunk-DKXG4XDG.mjs +2 -0
- package/dist/chunk-E7LWZRXY.mjs +4 -0
- package/dist/chunk-EZQ34M3F.mjs +124 -0
- package/dist/chunk-FDLXYYG4.mjs +4 -0
- package/dist/chunk-FUKQNOQS.mjs +9 -0
- package/dist/{chunk-DB4WKGYF.mjs → chunk-GB5SOYQJ.mjs} +2 -2
- package/dist/{chunk-ACOPDCNB.mjs → chunk-IVDLMMBD.mjs} +1 -1
- package/dist/chunk-JPOSLGOC.mjs +6 -0
- package/dist/chunk-JVHHHGEA.mjs +3 -0
- package/dist/chunk-L3QKH4OK.mjs +26 -0
- package/dist/{chunk-B3VTYBYR.mjs → chunk-LCTHTZIL.mjs} +2 -2
- package/dist/chunk-LEF6DRCK.mjs +59 -0
- package/dist/chunk-LS4F7JUM.mjs +2 -0
- package/dist/chunk-NO5NJCHO.mjs +2 -0
- package/dist/{chunk-UNUEWH4S.mjs → chunk-NQPVAU63.mjs} +1 -1
- package/dist/chunk-O35FIXRB.mjs +2 -0
- package/dist/chunk-PL6GHLGD.mjs +15 -0
- package/dist/{chunk-G7TOXONE.mjs → chunk-QJIR2BLP.mjs} +1 -1
- package/dist/chunk-QUPLVPB2.mjs +7 -0
- package/dist/chunk-R7BNENLY.mjs +4 -0
- package/dist/chunk-S6O4UHLC.mjs +2 -0
- package/dist/{chunk-HMHK3EQ6.mjs → chunk-SGJWQKEI.mjs} +1 -1
- package/dist/chunk-SYGN7GLU.mjs +2 -0
- package/dist/chunk-TYK6VNAB.mjs +10 -0
- package/dist/{chunk-EVJ3BX3M.mjs → chunk-U6KNPJPR.mjs} +1 -1
- package/dist/{chunk-YZ5Z6H4W.mjs → chunk-UALRNVSE.mjs} +1 -1
- package/dist/chunk-UMHUSOPJ.mjs +2 -0
- package/dist/chunk-V3HXVQEM.mjs +2 -0
- package/dist/{chunk-UXUK4PMC.mjs → chunk-V5JTW2LH.mjs} +1 -1
- package/dist/chunk-V5VXUJKG.mjs +2 -0
- package/dist/chunk-VBXMADMP.mjs +2 -0
- package/dist/chunk-VNV55RUA.mjs +2 -0
- package/dist/chunk-VPD2PS35.mjs +2 -0
- package/dist/chunk-W3BGBJQW.mjs +2 -0
- package/dist/chunk-W4VKN3GF.mjs +7 -0
- package/dist/chunk-W5BXXPDJ.mjs +3 -0
- package/dist/{chunk-Z52HTUW7.mjs → chunk-XNROSH2T.mjs} +2 -2
- package/dist/chunk-XUNHRCOG.mjs +6 -0
- package/dist/{chunk-6U2SLSDN.mjs → chunk-Y6RLRPG3.mjs} +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/math.js +18 -2
- package/kit/report.css +248 -137
- package/kit/runtime.js +126 -51
- package/kit/tables.js +51 -1
- package/kit/ui.js +97 -0
- package/package.json +1 -1
- package/release.json +8 -8
- package/sdk/doc-kit.d.ts +9 -3
- package/skill/SKILL.md +22 -10
- package/skill/references/building-components.md +46 -6
- package/skill/references/example-report.html +1 -1
- package/skill/references/writing-html.md +4 -4
- package/skill/sdk/doc-kit.d.ts +9 -3
- package/web/assets/index-Bh74DfsV.css +1 -0
- package/web/assets/index-UW0RyFFt.js +13 -0
- package/web/index.html +2 -2
- package/browser/editor/components/callout/page.mjs +0 -13
- package/dist/chunk-2PBALTR3.mjs +0 -57
- package/dist/chunk-3LA3EMDE.mjs +0 -15
- package/dist/chunk-5FRMCDPY.mjs +0 -16
- package/dist/chunk-5LEK2CBS.mjs +0 -2
- package/dist/chunk-5OFN5AHC.mjs +0 -2
- package/dist/chunk-5SIYS5HN.mjs +0 -2
- package/dist/chunk-5TEK3RAD.mjs +0 -2
- package/dist/chunk-7GHT2UCA.mjs +0 -15
- package/dist/chunk-ATCJYAR4.mjs +0 -2
- package/dist/chunk-C7HWOBIF.mjs +0 -2
- package/dist/chunk-E7VYDG2X.mjs +0 -2
- package/dist/chunk-FQGQH4KM.mjs +0 -2
- package/dist/chunk-GPF4YWWB.mjs +0 -9
- package/dist/chunk-GYQV7Y4J.mjs +0 -120
- package/dist/chunk-HQQMJCQJ.mjs +0 -10
- package/dist/chunk-IERUOGC3.mjs +0 -6
- package/dist/chunk-ITNLYXXV.mjs +0 -3
- package/dist/chunk-NFIBI4IP.mjs +0 -26
- package/dist/chunk-O52JMOCZ.mjs +0 -2
- package/dist/chunk-QMQUXTU4.mjs +0 -12
- package/dist/chunk-QVWVFC5K.mjs +0 -6
- package/dist/chunk-QZY6Q64C.mjs +0 -2
- package/dist/chunk-TDQP3DKQ.mjs +0 -10
- package/dist/chunk-V4CPWKMZ.mjs +0 -2
- package/dist/chunk-VACRB2NE.mjs +0 -3
- package/dist/chunk-W63L7367.mjs +0 -7
- package/dist/chunk-W7CXPCRV.mjs +0 -6
- package/dist/chunk-XINNG4F2.mjs +0 -4
- package/dist/chunk-YL2DPWVS.mjs +0 -4
- package/web/assets/index-B6Ai3wsa.js +0 -13
- package/web/assets/index-SYPC3EZy.css +0 -1
|
@@ -2,6 +2,7 @@ import { unitElement, unitsOf, withLeads } from "../core.mjs";
|
|
|
2
2
|
import { alive, ordered } from "../block-info.mjs";
|
|
3
3
|
import { entersAfter } from "../beside.mjs";
|
|
4
4
|
import { createFills } from "../fills.mjs";
|
|
5
|
+
import { FOLDED } from "../folded.mjs";
|
|
5
6
|
import { describe, note, report } from "../overlays.mjs";
|
|
6
7
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
7
8
|
|
|
@@ -20,6 +21,12 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
20
21
|
// page, made to match the selection the moment it changes (render), never on a later frame, so no
|
|
21
22
|
// fill outlives its block's selection, and none is drawn while the block tools are off. The frames
|
|
22
23
|
// only keep them where their blocks are (paint); a fill that cannot be drawn goes.
|
|
24
|
+
//
|
|
25
|
+
// What a folded heading hides (fold/) is never selected: select() leaves it out, Shift+↑/↓ and ↑/↓
|
|
26
|
+
// pass over it; what takes a folded heading away whole takes it along when it acts (fold/ takenWith).
|
|
27
|
+
|
|
28
|
+
// Whether a block is hidden by a folded heading (data-ge-folded on it or a block around it).
|
|
29
|
+
const folded = (node) => node.closest(`[${FOLDED}]`) !== null;
|
|
23
30
|
|
|
24
31
|
/**
|
|
25
32
|
* info: block-info.mjs. active(): whether block tools may show. say(text, kind, hold): the status
|
|
@@ -37,7 +44,7 @@ export function createSelection({ info, active, say, closeMenu, schedule }) {
|
|
|
37
44
|
/** Select a block, or several (none is clearing); `quiet`: without saying so. */
|
|
38
45
|
function select(targets, { quiet = false } = {}) {
|
|
39
46
|
closeMenu();
|
|
40
|
-
const next = ordered([targets].flat().filter(alive));
|
|
47
|
+
const next = ordered([targets].flat().filter(alive)).filter((node) => !folded(node));
|
|
41
48
|
if (next.length === 0) return clear();
|
|
42
49
|
if (next.length !== picked.length || next.some((node, index) => node !== picked[index])) {
|
|
43
50
|
picked = next;
|
|
@@ -79,7 +86,7 @@ export function createSelection({ info, active, say, closeMenu, schedule }) {
|
|
|
79
86
|
// Shift+↑/↓: the selection widens (or narrows) by one block among the anchor's neighbours.
|
|
80
87
|
function extend(delta) {
|
|
81
88
|
const container = anchor?.parentElement;
|
|
82
|
-
const list = container ? unitsOf(container) : [];
|
|
89
|
+
const list = container ? unitsOf(container).filter((unit) => !folded(unit)) : [];
|
|
83
90
|
const at = list.indexOf(anchor);
|
|
84
91
|
if (at < 0) return;
|
|
85
92
|
const inside = picked.map((node) => list.indexOf(node)).filter((index) => index >= 0);
|
|
@@ -94,7 +101,7 @@ export function createSelection({ info, active, say, closeMenu, schedule }) {
|
|
|
94
101
|
function sibling(node, forward) {
|
|
95
102
|
for (let next = forward ? node.nextElementSibling : withLeads(node)[0].previousElementSibling; next; next = forward ? next.nextElementSibling : next.previousElementSibling) {
|
|
96
103
|
const unit = unitElement(next);
|
|
97
|
-
if (unit && unit.parentElement === node.parentElement && unit !== node) return unit;
|
|
104
|
+
if (unit && unit.parentElement === node.parentElement && unit !== node && !folded(unit)) return unit;
|
|
98
105
|
}
|
|
99
106
|
return null;
|
|
100
107
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OLD_PROTOCOL, PROTOCOL, PROTOCOLS, VERSION, keyCombo } from "../../shared/session.mjs";
|
|
2
|
+
import { lookOf, lookOfElement } from "../../shared/document-look.mjs";
|
|
2
3
|
|
|
3
4
|
// The report page inside a workbench (an iframe of the same origin): it answers a few commands and
|
|
4
5
|
// tells the workbench its state, never the report's content. Nothing happens when the page is not
|
|
@@ -7,11 +8,13 @@ import { OLD_PROTOCOL, PROTOCOL, PROTOCOLS, VERSION, keyCombo } from "../../shar
|
|
|
7
8
|
// A message counts only from the parent window, of this origin and protocol. A command names the
|
|
8
9
|
// report it is meant for: one for another report (the iframe went to another page meanwhile) is
|
|
9
10
|
// refused. Every command gets exactly one reply, once it is done. Besides the editor's commands the
|
|
10
|
-
// session itself answers
|
|
11
|
-
// storing it), `
|
|
11
|
+
// session itself answers five: `theme` (show the report light or dark, or as the reader chose, without
|
|
12
|
+
// storing it), `look` (show the document in a look, 字体与布局: { look }, src/shared/document-look.mjs; the
|
|
13
|
+
// workbench writes it into doc.json), `keys` (key combinations to hand to the workbench when the page does not use them),
|
|
12
14
|
// `scroll-top` (back to the top when scrolled down) and `close-card` (the detail card on top closes as a
|
|
13
15
|
// press on its backdrop closes it: the workbench asks on a press on its own bars, or Esc there, while
|
|
14
|
-
// the page says a card is open, `card` in what it sends).
|
|
16
|
+
// the page says a card is open, `card` in what it sends). A card stands in the middle of the
|
|
17
|
+
// workbench's window, not of the frame (centreCards, below).
|
|
15
18
|
|
|
16
19
|
/** A page opened in a frame on its own (?solo), not as the workbench's report: no session, its own notices. */
|
|
17
20
|
export const SOLO = new URLSearchParams(location.search).has("solo");
|
|
@@ -34,6 +37,7 @@ export const SOLO = new URLSearchParams(location.search).has("solo");
|
|
|
34
37
|
*/
|
|
35
38
|
export function connectWorkbench({ slug, snapshot, commands, modals }) {
|
|
36
39
|
if (window.parent === window || SOLO) return null;
|
|
40
|
+
centreCards(modals);
|
|
37
41
|
// The id the workbench speaks (src/shared/session.mjs, OLD_PROTOCOL): null until its first command,
|
|
38
42
|
// and meanwhile every message goes under both, so that a workbench of either version hears it.
|
|
39
43
|
let spoken = null;
|
|
@@ -66,6 +70,13 @@ export function connectWorkbench({ slug, snapshot, commands, modals }) {
|
|
|
66
70
|
dispatchEvent(new CustomEvent("doc-theme-request", { detail: { theme } })); // the kit's ui.js
|
|
67
71
|
return { ok: true };
|
|
68
72
|
},
|
|
73
|
+
// The document's look shown at once (the kit's ui.js puts it on <html> and lays out again), never
|
|
74
|
+
// stored here: the workbench writes doc.json. Answers with the look the page shows then.
|
|
75
|
+
async look({ look = null }) {
|
|
76
|
+
if (look !== null && (typeof look !== "object" || Array.isArray(look))) return { ok: false, reason: "look" };
|
|
77
|
+
dispatchEvent(new CustomEvent("doc-look-request", { detail: { look: lookOf(look) } }));
|
|
78
|
+
return { ok: true, look: lookOfElement(document.documentElement) };
|
|
79
|
+
},
|
|
69
80
|
async keys({ keys: list }) {
|
|
70
81
|
if (!Array.isArray(list) || !list.every((key) => typeof key === "string")) return { ok: false, reason: "keys" };
|
|
71
82
|
keys = new Set(list);
|
|
@@ -115,3 +126,43 @@ export function connectWorkbench({ slug, snapshot, commands, modals }) {
|
|
|
115
126
|
workspace: () => send("workspace", {}),
|
|
116
127
|
};
|
|
117
128
|
}
|
|
129
|
+
|
|
130
|
+
// A detail card stands in the middle of the workbench's window, as the workbench's own dialogs do
|
|
131
|
+
// (组件列表, 写作偏好), not in the middle of this frame right of its sidebar and under its top bar;
|
|
132
|
+
// the workbench covers its bars while the page says a card is open (`card`), the card's backdrop
|
|
133
|
+
// the rest. The kit's dialog.css puts the card's middle at --doc-dialog-centre-x and -y, as far as
|
|
134
|
+
// the frame lets it stay clear of its sides, and makes it 80% as high as --doc-dialog-window-height.
|
|
135
|
+
// They are read from the frame's place in the workbench's window (of this origin, as every message
|
|
136
|
+
// of the session is), and said in a sheet the document adopts, which is in no markup (a copy of the
|
|
137
|
+
// view, the file); again as the frame changes size (the window resized, the sidebar opened, closed or
|
|
138
|
+
// dragged wider) and as a card opens or closes.
|
|
139
|
+
function centreCards(modals) {
|
|
140
|
+
let sheet;
|
|
141
|
+
try {
|
|
142
|
+
sheet = new CSSStyleSheet();
|
|
143
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
144
|
+
} catch {
|
|
145
|
+
return; // a browser with no constructed sheets: the card in the middle of the frame
|
|
146
|
+
}
|
|
147
|
+
let said = null;
|
|
148
|
+
const place = () => {
|
|
149
|
+
let rule = "";
|
|
150
|
+
try {
|
|
151
|
+
const frame = window.frameElement;
|
|
152
|
+
const outer = frame?.ownerDocument.documentElement;
|
|
153
|
+
if (outer) {
|
|
154
|
+
const box = frame.getBoundingClientRect();
|
|
155
|
+
const style = frame.ownerDocument.defaultView.getComputedStyle(frame);
|
|
156
|
+
const left = box.left + frame.clientLeft + parseFloat(style.paddingLeft);
|
|
157
|
+
const top = box.top + frame.clientTop + parseFloat(style.paddingTop);
|
|
158
|
+
rule = `:root { --doc-dialog-centre-x: ${outer.clientWidth / 2 - left}px; --doc-dialog-centre-y: ${outer.clientHeight / 2 - top}px; --doc-dialog-window-height: ${outer.clientHeight}px; }`;
|
|
159
|
+
}
|
|
160
|
+
} catch { /* a frame of another origin's page, or one taken away: in the middle of the frame */ }
|
|
161
|
+
if (rule === said) return;
|
|
162
|
+
said = rule;
|
|
163
|
+
sheet.replaceSync(rule);
|
|
164
|
+
};
|
|
165
|
+
place();
|
|
166
|
+
addEventListener("resize", place);
|
|
167
|
+
modals.listen(place);
|
|
168
|
+
}
|
|
@@ -2,7 +2,7 @@ import { belowControl, colorSection, colorSwatch, FORMAT_ICONS, formatLook, list
|
|
|
2
2
|
import { HIGHLIGHTS, TEXT_COLORS } from "../marks.mjs";
|
|
3
3
|
import { placeBox } from "../placement.mjs";
|
|
4
4
|
import { hideTooltip, shortcut } from "../tooltip.mjs";
|
|
5
|
-
import { element, fadeIn, fadeOut, mount } from "../ui.mjs";
|
|
5
|
+
import { addStyle, element, fadeIn, fadeOut, mount } from "../ui.mjs";
|
|
6
6
|
import { alignItems } from "./aligns.mjs";
|
|
7
7
|
import { TABLE_ICONS } from "./icons.mjs";
|
|
8
8
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
@@ -19,6 +19,8 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
19
19
|
const APPEAR = 120;
|
|
20
20
|
const PANEL = 120;
|
|
21
21
|
const GAP = 8; // between the cells and the bar
|
|
22
|
+
// A cell's fill is a block's: its swatches and its "A" show --doc-fill, not the words' deeper fill the kit gives a span.
|
|
23
|
+
const STYLE = `.ge-cell-bar :is(.ge-ink-sample, .ge-swatch > span)[class*="doc-highlight-"] { background: var(--doc-fill); }`;
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* run(command, value): "color" with { kind: "text" | "background", color } (null: none), "bold", "align" with an
|
|
@@ -27,6 +29,7 @@ const GAP = 8; // between the cells and the bar
|
|
|
27
29
|
*/
|
|
28
30
|
export function createCellBar(run) {
|
|
29
31
|
formatLook();
|
|
32
|
+
addStyle(STYLE);
|
|
30
33
|
const sample = element("span", { class: "ge-ink-sample", text: "A" });
|
|
31
34
|
const tools = {
|
|
32
35
|
color: tool("color", t("颜色"), sample, () => toggle("colors")),
|
|
@@ -15,7 +15,7 @@ import { createCellLists } from "./lists.mjs";
|
|
|
15
15
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
16
16
|
|
|
17
17
|
// Selected cells of a table and the keys in a table (notion-lite's, src/blocks/table/edit/; the
|
|
18
|
-
// rules in table-math-plan-2026-10-18.md), with the cells' bar (cell-bar.mjs) over them.
|
|
18
|
+
// rules in notes/plans/table-math-plan-2026-10-18.md), with the cells' bar (cell-bar.mjs) over them.
|
|
19
19
|
//
|
|
20
20
|
// - Selecting: a press in a cell dragged into another selects the cells between (the text selection
|
|
21
21
|
// goes; back in the first, that one cell, no caret); Shift+click from the selection's anchor (or
|
|
@@ -20,7 +20,7 @@ export const TABLE_ICONS = {
|
|
|
20
20
|
// A row and a column taken out: the table with the row (column) crossed.
|
|
21
21
|
removeRow: svg('<rect x="3.25" y="4.25" width="13.5" height="11.5" rx="2"/><path d="M3.25 8.25h13.5M3.25 11.75h13.5"/><path d="m8.25 8.75 3.5 2.5M11.75 8.75l-3.5 2.5"/>'),
|
|
22
22
|
removeColumn: svg('<rect x="3.25" y="4.25" width="13.5" height="11.5" rx="2"/><path d="M8.25 4.25v11.5M11.75 4.25v11.5"/><path d="m8.75 8.25 2.5 3.5M11.25 8.25l-2.5 3.5"/>'),
|
|
23
|
-
// 均分列宽: a table of three equal columns;
|
|
23
|
+
// 均分列宽: a table of three equal columns; 自动调整列宽: two edges and the arrows of a width between them.
|
|
24
24
|
even: svg('<rect x="3.25" y="4.25" width="13.5" height="11.5" rx="2"/><path d="M7.75 4.25v11.5M12.25 4.25v11.5"/>'),
|
|
25
25
|
autoWidth: svg('<path d="M3.25 4v12M16.75 4v12"/><path d="M6.25 10h7.5M8.25 8l-2 2 2 2M11.75 8l2 2-2 2"/>'),
|
|
26
26
|
// 合并单元格: a cell's two halves pushed together; 拆分单元格: a cell pulled apart at its middle line.
|
|
@@ -24,7 +24,7 @@ import { createColumnWidths } from "./widths.mjs";
|
|
|
24
24
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
25
25
|
|
|
26
26
|
// A table's own tools (notion-lite's simple table, src/blocks/table/; the plan's T2, its rules in
|
|
27
|
-
// table-math-plan-2026-10-18.md), for a table written as the kit writes one (the unit the server marked GRID):
|
|
27
|
+
// notes/plans/table-math-plan-2026-10-18.md), for a table written as the kit writes one (the unit the server marked GRID):
|
|
28
28
|
//
|
|
29
29
|
// - The selection: cells (a rectangle between two of them, cells.mjs, grown so that it cuts no
|
|
30
30
|
// merged cell), rows or columns, of one table; { unit, type: "cell" | "row" | "column", anchor,
|
|
@@ -535,7 +535,7 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
535
535
|
if (type === "column") {
|
|
536
536
|
// The columns' widths: shared out equally (several selected); the kit's own, or fixed as they are.
|
|
537
537
|
if (to > from) items.push({ label: t("均分列宽"), icon: ICONS.even, run: () => widths.even(unit, from, to) });
|
|
538
|
-
items.push({ label: t("
|
|
538
|
+
items.push({ label: t("自动调整列宽"), icon: ICONS.autoWidth, run: () => widths.fit(unit) });
|
|
539
539
|
}
|
|
540
540
|
items.push(
|
|
541
541
|
{ label: t("创建副本"), icon: ICONS.duplicate, hint: "Ctrl+D", run: () => duplicate(unit, type, from, to) },
|
|
@@ -918,16 +918,21 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
918
918
|
|
|
919
919
|
addEventListener("scroll", () => { if (!drag) draw(); }, { passive: true, capture: true });
|
|
920
920
|
|
|
921
|
-
// The rows of a table the tools manage.
|
|
921
|
+
// The rows of a table the tools manage. 一键优化 is a split row (menu.mjs): its words run it, its ›
|
|
922
|
+
// opens two of its parts to run alone, 自动调整列宽 and 识别数字列 (the user's, 2026-11-07).
|
|
922
923
|
function toolRows(unit) {
|
|
923
924
|
const state = tableState(unit);
|
|
924
925
|
return [
|
|
925
926
|
{ label: t("标题行"), icon: ICONS.headerRow, checked: state.headerRow, run: () => headerRow(unit, state) },
|
|
926
927
|
{ label: t("标题列"), icon: ICONS.headerColumn, checked: state.headerColumn, run: () => change(unit, () => setHeaderColumn(unit, !state.headerColumn), state.headerColumn ? t("已取消标题列") : t("已设为标题列")) },
|
|
927
|
-
{
|
|
928
|
-
|
|
928
|
+
{
|
|
929
|
+
label: t("一键优化"), icon: ICONS.magic, run: () => optimize(unit),
|
|
930
|
+
submenu: () => [
|
|
931
|
+
{ label: t("自动调整列宽"), icon: ICONS.autoWidth, run: () => widths.fit(unit) },
|
|
932
|
+
{ label: t("识别数字列"), icon: ICONS.numbers, run: () => numbers(unit) },
|
|
933
|
+
],
|
|
934
|
+
},
|
|
929
935
|
{ label: t("均分列宽"), icon: ICONS.even, run: () => widths.even(unit) },
|
|
930
|
-
{ label: t("识别数字列"), icon: ICONS.numbers, run: () => numbers(unit) },
|
|
931
936
|
];
|
|
932
937
|
}
|
|
933
938
|
|
|
@@ -974,8 +979,8 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
974
979
|
/**
|
|
975
980
|
* A table's rows of its block menu (notion-lite's 表格 section, under the menu's one title): a
|
|
976
981
|
* table of the kind these tools manage has 标题行 and 标题列, switches (checked when on); then
|
|
977
|
-
* actions, each applied once (no check):
|
|
978
|
-
* in its frame has its size's rows after them (layoutRows). [] for any other block.
|
|
982
|
+
* actions, each applied once (no check): 一键优化 › (自动调整列宽, 识别数字列 in its submenu),
|
|
983
|
+
* 均分列宽; any table in its frame has its size's rows after them (layoutRows). [] for any other block.
|
|
979
984
|
*/
|
|
980
985
|
menuItems(target) {
|
|
981
986
|
if (!target?.matches?.('[data-ge-unit-kind="table"]')) return [];
|
|
@@ -2,6 +2,7 @@ import { SPAN } from "../../../shared/media.mjs";
|
|
|
2
2
|
import { COLUMN_WIDTH, MIN_COLUMN_WIDTH, widthOf } from "../../components/table/index.mjs";
|
|
3
3
|
import { relayout, tableParts, tableState } from "../../components/table/page.mjs";
|
|
4
4
|
import { widthHolder } from "../../../shared/table-slots.mjs";
|
|
5
|
+
import { GONE_WORDS } from "../core.mjs";
|
|
5
6
|
import { gestureLock } from "../gesture.mjs";
|
|
6
7
|
import { drawBox } from "../overlay-layer.mjs";
|
|
7
8
|
import { hideTooltip, tooltip } from "../tooltip.mjs";
|
|
@@ -15,7 +16,7 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
15
16
|
// index.mjs COLUMN_WIDTH; shared/table-slots.mjs widthHolder: a merged cell holds none), the kit laying the table
|
|
16
17
|
// out at them (tables/layout.js manualLayout); the first
|
|
17
18
|
// drag of a table the kit lays out gives every column the width it has then. A double click fits
|
|
18
|
-
// the column to its widest cell, unwrapped. The menus'
|
|
19
|
+
// the column to its widest cell, unwrapped. The menus' 自动调整列宽 takes the widths off (the kit's
|
|
19
20
|
// again) or gives each column its width as it is; 均分列宽 shares the columns' room out equally.
|
|
20
21
|
// Sizing columns takes the table's own width off (--doc-span: the table is as wide as its
|
|
21
22
|
// columns again).
|
|
@@ -114,18 +115,22 @@ export function createColumnWidths(tools) {
|
|
|
114
115
|
relayout(unit);
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
// A column fitted to its widest cell of it alone, unwrapped (+1px)
|
|
118
|
+
// A column fitted to its widest cell of it alone, unwrapped (+1px): by its words, not the words an
|
|
119
|
+
// Agent took out that the page shows in it (changes/: out of the layout while it is measured).
|
|
118
120
|
function fit(unit, index) {
|
|
119
121
|
const { slots } = tableState(unit);
|
|
120
122
|
const widest = Math.max(0, ...[...slots.places].filter(([, place]) => place.c === index && place.cols === 1).map(([cell]) => {
|
|
121
123
|
const style = getComputedStyle(cell);
|
|
122
124
|
const was = cell.style.whiteSpace;
|
|
125
|
+
const gone = [...cell.querySelectorAll(GONE_WORDS)];
|
|
126
|
+
for (const mark of gone) mark.hidden = true;
|
|
123
127
|
cell.style.whiteSpace = "nowrap";
|
|
124
128
|
const range = document.createRange();
|
|
125
129
|
range.selectNodeContents(cell);
|
|
126
130
|
const width = range.getBoundingClientRect().width + parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
|
|
127
131
|
cell.style.whiteSpace = was;
|
|
128
132
|
if (cell.getAttribute("style") === "") cell.removeAttribute("style");
|
|
133
|
+
for (const mark of gone) mark.hidden = false;
|
|
129
134
|
return width;
|
|
130
135
|
}));
|
|
131
136
|
const widths = shown(unit);
|
|
@@ -291,9 +296,9 @@ export function createColumnWidths(tools) {
|
|
|
291
296
|
/** The edges plain again (the tools no longer stand by the table). */
|
|
292
297
|
disarm() { armed = null; },
|
|
293
298
|
get dragging() { return drag !== null; },
|
|
294
|
-
/**
|
|
299
|
+
/** 自动调整列宽: the kit's widths, from the columns' words (the reader's taken off; sizing a column fixes them again). */
|
|
295
300
|
fit(unit) {
|
|
296
|
-
if (set(unit, null, t("
|
|
301
|
+
if (set(unit, null, t("已自动调整列宽"))) return true;
|
|
297
302
|
tools.say(t("列宽已按内容调整,无需再调。"));
|
|
298
303
|
return false;
|
|
299
304
|
},
|
|
@@ -4,6 +4,7 @@ import { boxRole } from "../../components/registry.mjs";
|
|
|
4
4
|
import { UNIT, can, isHost, mergeInto, partsOf, removalTarget, takeOut, textEnd, unitElement } from "../core.mjs";
|
|
5
5
|
import { atomOf, caretAt, caretAtOffset, dragPlace, editable, hosts, pointRange, select } from "../caret.mjs";
|
|
6
6
|
import { edgeFollower } from "../edge-scroll.mjs";
|
|
7
|
+
import { FOLDED } from "../folded.mjs";
|
|
7
8
|
import { gapScore } from "../geometry.mjs";
|
|
8
9
|
import { endsGesture, gestureLock } from "../gesture.mjs";
|
|
9
10
|
import { joinCode } from "../marks.mjs";
|
|
@@ -21,8 +22,8 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
21
22
|
// the window's edges), drawn with the CSS Custom Highlight API in the selection's own colors, the
|
|
22
23
|
// browser's selecting held off meanwhile; back in that block, the browser's again. Shift+click in
|
|
23
24
|
// another block selects from the caret to there.
|
|
24
|
-
// Its text is that of body text, headings and list items (in quotes, highlight blocks, columns…).
|
|
25
|
-
// A block holding none (a code block, a table, a file, an image, a formula block, a
|
|
25
|
+
// Its text is that of body text, headings and list items (in quotes, highlight blocks, callouts, columns…).
|
|
26
|
+
// A block holding none (a code block, a table, a file, an image, a formula block, a
|
|
26
27
|
// divider…) is taken whole as it passes it, filled as a selected block is (selection/ mark), its
|
|
27
28
|
// own text not marked: over one, an end of the selection stands before it or after it, as the drag
|
|
28
29
|
// goes, and a press begun in one (its code, a cell) takes it whole once it has left it.
|
|
@@ -250,6 +251,20 @@ export function createTextSelection({ isEditing, struct, say, keepText, selectUn
|
|
|
250
251
|
if (host === selection.end.host) part.setEnd(selection.end.node, selection.end.offset);
|
|
251
252
|
return { host, range: part };
|
|
252
253
|
});
|
|
254
|
+
// What a folded heading hides (fold/) between the ends goes with the selection, wherever in the
|
|
255
|
+
// heading's words it begins: its text blocks' text in full and its other blocks whole (below), as
|
|
256
|
+
// a deletion takes the blocks between the ends whole (unitsBetween), so a copy or a cut carries
|
|
257
|
+
// what the deletion takes. An end is never in what is hidden (hosts() are those shown), so what is
|
|
258
|
+
// hidden and in the range lies between the ends.
|
|
259
|
+
const hidden = [...document.querySelectorAll(`main [${FOLDED}] :is([data-ge-block], [data-ge-new]), main :is([data-ge-block], [data-ge-new])[${FOLDED}]`)]
|
|
260
|
+
.filter((host) => isHost(host) && isText(host) && range.intersectsNode(host) && !folded(host));
|
|
261
|
+
for (const host of hidden) {
|
|
262
|
+
const part = document.createRange();
|
|
263
|
+
part.setStart(host, 0);
|
|
264
|
+
part.setEnd(host, textEnd(host));
|
|
265
|
+
texts.push({ host, range: part });
|
|
266
|
+
}
|
|
267
|
+
if (hidden.length > 0) texts.sort((a, b) => (a.host.compareDocumentPosition(b.host) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1));
|
|
253
268
|
const wholes = [...wholeClosed];
|
|
254
269
|
for (const unit of document.querySelectorAll(`main :is(${UNIT})`)) {
|
|
255
270
|
if (wholes.some((other) => other === unit || other.contains(unit))) continue;
|
|
@@ -348,7 +363,7 @@ export function createTextSelection({ isEditing, struct, say, keepText, selectUn
|
|
|
348
363
|
|
|
349
364
|
// Drawn again over its text blocks turned into another type (`texts`: where its pieces are now, in
|
|
350
365
|
// page order), as notion-lite's placeCaret(keep) keeps a selection; when one of them holds no text of
|
|
351
|
-
// the body now (a
|
|
366
|
+
// the body now (a disclosure's title, code), let go instead. Returns whether it is drawn.
|
|
352
367
|
function reselect(texts) {
|
|
353
368
|
if (!cross) return false;
|
|
354
369
|
if (texts.length === 0 || !texts.every(({ host }) => host.isConnected && isText(host))) {
|
|
@@ -22,7 +22,7 @@ const STYLE = `
|
|
|
22
22
|
const DELAY = 150;
|
|
23
23
|
const GAP = 6;
|
|
24
24
|
const MAC = /Mac|iPhone|iPad/.test(globalThis.navigator?.platform ?? "");
|
|
25
|
-
const KEYS = MAC ? { mod: "⌘", shift: "⇧", alt: "⌥" } : { mod: "Ctrl", shift: "⇧", alt: "Alt" };
|
|
25
|
+
const KEYS = MAC ? { mod: "⌘", shift: "⇧", alt: "⌥", enter: "↩" } : { mod: "Ctrl", shift: "⇧", alt: "Alt", enter: "Enter" };
|
|
26
26
|
let shown = null; // { node, tip }
|
|
27
27
|
let timer = 0;
|
|
28
28
|
|
|
@@ -6,8 +6,9 @@ import { t } from "../../shared/i18n/index.mjs";
|
|
|
6
6
|
|
|
7
7
|
// The types a block can be turned into, as icons side by side (as Feishu's block menu), the block's
|
|
8
8
|
// own type marked: the top of the block menu (block-menu/) and the format bar's "turn into"
|
|
9
|
-
// (format-bar/). The buttons are the format bar's (32×28px, 4px apart
|
|
10
|
-
//
|
|
9
|
+
// (format-bar/). The buttons are the format bar's (32×28px, 4px apart; the current one marked as
|
|
10
|
+
// a pressed tool of the bar is, its icon's lines in the bar's blue, --ge-accent, and no fill of its
|
|
11
|
+
// own, so the pointer's gray fills it as any other), so both read alike. Each icon names its type, and its Markdown shortcut, in a tooltip.
|
|
11
12
|
// A type that says `corner` has no cell of its own but the gray corner of that type's (a split
|
|
12
13
|
// button): 小字's of 文本's, so 文本 and the headings fill one line. The corner shows only with the
|
|
13
14
|
// pointer (or the keys) on the cell, or, blue, when the block is of that type. It is a click target
|
|
@@ -26,7 +27,7 @@ const STYLE = `
|
|
|
26
27
|
.ge-type { display: flex; align-items: center; justify-content: center; width: ${CELL}px; height: 28px; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--ge-ico); cursor: pointer; transition: background 20ms ease-in; }
|
|
27
28
|
.ge-type:hover, .ge-type[data-active] { background: var(--ge-hover); }
|
|
28
29
|
.ge-type:active { background: var(--ge-press); }
|
|
29
|
-
.ge-type[aria-current="true"] {
|
|
30
|
+
.ge-type[aria-current="true"] { color: var(--ge-accent); }
|
|
30
31
|
.ge-type-pair { position: relative; border-radius: 6px; overflow: hidden; }
|
|
31
32
|
.ge-type-corner { position: absolute; right: 0; bottom: 0; width: ${CORNER}px; height: ${CORNER}px; padding: 0; border: 0; background: transparent; cursor: pointer; }
|
|
32
33
|
.ge-type-corner::before { content: ""; position: absolute; inset: 0; background: light-dark(rgb(55 53 47 / 12%), rgb(255 255 255 / 13%)); clip-path: path("${TRIANGLE}"); opacity: 0; transition: background 20ms ease-in, opacity 120ms ease-out; }
|
|
@@ -12,7 +12,11 @@ import { ENTRIES } from "../../components/registry.mjs";
|
|
|
12
12
|
// the caret in a pad (pads.mjs). Code blocks are text blocks of their own (components/code/) and
|
|
13
13
|
// have none of this. A component's entry in the text (`entry`: a chip as padded as code) has the
|
|
14
14
|
// same stops, and Backspace at the stop after it (Delete at the one before it) takes the whole
|
|
15
|
-
// entry away (
|
|
15
|
+
// entry away (takeEntry, on the key's keydown through beforeDelete and on its beforeinput where an
|
|
16
|
+
// input method's keydown says 229; it says what the page says of that, editor.mjs records the step).
|
|
17
|
+
// A Backspace that takes the last character between inline code or an entry and the caret after it
|
|
18
|
+
// leaves the caret outside it, at the stop after it (deletingBack, deletedBack): the browser puts it
|
|
19
|
+
// in at the end (an entry's: left of its icon), its end and the text after it one place to it.
|
|
16
20
|
|
|
17
21
|
const EDGED = ["code", ...ENTRIES.map(({ at }) => at)].join(", ");
|
|
18
22
|
const isEdged = (node) => node?.nodeType === 1 && node.matches(EDGED);
|
|
@@ -187,28 +191,48 @@ export function codePress(host, x, y) {
|
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
/**
|
|
190
|
-
* Backspace
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
+
* Backspace (`back`) at the stop after a component's entry, or Delete at the one before it: the whole
|
|
195
|
+
* entry goes, as a chip of the text does, the caret where it stood, and what the page says of that is
|
|
196
|
+
* returned; nothing otherwise. The keydown's (beforeDelete) and the beforeinput's, for a key whose
|
|
197
|
+
* keydown the editor passed by (an input method's, keyCode 229: editor.mjs). The pads beside it go
|
|
198
|
+
* with it, before the text either side of it is joined, so that no zero-width space is left in that
|
|
199
|
+
* text (a pad an undo step put back is part of the text beside it by then).
|
|
194
200
|
*/
|
|
195
|
-
export function
|
|
201
|
+
export function takeEntry(host, back) {
|
|
196
202
|
const selection = getSelection();
|
|
197
203
|
if (!selection.rangeCount || !selection.isCollapsed) return;
|
|
198
|
-
const back = key === "Backspace";
|
|
199
|
-
// At the stop after an entry (or before it, for Delete): the whole entry goes.
|
|
200
204
|
const outside = caretAtEdge(host);
|
|
201
205
|
const entry = outside && !outside.inside ? entryOf(outside.code) : undefined;
|
|
202
|
-
if (entry
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return entry.removed;
|
|
206
|
+
if (!entry || (back ? outside.side !== "end" : outside.side !== "start")) return;
|
|
207
|
+
const { code } = outside;
|
|
208
|
+
for (const [next, trim] of [["previousSibling", new RegExp(`${ZWSP}+$`)], ["nextSibling", new RegExp(`^${ZWSP}+`)]]) {
|
|
209
|
+
for (let node = code[next]; node?.nodeType === 3; node = code[next]) {
|
|
210
|
+
node.data = node.data.replace(trim, "");
|
|
211
|
+
if (node.data !== "") break;
|
|
212
|
+
node.remove();
|
|
213
|
+
}
|
|
211
214
|
}
|
|
215
|
+
const caret = host.ownerDocument.createRange();
|
|
216
|
+
caret.setStartBefore(code);
|
|
217
|
+
caret.collapse(true);
|
|
218
|
+
code.remove();
|
|
219
|
+
host.normalize();
|
|
220
|
+
select(caret);
|
|
221
|
+
return entry.removed;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Backspace or Delete with the caret in a pad at an edge: the pad goes first, the caret where the
|
|
226
|
+
* key then takes the character beside it, as it would without the pad (keydown, before the browser
|
|
227
|
+
* deletes or the editor merges blocks). At the stop beside a component's entry the whole entry goes
|
|
228
|
+
* (takeEntry), and what the page says of that is returned; nothing otherwise.
|
|
229
|
+
*/
|
|
230
|
+
export function beforeDelete(host, key) {
|
|
231
|
+
const back = key === "Backspace";
|
|
232
|
+
const removed = takeEntry(host, back);
|
|
233
|
+
if (removed) return removed;
|
|
234
|
+
const selection = getSelection();
|
|
235
|
+
if (!selection.rangeCount || !selection.isCollapsed) return;
|
|
212
236
|
const pad = selection.anchorNode;
|
|
213
237
|
if (!isPad(pad) || !host.contains(pad)) return;
|
|
214
238
|
const edge = caretAtEdge(host);
|
|
@@ -226,3 +250,25 @@ export function beforeDelete(host, key) {
|
|
|
226
250
|
caret.collapse(true);
|
|
227
251
|
select(caret);
|
|
228
252
|
}
|
|
253
|
+
|
|
254
|
+
// The host whose caret a deletion backward found in text outside any inline code or entry (no pad),
|
|
255
|
+
// for the input after it (deletedBack).
|
|
256
|
+
let backFrom = null;
|
|
257
|
+
|
|
258
|
+
/** A deletion backward about to happen (its beforeinput): where the caret stands, for deletedBack. */
|
|
259
|
+
export function deletingBack(host) {
|
|
260
|
+
const selection = getSelection();
|
|
261
|
+
const node = selection.rangeCount && selection.isCollapsed ? selection.anchorNode : null;
|
|
262
|
+
backFrom = node?.nodeType === 3 && !isPad(node) && !node.parentElement.closest(EDGED) ? host : null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The deletion done (its input): a caret it took from the text after inline code or an entry into
|
|
267
|
+
* it, at its end, goes back out to the stop after it, where the reader deleting back stays.
|
|
268
|
+
*/
|
|
269
|
+
export function deletedBack(host) {
|
|
270
|
+
const was = backFrom === host;
|
|
271
|
+
backFrom = null;
|
|
272
|
+
const edge = was && caretAtEdge(host);
|
|
273
|
+
if (edge && edge.inside && edge.side === "end") padCaret(edge.code, "after");
|
|
274
|
+
}
|
|
@@ -47,13 +47,12 @@ function writtenTogether(host, node, start, end) {
|
|
|
47
47
|
/**
|
|
48
48
|
* struct(parents, touched, mutate): the editor's undoable structural change (history.mjs). say: the
|
|
49
49
|
* status line. keepText(unit): why a block may not go (block-info.mjs). paragraphBefore(host): an
|
|
50
|
-
* empty paragraph before the block, the caret staying (insert/).
|
|
51
|
-
* block, the caret in it, where its box takes one (insert/). removeBlocks(units): the blocks
|
|
50
|
+
* empty paragraph before the block, the caret staying (insert/). removeBlocks(units): the blocks
|
|
52
51
|
* deleted as the selected ones are (selection/delete.mjs). adding(): the block the "/" menu the
|
|
53
52
|
* handle's "+" opened is open in (slash-menu/), or null. leave: where Enter and Backspace leave a
|
|
54
|
-
* container (leave.mjs).
|
|
53
|
+
* container (leave.mjs). showHidden(node): the folds hiding a block opened (fold/ showAround).
|
|
55
54
|
*/
|
|
56
|
-
export function createTyping({ struct, say, keepText, paragraphBefore,
|
|
55
|
+
export function createTyping({ struct, say, keepText, paragraphBefore, removeBlocks, selectBlock = () => {}, adding = () => null, leave, showHidden = () => {} }) {
|
|
57
56
|
// A key, asked of the component of the text block `host` and then of what stands around it, nearest
|
|
58
57
|
// first (docs/editor.md, "The order of keys"): whether it was theirs. `ctx` is notion-lite's, as
|
|
59
58
|
// much of it as a key reads, the block being an element here.
|
|
@@ -75,8 +74,7 @@ export function createTyping({ struct, say, keepText, paragraphBefore, paragraph
|
|
|
75
74
|
// in any editor, and so does the empty last paragraph of a panel), the rule of every block.
|
|
76
75
|
if (asked((keys) => keys.enter, host) || leave.enter(host)) return;
|
|
77
76
|
if (!can(host, "split")) {
|
|
78
|
-
|
|
79
|
-
if (!HOSTS[host.getAttribute("data-ge-kind")]?.opensAfter || !atEdge(host, true) || !paragraphAfter(host)) say(limitText(host, "split"));
|
|
77
|
+
say(limitText(host, "split"));
|
|
80
78
|
return;
|
|
81
79
|
}
|
|
82
80
|
const selection = getSelection();
|
|
@@ -159,8 +157,15 @@ export function createTyping({ struct, say, keepText, paragraphBefore, paragraph
|
|
|
159
157
|
return true;
|
|
160
158
|
}
|
|
161
159
|
|
|
160
|
+
// A block on the line above or below that a folded heading hides (fold/: data-ge-folded on it or
|
|
161
|
+
// around it): its fold opens first, so a join never takes in words out of sight.
|
|
162
|
+
const opened = (line) => {
|
|
163
|
+
if (line?.closest?.("[data-ge-folded]")) showHidden(line);
|
|
164
|
+
};
|
|
165
|
+
|
|
162
166
|
// Backspace at the start of a block. Returns whether it was handled.
|
|
163
167
|
function backspace(host) {
|
|
168
|
+
opened(previousLine(host));
|
|
164
169
|
// The block's component (a list item's: a nested item moves out a level, the first or last item of
|
|
165
170
|
// a top-level list leaves it as body text, an emptied item holding lists stays), then what stands
|
|
166
171
|
// around it (the item a block stands under; a node, at the start of its first line, holding
|
|
@@ -192,6 +197,7 @@ export function createTyping({ struct, say, keepText, paragraphBefore, paragraph
|
|
|
192
197
|
|
|
193
198
|
// Delete at the end of a block. Returns whether it was handled.
|
|
194
199
|
function forwardDelete(host) {
|
|
200
|
+
opened(nextLine(host));
|
|
195
201
|
// Its component (an empty item holding blocks stays), then what stands around it (at the end of a
|
|
196
202
|
// node's last block nothing is taken in).
|
|
197
203
|
if (asked((keys) => keys.delete, host)) return true;
|
|
@@ -15,7 +15,7 @@ import { BLOCK_TYPES, LEAVES_BY_ENTER, accepts } from "../../components/registry
|
|
|
15
15
|
// - A block of a type that says `resettable` (a quote's paragraph, a list item) turns into a
|
|
16
16
|
// paragraph where it stands, its container splitting around it (convert.mjs).
|
|
17
17
|
// Enter in a title that goes into its content (a disclosure, a card, a code block) is another rule
|
|
18
|
-
// (their components')
|
|
18
|
+
// (their components').
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* The empty last block of a box that says `leaveByEnter`, moved out after the box (the box goes if
|
|
@@ -2,8 +2,8 @@ import { attr, hasClass } from "./html-tree.mjs";
|
|
|
2
2
|
import { BLOCK_ID, randomId } from "./ids.mjs";
|
|
3
3
|
|
|
4
4
|
// 批注 (docs/workbench.md "Annotations", docs/editor.md "Annotations"): the reader's suggestions for a
|
|
5
|
-
// local Agent, each about a piece of a report: words of its text, or blocks taken whole. One
|
|
6
|
-
//
|
|
5
|
+
// local Agent, each about a piece of a report: words of its text, or blocks taken whole. One has
|
|
6
|
+
// words: an item with none is none (cleanItem). Kept in reports/<slug>/annotations.json, never in report.html:
|
|
7
7
|
//
|
|
8
8
|
// { v: 1, items: [{ id, created, updated, text, anchor }] }
|
|
9
9
|
// anchor: { kind: "text", blocks: [data-block-id…], quote, prefix, suffix }
|
|
@@ -389,12 +389,13 @@ export function cleanAnchor(anchor) {
|
|
|
389
389
|
export const sameWords = (a, b) => Boolean(a && b) && a.kind === b.kind && (a.blocks ?? []).join() === (b.blocks ?? []).join() && squash(a.quote) === squash(b.quote)
|
|
390
390
|
&& squash(a.prefix).slice(-CONTEXT_KEPT) === squash(b.prefix).slice(-CONTEXT_KEPT) && squash(a.suffix).slice(0, CONTEXT_KEPT) === squash(b.suffix).slice(0, CONTEXT_KEPT);
|
|
391
391
|
|
|
392
|
-
/** An annotation as the file keeps it, from what was sent (`now`: when it is saved), or null when it is none. */
|
|
392
|
+
/** An annotation as the file keeps it, from what was sent (`now`: when it is saved), or null when it is none (no id, no anchor, no words). */
|
|
393
393
|
export function cleanItem(item, now = new Date().toISOString()) {
|
|
394
394
|
if (!item || typeof item !== "object" || typeof item.id !== "string" || !ID.test(item.id) || typeof item.text !== "string") return null;
|
|
395
395
|
const anchor = cleanAnchor(item.anchor);
|
|
396
396
|
if (!anchor) return null;
|
|
397
397
|
const text = item.text.replace(/\r\n?/g, "\n").slice(0, TEXT_MOST);
|
|
398
|
+
if (!text.trim()) return null;
|
|
398
399
|
return { id: item.id, created: typeof item.created === "string" ? item.created : now, updated: now, text, anchor };
|
|
399
400
|
}
|
|
400
401
|
|
|
@@ -421,7 +422,6 @@ export const itemsText = (items) => (items.length === 0 ? null : `${JSON.stringi
|
|
|
421
422
|
export const MARKDOWN_WORDS = {
|
|
422
423
|
head: "以下是我对这份文档的批注,共 {0} 条。请逐条按“建议”修改底稿;每条给出它在文档里的位置、块的 data-block-id 和批注的原文。",
|
|
423
424
|
headOne: "以下是我对这份文档的一条批注。请按“建议”修改底稿;它给出在文档里的位置、块的 data-block-id 和批注的原文。",
|
|
424
|
-
marks: "没有“建议”的批注只是标记了一个位置,不用改。",
|
|
425
425
|
report: "文档",
|
|
426
426
|
source: "底稿",
|
|
427
427
|
block: "块",
|
|
@@ -441,22 +441,20 @@ const fill = (text, ...values) => text.replace(/\{(\d+)\}/g, (whole, index) => (
|
|
|
441
441
|
* then each annotation in page order: where it is (`where`: the tab, the headings above it, the
|
|
442
442
|
* disclosure it is in; `top`: before the report's first heading, said 页首, which no tab or heading
|
|
443
443
|
* named 正文 is taken for), its blocks' ids (as the file has them), its quote and the suggestion, each
|
|
444
|
-
* of its lines a paragraph (blank lines in it make no more of them).
|
|
445
|
-
* place: it has no such line, and a line under the first says what such a one is.
|
|
444
|
+
* of its lines a paragraph (blank lines in it make no more of them).
|
|
446
445
|
* data: { title, path, items: [{ where: [text], top, blocks: [id], whole, quote, text }] }.
|
|
447
446
|
*/
|
|
448
447
|
export function annotationsMarkdown({ title, path, items }, words = MARKDOWN_WORDS) {
|
|
449
448
|
const w = { ...MARKDOWN_WORDS, ...words };
|
|
450
449
|
const head = items.length === 1 && w.headOne ? w.headOne : fill(w.head, items.length);
|
|
451
|
-
const
|
|
452
|
-
const lines = [head, ...(suggestions.some((said) => said.length === 0) ? [w.marks] : []), "", `- ${w.report}${w.colon}${title}`, `- ${w.source}${w.colon}${path}`];
|
|
450
|
+
const lines = [head, "", `- ${w.report}${w.colon}${title}`, `- ${w.source}${w.colon}${path}`];
|
|
453
451
|
items.forEach((item, index) => {
|
|
454
452
|
const where = item.where?.length ? item.where.join(" › ") : item.top ? w.top : w.body;
|
|
455
453
|
const ids = item.blocks?.length ? item.blocks.map((id) => `\`data-block-id="${id}"\``).join(w.comma) + (item.whole ? w.whole : "") : w.noId;
|
|
456
454
|
lines.push("", `## ${index + 1}. ${where}`, "", `${w.block}${w.colon}${ids}`, "");
|
|
457
455
|
for (const line of String(item.quote ?? "").split("\n")) lines.push(`> ${line}`);
|
|
458
|
-
const said =
|
|
459
|
-
|
|
456
|
+
const said = String(item.text ?? "").replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
457
|
+
lines.push("", `${w.suggestion}${w.colon}${said[0] ?? ""}`);
|
|
460
458
|
for (const line of said.slice(1)) lines.push("", line);
|
|
461
459
|
});
|
|
462
460
|
return `${lines.join("\n")}\n`;
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
// after a line that is folded (the kit's styles/code.css asks for it as `@fold-dots`, which
|
|
4
4
|
// kit-build.mjs fills in; the editor's styles interpolate it). Its colors are the kit's tokens.
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* A folding line's arrow (markup; it takes the color of its button), pointing down; folded, it is
|
|
8
|
+
* turned to point at what it folds: a code block's and its lines' -90° (right: they stand left of
|
|
9
|
+
* the code), a heading's 90° (left: it stands right of the words, page/fold/).
|
|
10
|
+
*/
|
|
7
11
|
export const FOLD_ARROW = '<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M3.25 4.5h5.5L6 8z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>';
|
|
8
12
|
|
|
9
13
|
/** The three dots of a folded line's "…" (a CSS image). */
|