@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,7 +2,7 @@ import { isHost, nestedBlocks, serializeInner, unitsOf } from "../../core.mjs";
|
|
|
2
2
|
import { BLOCK_TYPES, COPIED_AS_ITSELF, COPY_KEEPS, TEXT_TYPES, askClipboard, clipboardOf, sizeValue, unitClipboard } from "../../../components/registry.mjs";
|
|
3
3
|
import { BLOCK_COLORS } from "../../../components/colors.mjs";
|
|
4
4
|
import { containerType, typeOf } from "../../convert.mjs";
|
|
5
|
-
import { pageOfType } from "../../page-objects.mjs";
|
|
5
|
+
import { pageOfType, pageOfUnit } from "../../page-objects.mjs";
|
|
6
6
|
import { referencedIds } from "../../../../shared/copy-ids.mjs";
|
|
7
7
|
|
|
8
8
|
// Selected blocks as the clipboard's own description (docs/editor.md, "Copying blocks"): each
|
|
@@ -64,6 +64,10 @@ export function copied(doc, block) {
|
|
|
64
64
|
if (block.report !== reportOf(doc)) return null;
|
|
65
65
|
const unit = doc.querySelector(`main [data-ge-unit="${block.unit}"]`);
|
|
66
66
|
if (!unit || unit.getAttribute("data-ge-unit-kind") !== block.kind) return null;
|
|
67
|
+
// One whose unit's copy cannot stand for it (`copiesAsItself`: an instance with slots) is made of
|
|
68
|
+
// what it carries, here too: that copy would hold blocks the file writes whole, with ids no answer
|
|
69
|
+
// of a commit gives the page (source/apply.mjs restoredText names the block alone).
|
|
70
|
+
if (pageOfUnit(unit)?.copiesAsItself?.(unit) === false) return null;
|
|
67
71
|
const copy = copyOf(unit);
|
|
68
72
|
return (typeof block.data?.html === "string" ? copy.outerHTML === block.html : copy.textContent === block.text) ? unit : null;
|
|
69
73
|
}
|
|
@@ -84,12 +88,6 @@ const plain = (node) => (node?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
|
84
88
|
/** What copies of a block the page could make: { type, html, text, blocks, lists }, or null. */
|
|
85
89
|
export function describe(node) {
|
|
86
90
|
const kind = node.getAttribute("data-ge-unit-kind");
|
|
87
|
-
if (COPIES.has(kind)) {
|
|
88
|
-
if (!node.hasAttribute("data-ge-unit") || node.hasAttribute("data-ge-new")) return null; // not in the file yet
|
|
89
|
-
const copy = copyOf(node);
|
|
90
|
-
const data = unitClipboard(node).data?.(node, { serializeInner, copyOf, clipboardOf, copiedType, page: pageOfType });
|
|
91
|
-
return { type: "copy", kind, unit: Number(node.getAttribute("data-ge-unit")), report: reportOf(node.ownerDocument), text: copy.textContent, html: copy.outerHTML, ...(data ? { data } : {}) };
|
|
92
|
-
}
|
|
93
91
|
// What an element holds: its units. One that is no box has none (a list of a class of its own:
|
|
94
92
|
// source/units.mjs boxKind), and what stands in it is read as it stands.
|
|
95
93
|
const holding = (box) => {
|
|
@@ -98,6 +96,17 @@ export function describe(node) {
|
|
|
98
96
|
const blocks = (units.length > 0 || box.hasAttribute("data-ge-box") ? units : [...box.children]).map(describe);
|
|
99
97
|
return blocks.every(Boolean) ? blocks : null;
|
|
100
98
|
};
|
|
99
|
+
// Copied as itself. One whose unit's copy cannot stand for it (`copiesAsItself` of its page object:
|
|
100
|
+
// an instance with slots, whose blocks that copy would write whole) is copied by what it carries
|
|
101
|
+
// alone (`data`, those blocks described apart), and is no copy where it carries nothing (a block in a
|
|
102
|
+
// slot the page cannot make).
|
|
103
|
+
if (COPIES.has(kind)) {
|
|
104
|
+
if (!node.hasAttribute("data-ge-unit") || node.hasAttribute("data-ge-new")) return null; // not in the file yet
|
|
105
|
+
const copy = copyOf(node);
|
|
106
|
+
const data = unitClipboard(node).data?.(node, { serializeInner, copyOf, clipboardOf, copiedType, page: pageOfType, holding });
|
|
107
|
+
if (!data && pageOfUnit(node)?.copiesAsItself?.(node) === false) return null;
|
|
108
|
+
return { type: "copy", kind, unit: Number(node.getAttribute("data-ge-unit")), report: reportOf(node.ownerDocument), text: copy.textContent, html: copy.outerHTML, ...(data ? { data } : {}) };
|
|
109
|
+
}
|
|
101
110
|
const what = { page: pageOfType, holding, describe, copiedType, isHost, nestedBlocks, serializeInner, plain, colorOf, sizeValue };
|
|
102
111
|
const block = askClipboard("describe", node, what);
|
|
103
112
|
if (block !== undefined) return block;
|
|
@@ -10,16 +10,16 @@ import { WRITERS, heldToWords } from "./writers.mjs";
|
|
|
10
10
|
// Copied blocks for other programs (notion-lite serialize.js, after Notion 210528 / 389867: the
|
|
11
11
|
// Markdown its export writes, and HTML that reads as the same), each block by the writer of its kind
|
|
12
12
|
// (writers.mjs). Pasted back here without the clipboard's own data, both read as the same blocks
|
|
13
|
-
// (import/), but for a callout: its Markdown a highlight block, its HTML a
|
|
13
|
+
// (import/), but for a callout: its Markdown a highlight block, its HTML a group of its blocks.
|
|
14
14
|
// text/plain Markdown: # … #### headings, "- " / "1. " / "- [ ] " items (an item's lists 4 spaces
|
|
15
|
-
// in), "> " quotes, <aside> callouts (a highlight block: its emoji
|
|
16
|
-
//
|
|
15
|
+
// in), "> " quotes, <aside> callouts (a highlight block and a callout: its emoji, a highlight
|
|
16
|
+
// block's, and the text of its first block, its other blocks),
|
|
17
17
|
// ``` code (its language), $$ formula blocks (an empty one: none), | tables |, ---, a disclosure as an item
|
|
18
18
|
// holding its blocks, a tabs area as each tab's name and blocks (a row of columns, copied
|
|
19
19
|
// as itself, as its text); **bold** *italic* ~~strike~~ `code` $formula$, links as their text
|
|
20
20
|
// (Notion's plain text); a list's items one line apart, other blocks an empty line; a
|
|
21
21
|
// line that would read as a tag or a fence escaped (writers.mjs heldToWords).
|
|
22
|
-
// text/html plain markup: p
|
|
22
|
+
// text/html plain markup: p, a div of a highlight block's or a callout's blocks, h1–h4, ul / ol / li ("[x] " / "[ ] " before a to-do's text), blockquote,
|
|
23
23
|
// details, pre > code.language-x, hr, table, a formula block as $$ … $$ (an empty one:
|
|
24
24
|
// nothing, as in Markdown) and an inline one
|
|
25
25
|
// as $ … $ (Notion's export), colors as the kit's classes with their color in a style.
|
|
@@ -27,8 +27,8 @@ import { WRITERS, heldToWords } from "./writers.mjs";
|
|
|
27
27
|
// both. Text selected in one block (textFormats) is its inline HTML for other programs, and as plain
|
|
28
28
|
// text its words alone (notion-lite's inlinePlain: no list's marker, no Markdown).
|
|
29
29
|
|
|
30
|
-
// The kit's light colors, for programs without its styles (src/shared/colors.mjs).
|
|
31
|
-
const INK = Object.fromEntries(COLORS.flatMap(({ name, text,
|
|
30
|
+
// The kit's light colors (a background on words its words' fill), for programs without its styles (src/shared/colors.mjs).
|
|
31
|
+
const INK = Object.fromEntries(COLORS.flatMap(({ name, text, mark }) => [[`doc-text-${name}`, `color:${text[0]}`], [`doc-highlight-${name}`, `background-color:${mark[0]}`]]));
|
|
32
32
|
const FORMULA = /^\s*\\\(([\s\S]*)\\\)\s*$/;
|
|
33
33
|
|
|
34
34
|
// Inline HTML (the format bar's marks: serializeInner's) as nodes to walk, a link shown with its icon
|
|
@@ -14,12 +14,12 @@ import { groupItems } from "./rules.mjs";
|
|
|
14
14
|
// starts, those between whole, the last up to where it ends; a list's items each an
|
|
15
15
|
// item of its kind (`list`, as an item selected on its own) with its checked state
|
|
16
16
|
// and what of the blocks it holds the selection reaches, a quote's paragraphs in a
|
|
17
|
-
// quote, and a highlight block, a disclosure or a section the selection ends in
|
|
17
|
+
// quote, and a highlight block, a callout, a disclosure or a section the selection ends in
|
|
18
18
|
// with what of it comes before the end (its icon, its title), a tabs area with the
|
|
19
19
|
// tab shown and what of its panel comes before the end; a flowchart's nodes a
|
|
20
20
|
// flowchart with its direction (never loose paragraphs: its steps are no items),
|
|
21
21
|
// the node it ends in a node with what of it comes before the end. Around where it
|
|
22
|
-
// starts, a highlight block, a disclosure, a section, a flowchart's node, an item
|
|
22
|
+
// starts, a highlight block, a callout, a disclosure, a section, a flowchart's node, an item
|
|
23
23
|
// whose own text comes before it, a row of columns, a column or a tabs area are not
|
|
24
24
|
// copied, only what of them it holds (notion-lite copies no block its start is in:
|
|
25
25
|
// a callout, a toggle, a list item's parent; a row of columns it flattens wherever).
|
|
@@ -180,8 +180,6 @@ const paragraph = {
|
|
|
180
180
|
html: (block, how) => `<p>${how.portable(block.html ?? "")}</p>`,
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
-
const inAside = (text) => ["<aside>", ...text.split("\n"), "</aside>"];
|
|
184
|
-
|
|
185
183
|
const todo = list("ul", (_, checked, gap) => `- [${checked ? "x" : " "}]${gap}`);
|
|
186
184
|
|
|
187
185
|
export const WRITERS = {
|
|
@@ -218,15 +216,6 @@ export const WRITERS = {
|
|
|
218
216
|
},
|
|
219
217
|
html: (block, how) => `<div>${block.blocks.map(how.html).join("")}</div>`,
|
|
220
218
|
},
|
|
221
|
-
// A text block in one <aside>: its text alone, no blocks to set apart from it by an empty line. Its
|
|
222
|
-
// HTML is a paragraph of its text, as notion-lite's is no block of HTML: an <aside> would be rich
|
|
223
|
-
// formatting, for which the import reads the HTML of all that is copied with it, and has no block
|
|
224
|
-
// of its own. An empty one has none: an empty element would be read for it.
|
|
225
|
-
callout: {
|
|
226
|
-
copied: (block, how) => inAside(how.inline(block.html ?? "")),
|
|
227
|
-
exported: (unit, how) => inAside(how.own(unit)),
|
|
228
|
-
html: (block, how) => (block.html?.trim() ? `<p>${how.portable(block.html)}</p>` : ""),
|
|
229
|
-
},
|
|
230
219
|
// A disclosure: "- " and its title, its blocks 4 spaces in (Notion writes a toggle as an item), but
|
|
231
220
|
// no list: an empty line stands between it and a list beside it.
|
|
232
221
|
toggle: {
|
|
@@ -50,7 +50,7 @@ const STYLE = `
|
|
|
50
50
|
.ge-gap-pin > .ge-gap-add { left: -3px; top: -8px; opacity: 0; transform: scale(0); transform-origin: 50% 90%; transition: opacity .15s, transform .15s; }
|
|
51
51
|
.ge-gap-pin:hover > .ge-gap-dot { opacity: 0; }
|
|
52
52
|
.ge-gap-pin:hover > .ge-gap-add { opacity: 1; transform: scale(1); }
|
|
53
|
-
.ge-gap-badge { padding: 0 4px; border-radius: 4px; color: #fff; background: rgb(0 0 0 / 30%); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); font: 12px/18px var(--doc-font, system-ui, sans-serif); pointer-events: none; white-space: nowrap; }
|
|
53
|
+
.ge-gap-badge { padding: 0 4px; border-radius: 4px; color: #fff; background: rgb(0 0 0 / 30%); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); font: 12px/18px var(--doc-sans-font, var(--doc-font, system-ui, sans-serif)); pointer-events: none; white-space: nowrap; }
|
|
54
54
|
/* The arrow under a flowchart's gap line (the node's ::after, the kit's) dimmed while the line shows. */
|
|
55
55
|
ol.doc-flow > li::after { transition: opacity .12s; }
|
|
56
56
|
ol.doc-flow > li[data-ge-gap-dim]::after { opacity: .15; }
|
|
@@ -1598,21 +1598,34 @@ export function createCommits({ root, map, baselines, units, boxes, getLayout, s
|
|
|
1598
1598
|
* Someone else's write put back a unit this load made (`gone`) or moved (`moved`), and the reader
|
|
1599
1599
|
* keeps theirs (`mine`): one it made is a block made on the page again, written anew (around its
|
|
1600
1600
|
* blocks from the source it was put back with, if it was); one it moved goes where the page has it
|
|
1601
|
-
* again. Otherwise it is no longer checked.
|
|
1601
|
+
* again. Otherwise it is no longer checked. `within`: the units inside one made that the write took out
|
|
1602
|
+
* with it (the revert check's, heldWithin), decided with it.
|
|
1602
1603
|
*/
|
|
1603
|
-
function putBack(unit, { gone, mine }) {
|
|
1604
|
+
function putBack(unit, { gone, mine, within = [] }) {
|
|
1604
1605
|
const { around } = placed.get(unit) ?? {};
|
|
1605
|
-
|
|
1606
|
+
// What the write took out with it: kept or given up with it, made on the page again inside it (the
|
|
1607
|
+
// blocks of an instance's slots, of a disclosure made this load). What it holds that the file still
|
|
1608
|
+
// has (a block moved in, kept where the Agent unwrapped it) stays the file's, a unit the commit
|
|
1609
|
+
// making it again takes along, as one it was put back around, and is decided by its own card if any.
|
|
1610
|
+
const inner = gone ? within : [];
|
|
1611
|
+
for (const id of [unit, ...inner]) placed.delete(id);
|
|
1606
1612
|
if (!mine) return;
|
|
1607
1613
|
touched(); // the layout changes in place
|
|
1608
1614
|
if (gone) {
|
|
1609
1615
|
const key = map.units[unit].cid;
|
|
1610
1616
|
drop(unit);
|
|
1617
|
+
inner.forEach(drop);
|
|
1611
1618
|
if (around && dropped.has(key)) dropped.get(key).around = around;
|
|
1612
1619
|
} else for (const [box, order] of getLayout()?.orders ?? []) getLayout().orders.set(box, order.filter((id) => id !== unit));
|
|
1613
1620
|
marks();
|
|
1614
1621
|
}
|
|
1615
1622
|
|
|
1623
|
+
// The units the page shows inside unit `unit`'s element, at any depth.
|
|
1624
|
+
const unitsWithin = (unit) => [...(unitElement(unit)?.querySelectorAll("[data-ge-unit]") ?? [])].map(idOf).filter((id) => id !== null);
|
|
1625
|
+
|
|
1626
|
+
/** Whether unit `unit` stands inside one of `units` on the page: what someone else's write took out with it is decided with it. */
|
|
1627
|
+
const heldWithin = (unit, units) => units.some((other) => other !== unit && unitsWithin(other).includes(unit));
|
|
1628
|
+
|
|
1616
1629
|
/** The text of the blocks in a unit the page shows (the reader's words), or "". */
|
|
1617
1630
|
const unitText = (unit) => {
|
|
1618
1631
|
const element = unitElement(unit);
|
|
@@ -1728,9 +1741,11 @@ export function createCommits({ root, map, baselines, units, boxes, getLayout, s
|
|
|
1728
1741
|
}
|
|
1729
1742
|
|
|
1730
1743
|
return {
|
|
1731
|
-
build, apply, collect, current, marks, counts, buffer, texts, resolve, committed, unjournal, placedUnits, putBack, unitText, written, carry, dataOfUnit, heldOtherwise, rewrite, issues, removed, pageIds, idFor,
|
|
1744
|
+
build, apply, collect, current, marks, counts, buffer, texts, resolve, committed, unjournal, placedUnits, putBack, heldWithin, unitText, written, carry, dataOfUnit, heldOtherwise, rewrite, issues, removed, pageIds, idFor,
|
|
1732
1745
|
/** The id a unit of the page has in the file, or the one it proposes for it; null for none. */
|
|
1733
1746
|
fileId: (unit) => map.units[unit]?.blockId ?? map.units[unit]?.proposed ?? null,
|
|
1747
|
+
/** The hash a unit of the page has in the file now (its map's, kept by every answer); null for none (changes/ finds the Agent's blocks by it). */
|
|
1748
|
+
fileHash: (unit) => map.units[unit]?.hash ?? null,
|
|
1734
1749
|
/** save(pending) as JSON, with the page's body (sync-state.mjs pack). */
|
|
1735
1750
|
keep: (pending) => pack(root, save(pending)),
|
|
1736
1751
|
/** Identities more than one element on the page carries (none, ever). */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { blocksText, partOwner, plainText } from "./core.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { reveal } from "./folded.mjs";
|
|
3
|
+
import { wordDiff } from "./merge.mjs";
|
|
3
4
|
import { PANEL_STYLE, panelGone, panelShown } from "./panel.mjs";
|
|
4
5
|
import { addStyle, button, element, mount } from "./ui.mjs";
|
|
5
6
|
import { t } from "../../shared/i18n/index.mjs";
|
|
@@ -26,7 +27,7 @@ import { t } from "../../shared/i18n/index.mjs";
|
|
|
26
27
|
// a popover like the workbench's, no colored frame, the flagged block is what stands out; the
|
|
27
28
|
// reader's words and the file's are told apart by one pair of colors.
|
|
28
29
|
const STYLE = `
|
|
29
|
-
.ge-conflicts { --ge-mine:
|
|
30
|
+
.ge-conflicts { --ge-mine: var(--ge-diff-del); --ge-mine-text: var(--ge-diff-del-text); --ge-theirs: var(--ge-diff-add); --ge-theirs-text: var(--ge-diff-add-text); }
|
|
30
31
|
.ge-conflict[data-ge-current] { background: var(--ge-hover); }
|
|
31
32
|
.ge-conflict-label { display: flex; gap: 6px; margin: 10px 0 3px; color: var(--ge-tx2); font-size: 12px; }
|
|
32
33
|
.ge-conflict :is(ins, del, .ge-chip) { border-radius: 3px; text-decoration: none; }
|
|
@@ -84,29 +85,8 @@ function describe(entry, restorable) {
|
|
|
84
85
|
return { text: t("你新建(或放回)的这一块没法放进文件(周围的块已被 AI 改动或删除)。"), actions: [[t("放到附近"), "nearby", true], [t("撤销这一块"), "undo"], copy] };
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
// the del it stands beside). Words are runs of letters and digits, spaces, single other characters
|
|
90
|
-
// (each Chinese character on its own); at most 1500 words of each (merge.mjs commonRun finds what is shared).
|
|
91
|
-
export function wordDiff(mine, theirs) {
|
|
92
|
-
const tokens = (text) => (text.match(/[A-Za-z0-9]+|\s+|[^A-Za-z0-9\s]/gu) ?? []).slice(0, 1500);
|
|
93
|
-
const a = tokens(mine);
|
|
94
|
-
const b = tokens(theirs);
|
|
95
|
-
const runs = [];
|
|
96
|
-
const add = (tag, text) => {
|
|
97
|
-
if (runs.at(-1)?.tag === tag) runs.at(-1).text += text;
|
|
98
|
-
else runs.push({ tag, text });
|
|
99
|
-
};
|
|
100
|
-
let [i, j] = [0, 0];
|
|
101
|
-
for (const [x, y] of [...commonRun(a, b), [a.length, b.length]]) {
|
|
102
|
-
while (j < y) add("ins", b[j++]);
|
|
103
|
-
while (i < x) add("del", a[i++]);
|
|
104
|
-
if (x < a.length) add(null, a[i++]);
|
|
105
|
-
j++;
|
|
106
|
-
}
|
|
107
|
-
return runs;
|
|
108
|
-
}
|
|
109
|
-
|
|
88
|
+
// The word diff (merge.mjs wordDiff): its runs as the card's nodes.
|
|
89
|
+
export { wordDiff };
|
|
110
90
|
const diffNodes = (mine, theirs) => wordDiff(mine, theirs).map(({ tag, text }) => tag ? element(tag, { text }) : document.createTextNode(text));
|
|
111
91
|
|
|
112
92
|
/**
|
|
@@ -240,7 +220,7 @@ export function createConflicts({ act, nodeOf, restorable, copyBlock, say }) {
|
|
|
240
220
|
function locate(key) {
|
|
241
221
|
const found = cards.get(key);
|
|
242
222
|
const node = found && nodeOf(found.entry);
|
|
243
|
-
if (node)
|
|
223
|
+
if (node) reveal(node); // a folded heading's section too (fold/)
|
|
244
224
|
node?.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
245
225
|
for (const other of cards.values()) other.node.toggleAttribute("data-ge-current", other === found);
|
|
246
226
|
}
|
|
@@ -12,9 +12,9 @@ import { asTitle, flowchartFromList, flowchartFromPanel, itemFromStep, listFromF
|
|
|
12
12
|
|
|
13
13
|
// Turning blocks into other types on the page (DOM only; tested with jsdom): text, small text
|
|
14
14
|
// (小字, the kit's .doc-note), four heading levels, bulleted, numbered and to-do lists and flow charts (流程图), disclosures
|
|
15
|
-
// (折叠列表),
|
|
16
|
-
//
|
|
17
|
-
// into a disclosure, goes into it as it is), code blocks, quotes and
|
|
15
|
+
// (折叠列表), highlight panels (高亮块: a container with the opening summary's look, whose blocks keep
|
|
16
|
+
// their own types, lists and headings among them) and callouts (提示块: the same kind of container, its
|
|
17
|
+
// look its own; a block turned into either, or into a disclosure, goes into it as it is), code blocks, quotes and
|
|
18
18
|
// dividers; the types are the components' (src/editor/components/, registry.mjs), and so is how a
|
|
19
19
|
// type that is no text block is made (its `create`). The "/" menu,
|
|
20
20
|
// Markdown shortcuts, the block menu and its "在下方新建一块" all come here (editor.mjs:
|
|
@@ -48,7 +48,8 @@ const ownAttribute = (host, name) => Boolean(ATTRIBUTES[host.getAttribute("data-
|
|
|
48
48
|
|
|
49
49
|
// The types a block, or a group of blocks, goes into as it is (each keeping its type: `takesAsIs`): a
|
|
50
50
|
// highlight block (notion-lite's callout, whose body is its children: transformBlocks moves the blocks
|
|
51
|
-
// into a new one) and, as the reader asked, a disclosure (into its
|
|
51
|
+
// into a new one), a callout (the same kind of block) and, as the reader asked, a disclosure (into its
|
|
52
|
+
// content; its title left empty to type).
|
|
52
53
|
const TAKES_AS_IS = new Set(Object.keys(typesWith("takesAsIs")));
|
|
53
54
|
|
|
54
55
|
// Whether a box takes the block of a type: what its role accepts, and none of a type that does not
|
|
@@ -181,7 +182,7 @@ export function containerType(unit) {
|
|
|
181
182
|
const contentOf = (unit) => BLOCK_TYPES[containerType(unit)]?.content?.(unit) ?? unit;
|
|
182
183
|
|
|
183
184
|
// The containers that turn into one another, what they hold carried along (Notion's: HOLDERS): a
|
|
184
|
-
// disclosure, a quote, a highlight block. Each is the kind of box it is, as well as of block.
|
|
185
|
+
// disclosure, a quote, a highlight block, a callout. Each is the kind of box it is, as well as of block.
|
|
185
186
|
|
|
186
187
|
// The blocks a disclosure, quote or highlight block holds (a highlight block's icon not among them).
|
|
187
188
|
const heldBy = (unit) => heldIn(contentOf(unit));
|
|
@@ -272,8 +273,8 @@ function standIn(host, type) {
|
|
|
272
273
|
return standsFor(next, host);
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
// Whether a block already is the text block of a type: its tag, and
|
|
276
|
-
//
|
|
276
|
+
// Whether a block already is the text block of a type: its tag, and its class where its type has
|
|
277
|
+
// one; of the paragraph types, the one its class makes it.
|
|
277
278
|
function isOfType(host, type) {
|
|
278
279
|
const spec = BLOCK_TYPES[type];
|
|
279
280
|
if (host.localName !== spec.tag) return false;
|
|
@@ -344,13 +344,13 @@ export function serializeInner(element) {
|
|
|
344
344
|
if (inner) return inner(element);
|
|
345
345
|
// A table cell's lists are its text (单元格里的列表); any other block's are blocks of their own.
|
|
346
346
|
const lists = element.getAttribute?.("data-ge-kind") === "cell";
|
|
347
|
-
return ownNodes(element)
|
|
347
|
+
return serializeNodes(ownNodes(element), lists).replace(/<br>\s*$/, "");
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
// A list in a table cell, its items' inline content (a line break holding an empty item's caret left out).
|
|
351
351
|
function serializeList(list) {
|
|
352
352
|
const tag = list.tagName.toLowerCase();
|
|
353
|
-
const items = [...list.children].filter((item) => item.tagName === "LI").map((item) => `<li>${[...item.childNodes]
|
|
353
|
+
const items = [...list.children].filter((item) => item.tagName === "LI").map((item) => `<li>${serializeNodes([...item.childNodes]).replace(/<br>\s*$/, "")}</li>`);
|
|
354
354
|
return items.length > 0 ? `<${tag}>${items.join("")}</${tag}>` : "";
|
|
355
355
|
}
|
|
356
356
|
|
|
@@ -383,11 +383,16 @@ export function textEnd(element) {
|
|
|
383
383
|
|
|
384
384
|
const ownNodes = (element) => [...element.childNodes].slice(0, textEnd(element));
|
|
385
385
|
|
|
386
|
-
/**
|
|
386
|
+
/**
|
|
387
|
+
* A block's own content as HTML (innerHTML, without the blocks a list item holds, nor the words an
|
|
388
|
+
* Agent took out that the page shows in it: GONE_WORDS), what undo and redo record of it.
|
|
389
|
+
*/
|
|
387
390
|
export function ownHTML(element) {
|
|
388
|
-
|
|
391
|
+
const shown = element.querySelector(GONE_WORDS);
|
|
392
|
+
if (nestedBlocks(element).length === 0 && !shown) return element.innerHTML;
|
|
389
393
|
const copy = element.ownerDocument.createElement("div");
|
|
390
394
|
copy.append(...ownNodes(element).map((node) => node.cloneNode(true)));
|
|
395
|
+
if (shown) copy.querySelectorAll(GONE_WORDS).forEach((node) => node.remove());
|
|
391
396
|
return copy.innerHTML;
|
|
392
397
|
}
|
|
393
398
|
|
|
@@ -429,6 +434,33 @@ export const parentItem = (item) => item?.tagName === "LI" && item.parentElement
|
|
|
429
434
|
// edits becomes a space too.
|
|
430
435
|
const spaced = (text) => escapeTyped(text).replace(/ *\u00a0[ \u00a0]*/g, " ");
|
|
431
436
|
|
|
437
|
+
// The editor's own nodes in a text block (data-doc-dev: the words an Agent took out, which the page
|
|
438
|
+
// shows where they were, changes/) are no content: left out, the text on either side of one is
|
|
439
|
+
// written as one text, as it was before the node split it (a space either side of it the one space
|
|
440
|
+
// it was, never two).
|
|
441
|
+
function serializeNodes(nodes, lists = false) {
|
|
442
|
+
let html = "";
|
|
443
|
+
let text = null; // a text node's, not written yet: the text after an editor's node joins it
|
|
444
|
+
let across = false; // an editor's node stands between it and what comes next
|
|
445
|
+
for (const node of nodes) {
|
|
446
|
+
if (node.nodeType === 1 && node.hasAttribute("data-doc-dev")) {
|
|
447
|
+
across = text !== null;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (node.nodeType === 3 && node.data !== ZWSP) {
|
|
451
|
+
if (text !== null && !across) html += spaced(text);
|
|
452
|
+
text = text !== null && across ? text + node.data : node.data;
|
|
453
|
+
across = false;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (text !== null) html += spaced(text);
|
|
457
|
+
text = null;
|
|
458
|
+
across = false;
|
|
459
|
+
html += serializeNode(node, lists);
|
|
460
|
+
}
|
|
461
|
+
return text === null ? html : html + spaced(text);
|
|
462
|
+
}
|
|
463
|
+
|
|
432
464
|
function serializeNode(node, lists = false) {
|
|
433
465
|
// A text node holding nothing but a zero-width space keeps the caret after a mark the editor
|
|
434
466
|
// made (markdown/, typing shortcuts); it is never content.
|
|
@@ -443,7 +475,7 @@ function serializeNode(node, lists = false) {
|
|
|
443
475
|
// and their text kept.
|
|
444
476
|
const fromSource = node.hasAttribute("data-ge-src") && node.tagName in TAGS;
|
|
445
477
|
const tag = fromSource ? node.tagName.toLowerCase() : TAGS[node.tagName];
|
|
446
|
-
const inner = [...node.childNodes]
|
|
478
|
+
const inner = serializeNodes([...node.childNodes]);
|
|
447
479
|
const ink = node.tagName === "SPAN" ? [...node.classList].filter(isInkClass) : [];
|
|
448
480
|
if (ink.length > 0) return inner ? `<span class="${ink.join(" ")}">${inner}</span>` : "";
|
|
449
481
|
// Small text's end part (its Tab: components/paragraph/page.mjs), at the line's right end.
|
|
@@ -498,8 +530,9 @@ export function isEmpty(element) {
|
|
|
498
530
|
const siblingTag = (element) => element.tagName === "LI" ? "li" : "p";
|
|
499
531
|
|
|
500
532
|
/**
|
|
501
|
-
* A text block made on the page: data-ge-new, with its type and unit kind
|
|
502
|
-
*
|
|
533
|
+
* A text block made on the page: data-ge-new, with its type and unit kind, a text block of the flow
|
|
534
|
+
* (every type's that is one: a callout's words, its own kind until 2026-11-07, are a paragraph of it
|
|
535
|
+
* now). Inside a component it leaves the frame to the component, as `from` (the block it was split off) does.
|
|
503
536
|
*/
|
|
504
537
|
export function newHost(doc, type, from = null) {
|
|
505
538
|
const spec = BLOCK_TYPES[type];
|
|
@@ -509,9 +542,8 @@ export function newHost(doc, type, from = null) {
|
|
|
509
542
|
block.setAttribute("data-ge-new", "");
|
|
510
543
|
block.setAttribute("data-ge-type", type);
|
|
511
544
|
block.setAttribute("data-ge-unit-kind", spec.kind);
|
|
512
|
-
|
|
513
|
-
block.setAttribute("data-ge-
|
|
514
|
-
block.setAttribute("data-ge-caps", capsFor(kind));
|
|
545
|
+
block.setAttribute("data-ge-kind", "flow");
|
|
546
|
+
block.setAttribute("data-ge-caps", capsFor("flow"));
|
|
515
547
|
if (from?.hasAttribute("data-ge-framed")) block.setAttribute("data-ge-framed", "");
|
|
516
548
|
return block;
|
|
517
549
|
}
|
|
@@ -872,6 +904,9 @@ export function mergeInto(into, from, { anyKind = false } = {}) {
|
|
|
872
904
|
// with its own.
|
|
873
905
|
|
|
874
906
|
export const ELEMENT_ID = "data-ge-id";
|
|
907
|
+
|
|
908
|
+
/** The words an Agent took out of a text block, shown where they were (changes/): never its content. */
|
|
909
|
+
export const GONE_WORDS = "[data-ge-gone-words]";
|
|
875
910
|
const ELEMENT_ID_PREFIX = Math.random().toString(36).slice(2, 6); // a page's own, as formulas' keys
|
|
876
911
|
let elementIds = 0;
|
|
877
912
|
|
|
@@ -927,10 +962,10 @@ export const inTable = (node) => /^(TR|TD|TH)$/.test(node?.tagName ?? "") && nod
|
|
|
927
962
|
export function unitElement(node) {
|
|
928
963
|
let found = node?.closest?.(UNIT_OR_LEAD) ?? null;
|
|
929
964
|
while (inTable(found)) found = found.parentElement?.closest(UNIT_OR_LEAD) ?? null;
|
|
930
|
-
// Text that is part of a block
|
|
931
|
-
//
|
|
965
|
+
// Text made on the page that is part of a block (a new code block's code, a media block's new
|
|
966
|
+
// caption) stands for that block: the part's own block, not the one around it.
|
|
932
967
|
const part = found && !found.hasAttribute("data-ge-unit") ? partOf(found) : null;
|
|
933
|
-
if (part) found =
|
|
968
|
+
if (part) found = part.closest(UNIT);
|
|
934
969
|
if (!found?.hasAttribute("data-ge-lead")) return found;
|
|
935
970
|
const id = found.getAttribute("data-ge-lead");
|
|
936
971
|
let next = found.nextElementSibling;
|
|
@@ -1180,12 +1215,12 @@ export function snapshotLayout(root) {
|
|
|
1180
1215
|
}
|
|
1181
1216
|
|
|
1182
1217
|
// Whether a text block may be written with no text: it is of a type whose component says so (its
|
|
1183
|
-
// `empty`: a paragraph, a heading, a list item
|
|
1218
|
+
// `empty`: a paragraph, a heading, a list item; the server's emptyLeaf reads the same)
|
|
1184
1219
|
// and can be emptied (can "empty"), an item holding blocks too, its line kept over them (keepLine). A
|
|
1185
1220
|
// field that may be emptied (a cell, a caption) is no block written empty; a new code block is
|
|
1186
1221
|
// written empty by its type (describeNode).
|
|
1187
1222
|
const clearable = (element) => can(element, "empty");
|
|
1188
|
-
/** Whether an empty text block stays, written with no text (a paragraph, a heading, a list item, a
|
|
1223
|
+
/** Whether an empty text block stays, written with no text (a paragraph, a heading, a list item, a code block). */
|
|
1189
1224
|
const keepsWhenEmpty = (element) => BLOCK_TYPES[textTypeOf(element)]?.empty === true && clearable(element);
|
|
1190
1225
|
|
|
1191
1226
|
/** Whether a text block may go: it can be removed, is not kept (data-ge-keep) and holds no blocks (it would show no line of its own). */
|
|
@@ -1208,8 +1243,11 @@ const leaves = (node, emptied) => gone(node) || emptied.has(node);
|
|
|
1208
1243
|
// The text block of a block: the block itself, or where its component says (a code block's code).
|
|
1209
1244
|
const textOf = (node) => BLOCK_TYPES[blockTypeOf(node)]?.textHost?.(node) ?? node;
|
|
1210
1245
|
|
|
1211
|
-
// What a component's description is given to say what goes with a block (`extras`).
|
|
1246
|
+
// What a component's description is given to say what goes with a block (`extras`). `nodes(box)`: the
|
|
1247
|
+
// blocks of a box of its own as the commit writes them (nodeOf), a block written whole holding those of
|
|
1248
|
+
// boxes of its own apart from its markup (an instance's slots: installed/index.mjs).
|
|
1212
1249
|
const HELPERS = { sizeValue, serializeInner, unitsOf };
|
|
1250
|
+
const helpersFor = (emptied) => ({ ...HELPERS, nodes: (box) => unitsOf(box).map((child) => nodeOf(child, emptied)).filter(Boolean) });
|
|
1213
1251
|
|
|
1214
1252
|
/**
|
|
1215
1253
|
* The first element in `root` (in document order, `root` itself left out) carrying each value of
|
|
@@ -1351,7 +1389,7 @@ function describeNode(element, emptied) {
|
|
|
1351
1389
|
// (`content`: a disclosure's content region), its title (`title`), its id (`keyed`: a detail
|
|
1352
1390
|
// card's), when it is written at all (`written`: a row once each of its columns has something, a
|
|
1353
1391
|
// detail card while a link opens it; else once it holds something).
|
|
1354
|
-
const extras = spec.extras?.(element,
|
|
1392
|
+
const extras = spec.extras?.(element, helpersFor(emptied)) ?? {};
|
|
1355
1393
|
if (spec.bare) return spec.written?.(element, []) === false ? null : { type, ...extras };
|
|
1356
1394
|
if (!spec.box) {
|
|
1357
1395
|
const text = textOf(element);
|
|
@@ -28,6 +28,8 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
28
28
|
// cancels, and so does anything that ends the press another way (the handle losing the pointer
|
|
29
29
|
// capture, the window losing the focus, the page hidden): never a drop. Ending is idempotent, and
|
|
30
30
|
// the line, tint and copy belong to their drag (overlays.mjs). Ctrl+Shift+arrows move selected blocks standing next to each other by one place (moveBy).
|
|
31
|
+
// A folded heading goes with what it hides, or as the section it covers whole (fold/ takenWith); its
|
|
32
|
+
// copy shows what is in sight, and Ctrl+Shift+↑/↓ take it a section of its level at a time.
|
|
31
33
|
|
|
32
34
|
const STYLE = `
|
|
33
35
|
${gestureLock("data-ge-drag", { cursor: "grabbing", select: true })}
|
|
@@ -43,9 +45,9 @@ const STYLE = `
|
|
|
43
45
|
* info: block-info.mjs. selection: selection/index.mjs. struct(parents, touched, mutate): the
|
|
44
46
|
* editor's undoable structural change (history.mjs). say: the status line. closeMenu(): the block
|
|
45
47
|
* menu closes. schedule(): the block tools' next frame (blocks.mjs), which calls step() while a
|
|
46
|
-
* drag lasts.
|
|
48
|
+
* drag lasts. folds: the headings' folds (fold/).
|
|
47
49
|
*/
|
|
48
|
-
export function createDrag({ info, selection, struct, say, closeMenu, schedule }) {
|
|
50
|
+
export function createDrag({ info, selection, struct, say, closeMenu, schedule, folds }) {
|
|
49
51
|
addStyle(STYLE);
|
|
50
52
|
const { addColumn, columnsOf, rowAround } = columnOps() ?? {};
|
|
51
53
|
const { kindOf, nameOf, named, fixedText, boxKindOf } = info;
|
|
@@ -105,7 +107,8 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
// Put blocks beside a block (drop.mjs `aside`): { target, side }, the two side by side in a new
|
|
108
|
-
// row of columns
|
|
110
|
+
// row of columns (a folded heading's column taking what it hides: fold/ withHidden); { row, index,
|
|
111
|
+
// whole }, a new column of that row at `index` holding them, or,
|
|
109
112
|
// when they are all of one column of it (`whole`), that column moved there. One undo step that
|
|
110
113
|
// keeps them selected; a container they leave empty goes, as when they move (and a row left
|
|
111
114
|
// with one column gives its blocks back: history.mjs).
|
|
@@ -122,7 +125,7 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
122
125
|
const leftovers = leftoversOf(targets, aside.row ?? aside.target);
|
|
123
126
|
const parents = [...new Set([...targets.map((target) => target.parentNode), aside.row ?? aside.target.parentElement, ...leftovers.map((node) => node.parentNode)].filter(Boolean))];
|
|
124
127
|
struct(parents, [], () => {
|
|
125
|
-
const { created } = aside.row ? addColumn(aside.row, aside.index, parts) : rowAround(aside.target, [parts], aside.side);
|
|
128
|
+
const { created } = aside.row ? addColumn(aside.row, aside.index, parts) : rowAround(folds.withHidden(aside.target), [parts], aside.side);
|
|
126
129
|
leftovers.forEach((node) => node.remove());
|
|
127
130
|
return { touched: [], focus: null, select: targets, reselect: true, created };
|
|
128
131
|
});
|
|
@@ -134,30 +137,40 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
134
137
|
*/
|
|
135
138
|
function moveBy(targets, delta, { keepFocus = false } = {}) {
|
|
136
139
|
closeMenu();
|
|
137
|
-
|
|
140
|
+
const picked = ordered([targets].flat().filter(alive));
|
|
141
|
+
targets = folds.takenWith(picked);
|
|
138
142
|
if (targets.length === 0) return false;
|
|
139
143
|
const reason = targets.map(fixedText).find(Boolean);
|
|
140
144
|
if (reason) {
|
|
141
145
|
say(reason);
|
|
142
146
|
return false;
|
|
143
147
|
}
|
|
144
|
-
|
|
148
|
+
const name = folds.namedWith(picked, targets, named);
|
|
149
|
+
// The blocks to move among: not a box's lead (a highlight block's icon stays first in it), as the
|
|
150
|
+
// reader sees them: a folded heading and what it hides one (fold/ groupsOf).
|
|
145
151
|
const boxKind = boxKindOf(targets[0].parentElement);
|
|
146
152
|
const list = unitsOf(targets[0].parentElement).filter((unit) => !isLead(boxKind, kindOf(unit)));
|
|
147
|
-
const
|
|
148
|
-
|
|
153
|
+
const groups = folds.groupsOf(list);
|
|
154
|
+
const first = groups.findIndex((group) => group[0] === targets[0]);
|
|
155
|
+
let last = first;
|
|
156
|
+
let count = first < 0 ? 0 : groups[first].length;
|
|
157
|
+
while (first >= 0 && count < targets.length && last + 1 < groups.length) count += groups[++last].length;
|
|
158
|
+
if (first < 0 || !groups.slice(first, last + 1).flat().every((unit, index) => targets[index] === unit) || count !== targets.length) {
|
|
149
159
|
say(t("选中的块不相邻,不能一起上下移动;可以拖动其中一块的手柄,把它们一起移到别处。"));
|
|
150
160
|
return false;
|
|
151
161
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
162
|
+
// A folded heading alone goes a section of its level at a time (fold/ stepOver); others a block in sight at a time.
|
|
163
|
+
const alone = first === last && folds.isFolded(targets[0]);
|
|
164
|
+
const step = alone ? folds.stepOver(targets[0], delta, list) : null;
|
|
165
|
+
const over = alone ? step && [step.first, step.last] : groups[delta < 0 ? first - 1 : last + 1];
|
|
166
|
+
if (!over) {
|
|
167
|
+
say(groups.length === last - first + 1 ? t("这里只有{0};可以拖动手柄移到别处。", targets.length === 1 ? t("这一个{0}", nameOf(targets[0])) : t("这几块")) : t("{0}已经在{1}。", name, delta < 0 ? t("最前面") : t("最后面")));
|
|
155
168
|
return false;
|
|
156
169
|
}
|
|
157
|
-
moveTo(targets, targets[0].parentElement, delta < 0 ? withLeads(
|
|
170
|
+
moveTo(targets, targets[0].parentElement, delta < 0 ? withLeads(over[0])[0] : over.at(-1).nextSibling);
|
|
158
171
|
if (!keepFocus) selection.select(targets, { quiet: true });
|
|
159
172
|
targets[delta < 0 ? 0 : targets.length - 1].scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
160
|
-
say(t("已移动{0} · Ctrl+Z 撤销",
|
|
173
|
+
say(t("已移动{0} · Ctrl+Z 撤销", name));
|
|
161
174
|
return true;
|
|
162
175
|
}
|
|
163
176
|
|
|
@@ -165,7 +178,8 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
165
178
|
// the drag started (a block that cannot move says why).
|
|
166
179
|
function start(target, x, y) {
|
|
167
180
|
const picked = selection.picked;
|
|
168
|
-
const
|
|
181
|
+
const chosen = picked.length > 1 && picked.includes(target) ? [...picked] : [target];
|
|
182
|
+
const targets = folds.takenWith(chosen); // a folded heading with what it hides (fold/)
|
|
169
183
|
const reason = targets.map(fixedText).find(Boolean);
|
|
170
184
|
if (reason) {
|
|
171
185
|
say(reason);
|
|
@@ -174,7 +188,7 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
174
188
|
closeMenu();
|
|
175
189
|
selection.select(targets, { quiet: true });
|
|
176
190
|
const session = {
|
|
177
|
-
targets, target, from: target.parentElement, x, y, drop: null, rest: null, opened: new Set(),
|
|
191
|
+
targets, target, from: target.parentElement, x, y, drop: null, rest: null, opened: new Set(), name: folds.namedWith(chosen, targets, named),
|
|
178
192
|
line: element("div", { class: "ge-drop-line", hidden: "" }),
|
|
179
193
|
tint: element("div", { class: "ge-drop-tint", hidden: "" }),
|
|
180
194
|
preview: null,
|
|
@@ -184,12 +198,12 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
184
198
|
// The line and the tint go in the layer of where the blocks would go
|
|
185
199
|
// (overlay-layer.mjs), when there is one; the copy follows the pointer in the window.
|
|
186
200
|
for (const kind of ["line", "tint"]) own(session[kind], `drag ${kind}`, { owner: target, alive });
|
|
187
|
-
session.preview = createPreview(targets, x, y, { owner: target, alive });
|
|
201
|
+
session.preview = createPreview(targets.filter((node) => !folds.hidden(node)), x, y, { owner: target, alive }); // what is in sight
|
|
188
202
|
drag = session;
|
|
189
203
|
note("drag", `start ${targets.map(describe).join(", ")}`);
|
|
190
204
|
targets.flatMap(withLeads).forEach((part) => part.setAttribute("data-ge-dragging", ""));
|
|
191
205
|
document.documentElement.setAttribute("data-ge-drag", "");
|
|
192
|
-
say(t("正在移动{0} · 拖到要放的位置后松开 · 停在选项卡上切换,停在折叠块标题上展开 · Esc 取消",
|
|
206
|
+
say(t("正在移动{0} · 拖到要放的位置后松开 · 停在选项卡上切换,停在折叠块标题上展开 · Esc 取消", session.name), "prompt");
|
|
193
207
|
update();
|
|
194
208
|
schedule();
|
|
195
209
|
return true;
|
|
@@ -219,8 +233,9 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
219
233
|
: items ? accepts(box.kind, "item") || accepts(box.kind, "list")
|
|
220
234
|
: steps ? accepts(box.kind, "step") || (accepts(box.kind, "flowchart") && box.kind !== "nest" && !box.el.closest("ol.doc-flow"))
|
|
221
235
|
: [...kinds].every((kind) => accepts(box.kind, kind)));
|
|
222
|
-
// Blocks a column takes may go beside a block, into a column (分栏)
|
|
223
|
-
|
|
236
|
+
// Blocks a column takes may go beside a block, into a column (分栏); beside a folded heading when a
|
|
237
|
+
// column takes what it hides too, which goes into its column with it (besideTo).
|
|
238
|
+
const aside = !tabs && [...kinds].every((kind) => accepts("column", kind)) && ((unit) => folds.withHidden(unit.el).every((node) => accepts("column", kindOf(node))));
|
|
224
239
|
return resolveDrop(drag.tree, x + scrollX, y + scrollY, { takes, dragged: (unit) => targets.includes(unit.el), count: targets.length, items, aside, edge: scrollX });
|
|
225
240
|
}
|
|
226
241
|
|
|
@@ -272,7 +287,7 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
272
287
|
// Releasing (commit) or cancelling: the blocks go where the line showed, or stay.
|
|
273
288
|
function end(commit) {
|
|
274
289
|
if (!drag) return;
|
|
275
|
-
const { targets, target, drop, line, tint, preview, opened } = drag;
|
|
290
|
+
const { targets, target, drop, line, tint, preview, opened, name } = drag;
|
|
276
291
|
clearTimeout(drag.rest?.timer);
|
|
277
292
|
drag = null;
|
|
278
293
|
line.remove();
|
|
@@ -293,16 +308,17 @@ export function createDrag({ info, selection, struct, say, closeMenu, schedule }
|
|
|
293
308
|
besideTo(targets, drop.aside);
|
|
294
309
|
selection.select(targets, { quiet: true });
|
|
295
310
|
target.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
296
|
-
say(t("{0} · 拖动栏间的竖线调宽度 · Ctrl+Z 撤销", drop.aside.whole ? t("已移动这一栏") : drop.aside.row ? t("已把{0}放进新的一栏",
|
|
311
|
+
say(t("{0} · 拖动栏间的竖线调宽度 · Ctrl+Z 撤销", drop.aside.whole ? t("已移动这一栏") : drop.aside.row ? t("已把{0}放进新的一栏", name) : t("已把{0}并排分栏", name)));
|
|
297
312
|
return;
|
|
298
313
|
}
|
|
299
314
|
const others = unitsOf(drop.container).filter((unit) => !targets.includes(unit) && extentOf(unit));
|
|
300
|
-
// The place is never before a box's lead (a highlight block's icon): drop.mjs.
|
|
301
|
-
|
|
315
|
+
// The place is never before a box's lead (a highlight block's icon): drop.mjs. At the end, after
|
|
316
|
+
// what the last block in sight hides too (a folded heading's section: fold/), never between them.
|
|
317
|
+
const reference = drop.slot < others.length ? withLeads(others[drop.slot])[0] : others.length > 0 ? folds.lastOf(others.at(-1)).nextSibling : null;
|
|
302
318
|
const emptied = moveTo(targets, drop.nest ?? drop.container, drop.nest ? null : reference);
|
|
303
319
|
selection.select(targets, { quiet: true });
|
|
304
320
|
target.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
305
|
-
say(t("已移动{0}{1}{2} · Ctrl+Z 撤销",
|
|
321
|
+
say(t("已移动{0}{1}{2} · Ctrl+Z 撤销", name, drop.nest ? t("到上一项下面") : "", emptied ? t("(原来的外层已空,一并去掉)") : ""));
|
|
306
322
|
}
|
|
307
323
|
|
|
308
324
|
// The window losing the focus, or the page going out of sight, ends a drag: cancelled.
|