@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
|
@@ -5,6 +5,7 @@ import { caretAt, editable, select } from "../caret.mjs";
|
|
|
5
5
|
import { ordered } from "../block-info.mjs";
|
|
6
6
|
import { firstHost, isHost, unitElement } from "../core.mjs";
|
|
7
7
|
import { createFills } from "../fills.mjs";
|
|
8
|
+
import { reveal } from "../folded.mjs";
|
|
8
9
|
import { FOLLOW, task } from "../frames.mjs";
|
|
9
10
|
import { BAR } from "../handle-sizes.mjs";
|
|
10
11
|
import { MARGIN, margin, onMargin, rightRoom, setMargin } from "../margin.mjs";
|
|
@@ -44,17 +45,17 @@ import { HOT, MARK, STYLE, barNode, cardNode, fitField, layerNode, markerNode }
|
|
|
44
45
|
// mark (blocks, the fill selected blocks have, in a warm colour: fills.mjs): words have it where the
|
|
45
46
|
// format bar stood (below their last line, by their last character), blocks at the last one's bottom
|
|
46
47
|
// right (at the window's bottom where that is below it), always inside the column. It exists once
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
48
|
+
// something is typed in it (white space is nothing): a card left with none leaves nothing, and a saved
|
|
49
|
+
// one emptied goes as its card closes, offered back as its delete button's is (an annotation has words:
|
|
50
|
+
// the user's, 2026-11-06); typing saves it (500ms after the last key, at once when it closes; a text
|
|
51
|
+
// with no words never: the file keeps its last words while the card is open); a press elsewhere, Esc, Enter, Tab or Ctrl+Alt+M close it into a marker
|
|
50
52
|
// in the column's right gutter, at its first line or the middle of its several blocks (right of a
|
|
51
53
|
// section's strip for what is in that section; the place the button beside a block stands on:
|
|
52
54
|
// markerSpot(), which says no where a marker stands already). Closed
|
|
53
55
|
// by a key the caret goes back to its words, or to its blocks where the keys opened it; a card of
|
|
54
56
|
// blocks the pointer opened places none. Resting on a marker (or clicking it) shows its card again
|
|
55
57
|
// where it was written, by its words, which light up, and it stays while the pointer goes over to it.
|
|
56
|
-
// The button at a card's right deletes it, and
|
|
57
|
-
// emptied stays, with no words. One card shows at a time, and starting on the words of an annotation already there shows its
|
|
58
|
+
// The button at a card's right deletes it, and so does emptying it, or its words or blocks going. One card shows at a time, and starting on the words of an annotation already there shows its
|
|
58
59
|
// card rather than a second one. The workbench's list asks for the page's annotations (rows()) and
|
|
59
60
|
// goes to one (goTo()): what hides it opened, the page scrolled to it, its card shown as resting on its
|
|
60
61
|
// marker shows it, until a press elsewhere or the words leave the window. Markers stack without
|
|
@@ -109,6 +110,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
109
110
|
let reading = null; // the first read from the server, under way or done (taken back when it failed)
|
|
110
111
|
let counted = false; // that read is in and what it brought found: the page can say how many it has
|
|
111
112
|
let items = new Map(); // id -> { id, created, updated, text, anchor } in page order, as the server last said, with the page's own changes
|
|
113
|
+
let filed = new Map(); // id -> its text as the file last held it (the server's last answer): what 撤销 brings back of one emptied in its card
|
|
112
114
|
let version = null; // the file's, as last read or written
|
|
113
115
|
const lost = new Map(); // id -> an annotation a check deleted this visit (its words gone): back when an undo brings them back
|
|
114
116
|
const timers = new Map(); // id -> the timer of its save (typed, not saved yet)
|
|
@@ -132,9 +134,9 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
132
134
|
let remeasure = true; // all of them to measure again (what may move them: the page's size, its structure), else those in `stale`
|
|
133
135
|
const stale = new Set(); // ids found again since they were measured
|
|
134
136
|
// The annotation whose card shows, or stands in front: { id, focus, hover, keys, located, side, begun,
|
|
135
|
-
//
|
|
137
|
+
// opening, within }: `keys`, opened by the keys, which give the caret back; `located`, shown by
|
|
136
138
|
// the workbench's list; `side`, by its words, "below" or "above" them, kept while it is written;
|
|
137
|
-
// `begun`, new
|
|
139
|
+
// `begun`, new; `opening`, opened for writing and not laid out yet in
|
|
138
140
|
// the margin; `within`, what it is laid out by there instead of its anchor, which is out of the window.
|
|
139
141
|
let shown = null;
|
|
140
142
|
let hot = null; // the id whose words are under the pointer
|
|
@@ -198,6 +200,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
198
200
|
heard = true;
|
|
199
201
|
version = answer.version;
|
|
200
202
|
const dropped = new Set(answer.dropped ?? []);
|
|
203
|
+
filed = new Map((answer.items ?? []).map((item) => [item.id, item.text]));
|
|
201
204
|
const next = new Map();
|
|
202
205
|
for (const item of answer.items ?? []) {
|
|
203
206
|
const mine = items.get(item.id);
|
|
@@ -298,12 +301,13 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
298
301
|
return answer;
|
|
299
302
|
}
|
|
300
303
|
|
|
301
|
-
// Save it now: made (its first save) or changed
|
|
304
|
+
// Save it now: made (its first save) or changed; never with no words (left so, it goes: close).
|
|
302
305
|
async function save(id) {
|
|
303
306
|
clearTimeout(timers.get(id));
|
|
304
307
|
timers.delete(id);
|
|
305
308
|
const item = items.get(id);
|
|
306
309
|
if (!item) return;
|
|
310
|
+
if (!item.text.trim()) return; // no words: nothing to save, the file keeps its last words
|
|
307
311
|
if (!item.anchor) {
|
|
308
312
|
if (!made.has(id)) return;
|
|
309
313
|
item.anchor = anchorOf(id);
|
|
@@ -366,14 +370,16 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
366
370
|
|
|
367
371
|
/** Save what is typed and not saved yet, now (Ctrl+S, the workbench's save, the page closing: `keepalive`). */
|
|
368
372
|
function flush({ keepalive = false } = {}) {
|
|
369
|
-
|
|
373
|
+
// What was typed, and one begun and not saved yet; one with no words saves nothing (save), and one
|
|
374
|
+
// the file has keeps its last words (the page going, no 撤销 could be offered for its going).
|
|
375
|
+
const ids = [...new Set([...timers.keys(), ...[...made.keys()].filter((id) => !sending.has(id))])];
|
|
370
376
|
if (keepalive) {
|
|
371
377
|
sync({ keepalive: true });
|
|
372
378
|
for (const id of ids) {
|
|
373
379
|
clearTimeout(timers.get(id));
|
|
374
380
|
timers.delete(id);
|
|
375
381
|
const item = items.get(id);
|
|
376
|
-
if (!item) continue;
|
|
382
|
+
if (!item?.text.trim()) continue;
|
|
377
383
|
if (!item.anchor && made.has(id)) item.anchor = anchorOf(id);
|
|
378
384
|
postJson(ROUTE, token, { slug, put: wire(item) }, { keepalive: true }).catch(() => {});
|
|
379
385
|
}
|
|
@@ -460,9 +466,9 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
460
466
|
// A card's field has the focus (a card being written); `shown.focus` follows it.
|
|
461
467
|
const focused = () => Boolean(document.activeElement?.closest?.(".ge-note-card"));
|
|
462
468
|
const writing = () => Boolean(shown) && focused();
|
|
463
|
-
// A new one
|
|
464
|
-
//
|
|
465
|
-
const drafted = (id) =>
|
|
469
|
+
// A new one with no words (made on the page, not saved yet, nothing typed but white space): no
|
|
470
|
+
// annotation yet: no marker, no count, no row; left so, it leaves nothing (close).
|
|
471
|
+
const drafted = (id) => made.has(id) && !(items.get(id)?.text ?? "").trim();
|
|
466
472
|
|
|
467
473
|
// The annotation already on `selection` (the same words or blocks), or undefined.
|
|
468
474
|
function sameAs(selection) {
|
|
@@ -533,7 +539,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
533
539
|
if (closing) {
|
|
534
540
|
event.preventDefault();
|
|
535
541
|
event.stopPropagation();
|
|
536
|
-
backToText(
|
|
542
|
+
backToText();
|
|
537
543
|
}
|
|
538
544
|
});
|
|
539
545
|
// A press in a card edits it, the caret where pressed (at the end of its text for a press beside the
|
|
@@ -604,7 +610,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
604
610
|
if (!focus) return true;
|
|
605
611
|
card.field.focus({ preventScroll: true });
|
|
606
612
|
if (document.activeElement !== card.field) {
|
|
607
|
-
if (
|
|
613
|
+
if (drafted(id)) {
|
|
608
614
|
items.delete(id);
|
|
609
615
|
made.delete(id);
|
|
610
616
|
dropView(id);
|
|
@@ -627,6 +633,15 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
627
633
|
wake();
|
|
628
634
|
}
|
|
629
635
|
|
|
636
|
+
// What 撤销 puts back of one going at the reader's word (its delete button, emptied and closed): as
|
|
637
|
+
// it is, or, emptied in its card, with the words the file last held; null when the file never had it.
|
|
638
|
+
function backOf(id) {
|
|
639
|
+
const item = items.get(id);
|
|
640
|
+
if (!item?.anchor) return null;
|
|
641
|
+
const text = item.text.trim() ? item.text : filed.get(id);
|
|
642
|
+
return text ? { ...wire(item), text } : null;
|
|
643
|
+
}
|
|
644
|
+
|
|
630
645
|
// An annotation gone at the reader's word, from the page and the file (one never saved leaves nothing).
|
|
631
646
|
function forget(id) {
|
|
632
647
|
const item = items.get(id);
|
|
@@ -640,26 +655,30 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
640
655
|
dropView(id);
|
|
641
656
|
}
|
|
642
657
|
|
|
643
|
-
// The card closes into its marker: what was typed saved at once
|
|
644
|
-
//
|
|
645
|
-
//
|
|
658
|
+
// The card closes into its marker: what was typed saved at once. One left with no words is none: it
|
|
659
|
+
// goes, from the page and the file, and one the file had is offered back as its delete button's is
|
|
660
|
+
// (`removed`, with its last words). `drop`: it goes whatever it says (its delete button, which offers
|
|
661
|
+
// it back itself). With room it goes back among the others.
|
|
646
662
|
function close({ drop = false } = {}) {
|
|
647
663
|
if (!shown) return;
|
|
648
664
|
const { id } = shown;
|
|
649
665
|
shown = null;
|
|
650
666
|
const item = items.get(id);
|
|
651
|
-
if (drop
|
|
652
|
-
|
|
667
|
+
if (item && (drop || !item.text.trim())) {
|
|
668
|
+
const back = drop ? null : backOf(id);
|
|
669
|
+
forget(id);
|
|
670
|
+
if (back) removed([back]);
|
|
671
|
+
} else if (item && (timers.has(id) || !item.anchor)) save(id);
|
|
653
672
|
changed();
|
|
654
673
|
wake();
|
|
655
674
|
}
|
|
656
675
|
|
|
657
|
-
// A card's delete button: its annotation goes at once, and the workbench offers it back
|
|
658
|
-
// saved leaves nothing to bring back.
|
|
676
|
+
// A card's delete button: its annotation goes at once, and the workbench offers it back (emptied in
|
|
677
|
+
// its card, with the words the file last held). One never saved leaves nothing to bring back.
|
|
659
678
|
function erase(id) {
|
|
660
679
|
const item = items.get(id);
|
|
661
680
|
if (!item) return;
|
|
662
|
-
const was =
|
|
681
|
+
const was = backOf(id);
|
|
663
682
|
if (shown?.id !== id) {
|
|
664
683
|
forget(id);
|
|
665
684
|
changed();
|
|
@@ -699,7 +718,6 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
699
718
|
const was = drafted(id);
|
|
700
719
|
item.text = field.value;
|
|
701
720
|
item.updated = new Date().toISOString();
|
|
702
|
-
if (item.text.trim() && shown?.id === id) shown.held = true;
|
|
703
721
|
schedule(id);
|
|
704
722
|
changed();
|
|
705
723
|
if (!standing || was !== drafted(id)) wake();
|
|
@@ -824,7 +842,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
824
842
|
const view = views.get(id);
|
|
825
843
|
if (!view || drafted(id)) return false;
|
|
826
844
|
const start = view.range?.startContainer ?? view.units[0];
|
|
827
|
-
|
|
845
|
+
reveal(start.nodeType === 1 ? start : start.parentElement); // a folded heading's section too (fold/)
|
|
828
846
|
remeasure = true; // what was hidden stands now, before the observer says so
|
|
829
847
|
const line = lineOf(view);
|
|
830
848
|
if (!line) return false;
|
|
@@ -896,7 +914,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
896
914
|
const view = known ?? {};
|
|
897
915
|
if (!found || (found.units?.length ?? 0) === 0) {
|
|
898
916
|
// A new one with no words, not saved yet, is about nothing now: it goes with them, for no save to keep.
|
|
899
|
-
if (
|
|
917
|
+
if (drafted(id) && !item.anchor) {
|
|
900
918
|
forget(id);
|
|
901
919
|
if (shown?.id === id) shown = null;
|
|
902
920
|
continue;
|
|
@@ -946,16 +964,25 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
946
964
|
}
|
|
947
965
|
|
|
948
966
|
// The outermost closed disclosure `node` is hidden in (in its content, not its title, nor `node`
|
|
949
|
-
// itself: it shows, as its title), or
|
|
967
|
+
// itself: it shows, as its title), or the folded heading whose section hides it (fold/: the
|
|
968
|
+
// outermost, the heading its own title), whichever holds the other; null where neither hides it.
|
|
950
969
|
function closedAround(node) {
|
|
951
970
|
const element = node?.nodeType === 1 ? node : node?.parentElement;
|
|
952
971
|
let found = null;
|
|
953
972
|
for (let details = element?.closest("details:not([open])"); details; details = details.parentElement?.closest("details:not([open])")) {
|
|
954
973
|
if (details !== node && !details.querySelector(":scope > summary")?.contains(element)) found = details;
|
|
955
974
|
}
|
|
956
|
-
|
|
975
|
+
let marked = null;
|
|
976
|
+
for (let at = element?.closest("[data-ge-folded]"); at; at = at.parentElement?.closest("[data-ge-folded]")) marked = at;
|
|
977
|
+
const heading = marked && document.querySelector(`[data-ge-id="${CSS.escape(marked.getAttribute("data-ge-folded"))}"]`);
|
|
978
|
+
if (!heading) return found;
|
|
979
|
+
return found && found.contains(heading) ? found : heading;
|
|
957
980
|
}
|
|
958
981
|
|
|
982
|
+
// What stands for a closed disclosure or a folded section (closedAround): the disclosure's title, or
|
|
983
|
+
// the heading itself, which has none.
|
|
984
|
+
const titleOf = (closed) => closed.querySelector(":scope > summary") ?? closed;
|
|
985
|
+
|
|
959
986
|
// The first line of `node` (window coordinates): { top, height, left }: of its first words; where a
|
|
960
987
|
// picture standing as a block comes before them, the picture's top (as the handle's first line,
|
|
961
988
|
// handle/: not its caption, far from there); its box's top where it holds neither.
|
|
@@ -987,13 +1014,13 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
987
1014
|
// holds), box (what it stands for as shown) }, or null when it does not show: its first line, or all
|
|
988
1015
|
// of them for several blocks, from the first's top to the last's bottom, the marker at their middle.
|
|
989
1016
|
// In a closed disclosure (laid out though hidden), its title's line: checked first, as the hidden
|
|
990
|
-
// text still has boxes.
|
|
1017
|
+
// text still has boxes; in a folded heading's section (fold/), the heading's.
|
|
991
1018
|
function lineOf(view) {
|
|
992
1019
|
const start = view.range?.startContainer ?? view.units?.find((unit) => unit.isConnected);
|
|
993
1020
|
if (!start?.isConnected) return null;
|
|
994
1021
|
const closed = closedAround(start);
|
|
995
1022
|
if (closed) {
|
|
996
|
-
const summary = closed
|
|
1023
|
+
const summary = titleOf(closed);
|
|
997
1024
|
if (!summary?.getClientRects().length) return null;
|
|
998
1025
|
const box = summary.getBoundingClientRect();
|
|
999
1026
|
return { ...firstLine(summary), folded: true, box, bottom: box.bottom };
|
|
@@ -1074,13 +1101,13 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
1074
1101
|
// What a card stands by (window coordinates), or null when it does not show: words, their box and
|
|
1075
1102
|
// where their last line ends ({ words, top, left, right: the last character's, bottom: the last
|
|
1076
1103
|
// line's }); blocks, theirs ({ top, bottom and right: the last one's }); in a closed disclosure (laid
|
|
1077
|
-
// out though hidden), its title as a block.
|
|
1104
|
+
// out though hidden), its title as a block; in a folded heading's section, the heading.
|
|
1078
1105
|
function anchorBox(target) {
|
|
1079
1106
|
const start = target.range?.startContainer ?? target.units?.find((unit) => unit.isConnected);
|
|
1080
1107
|
if (!start?.isConnected) return null;
|
|
1081
1108
|
const closed = closedAround(start);
|
|
1082
1109
|
if (closed) {
|
|
1083
|
-
const summary = closed
|
|
1110
|
+
const summary = titleOf(closed);
|
|
1084
1111
|
if (!summary?.getClientRects().length) return null;
|
|
1085
1112
|
const box = summary.getBoundingClientRect();
|
|
1086
1113
|
return { top: box.top, bottom: box.bottom, right: box.right };
|
|
@@ -1379,7 +1406,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
1379
1406
|
quiet = false;
|
|
1380
1407
|
|
|
1381
1408
|
// A block anchor's blocks filled while it is shown, or its card in the margin is under the pointer;
|
|
1382
|
-
// those in a closed disclosure, the disclosure.
|
|
1409
|
+
// those in a closed disclosure, the disclosure; in a folded section, its heading.
|
|
1383
1410
|
const filled = views.get(shown?.id ?? (standing ? hot : null));
|
|
1384
1411
|
fills.paint(filled && !filled.range && !filled.hidden ? ordered(filled.units.filter((unit) => unit.isConnected).map((unit) => closedAround(unit) ?? unit)) : []);
|
|
1385
1412
|
paintMarks();
|
|
@@ -1562,7 +1589,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
1562
1589
|
},
|
|
1563
1590
|
};
|
|
1564
1591
|
|
|
1565
|
-
/** How many annotations the page has (their words or blocks still there; a new one once
|
|
1592
|
+
/** How many annotations the page has (their words or blocks still there; a new one once it has words). */
|
|
1566
1593
|
const count = () => [...items.keys()].filter((id) => views.has(id) && !drafted(id)).length;
|
|
1567
1594
|
|
|
1568
1595
|
provide("annotations", () => ({
|
|
@@ -42,6 +42,10 @@ const QUIET = 1500; // how long the reader has changed nothing before the page r
|
|
|
42
42
|
const STORE_EVERY = 1000; // the crash buffer while typing, at most this often
|
|
43
43
|
const JOURNAL_FOR = 60 * 60 * 1000; // how long a committed text is checked for being put back
|
|
44
44
|
const OWN_FOR = 5000; // how long the live-reload channel may still announce a version this page wrote
|
|
45
|
+
// A page of the Agent's own (its address's ?agent: the Agent 自用 one gallery open prints, gallery
|
|
46
|
+
// component try's): its commits say so (`agent`), and are no reader's edit to the review of the
|
|
47
|
+
// Agent's changes, so none ends a document's first writing (docs/editor.md "Agent's changes").
|
|
48
|
+
const AGENT_PAGE = typeof location !== "undefined" && new URLSearchParams(location.search).has("agent");
|
|
45
49
|
|
|
46
50
|
// A committed text as the revert check names it, whichever load named it: the report's title, a block of
|
|
47
51
|
// a unit by the unit's id (or what named the unit, with none) and its place in it, any other by what
|
|
@@ -347,6 +351,8 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
347
351
|
*/
|
|
348
352
|
function heard(version, anyway = false, at = Date.now()) {
|
|
349
353
|
if (closed) return;
|
|
354
|
+
// A write the reader asked for is on its way (showOwn): what is heard meanwhile is its own, or heard again once it failed.
|
|
355
|
+
if (owning) return void (missed = true);
|
|
350
356
|
// It may be the commit on its way, or one whose answer was lost: known once that one is answered
|
|
351
357
|
// (as of when it was announced).
|
|
352
358
|
if ((flight || parked) && !anyway) return void events.push([version, at]);
|
|
@@ -396,10 +402,13 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
396
402
|
} catch {}
|
|
397
403
|
checking = false;
|
|
398
404
|
if (closed) return false;
|
|
405
|
+
// Of the blocks it made that the write took out, those inside another such go with it, one card.
|
|
406
|
+
const goneUnits = (answer?.units ?? []).filter(({ gone }) => gone).map(({ key }) => key);
|
|
399
407
|
const back = answer?.ok ? [
|
|
400
408
|
...answer.reverted.map(({ key, theirs }) => ({ ...texts.find((entry) => entry.key === key), theirs })).filter((entry) => entry.key),
|
|
401
|
-
// A block put back: by its unit, with what it holds (the reader's words, to copy)
|
|
402
|
-
|
|
409
|
+
// A block put back: by its unit, with what it holds (the reader's words, to copy) and, taken out, the
|
|
410
|
+
// blocks taken out inside it (`within`, decided with it; what it holds that the file still has is not).
|
|
411
|
+
...(answer.units ?? []).filter(({ key, gone }) => units.some((unit) => unit.key === key) && !(gone && commits.heldWithin?.(key, goneUnits))).map(({ key, gone }) => ({ key: `p${key}`, unit: key, gone: Boolean(gone), mine: commits.unitText(key), ...(gone ? { within: goneUnits.filter((inner) => commits.heldWithin?.(inner, [key])) } : {}) })),
|
|
403
412
|
// Parts a component wrote put back: by its unit, with the hashes the file holds of them (`written`)
|
|
404
413
|
// and what the page holds of them (`data`, the reader's, kept with the crash buffer); none the page
|
|
405
414
|
// holds as the file does, however the file spells it (heldOtherwise).
|
|
@@ -499,7 +508,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
499
508
|
// A block put back: kept (made again, or moved again where the page has it), or the file's. Parts a
|
|
500
509
|
// component wrote: written again over the file's (its hashes of them), or the file's.
|
|
501
510
|
if (entry.written) commits.rewrite(entry.unit, entry.written, mine);
|
|
502
|
-
else commits.putBack(entry.unit, { gone: entry.gone, mine });
|
|
511
|
+
else commits.putBack(entry.unit, { gone: entry.gone, mine, within: entry.within });
|
|
503
512
|
reverts.delete(key);
|
|
504
513
|
storedFrom = null;
|
|
505
514
|
if (mine) changed(true);
|
|
@@ -523,6 +532,42 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
523
532
|
return true;
|
|
524
533
|
}
|
|
525
534
|
|
|
535
|
+
/**
|
|
536
|
+
* A write the reader asked for that changes the file (撤回 of the Agent's changes and its 撤销:
|
|
537
|
+
* changes/): `run()` makes it, resolving with whether it did; then the page shows the file as after a
|
|
538
|
+
* write of its own, at once, asking nothing of what it put back (the revert check: the reader asked
|
|
539
|
+
* for it), and what is heard meanwhile shows nothing first. The caller saved what waited first
|
|
540
|
+
* (flush) and made sure nothing waits for the reader. Resolves with whether the page goes.
|
|
541
|
+
*/
|
|
542
|
+
let owning = false; // showOwn's write is on its way
|
|
543
|
+
let missed = false; // and the live-reload channel spoke meanwhile
|
|
544
|
+
async function showOwn(run) {
|
|
545
|
+
if (closed || reloading) return false;
|
|
546
|
+
reloading = owning = true;
|
|
547
|
+
missed = false;
|
|
548
|
+
clearTimeout(reloadTimer);
|
|
549
|
+
let done = false;
|
|
550
|
+
try {
|
|
551
|
+
done = Boolean(await run());
|
|
552
|
+
} catch {}
|
|
553
|
+
owning = false;
|
|
554
|
+
if (closed) return done;
|
|
555
|
+
if (!done) {
|
|
556
|
+
reloading = false;
|
|
557
|
+
refresh();
|
|
558
|
+
if (missed) heard(null, true);
|
|
559
|
+
else if (foreign) maybeReload();
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
storage.remove(bufferKey());
|
|
563
|
+
storage.remove(keptKey());
|
|
564
|
+
storedFrom = null;
|
|
565
|
+
keepJournal();
|
|
566
|
+
refresh();
|
|
567
|
+
reloadView(map.slug, null, null);
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
526
571
|
/** Show the file as it is once nothing waits (the reader gave a change up for the file's, or what the page is made of changed). */
|
|
527
572
|
function reloadSoon() {
|
|
528
573
|
foreign = true;
|
|
@@ -556,6 +601,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
556
601
|
store,
|
|
557
602
|
decide,
|
|
558
603
|
reloadSoon,
|
|
604
|
+
showOwn,
|
|
559
605
|
close,
|
|
560
606
|
get state() {
|
|
561
607
|
return { waiting: waiting(), syncing: Boolean(flight), offline, stopped, foreign, reloading, held: commits.issues.size + reverts.size, drafts, unstored };
|
|
@@ -566,7 +612,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
566
612
|
// A commit sent: the server's answer, or { ok: false, reason: "server" } when it failed or answered
|
|
567
613
|
// no JSON (a lost connection rejects: the caller reads that as offline).
|
|
568
614
|
async function postCommit(map, request, { keepalive = false } = {}) {
|
|
569
|
-
const response = await postJson("/__gallery/edit/commit", map.token, { slug: map.slug, ...request }, { keepalive });
|
|
615
|
+
const response = await postJson("/__gallery/edit/commit", map.token, { slug: map.slug, ...request, ...(AGENT_PAGE ? { agent: true } : {}) }, { keepalive });
|
|
570
616
|
if (response.status >= 500) return { ok: false, reason: "server" };
|
|
571
617
|
return response.json().catch(() => ({ ok: false, reason: "server" }));
|
|
572
618
|
}
|
|
@@ -6,16 +6,16 @@ import { isHost } from "./core.mjs";
|
|
|
6
6
|
// (selection/).
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Whether a click to the right of a block
|
|
10
|
-
*
|
|
11
|
-
* and the boxes text flows in (a list, a quote, a
|
|
9
|
+
* Whether a click to the right of a block, past the text area it stands in (insert/ boxedBeside),
|
|
10
|
+
* selects it: a boxed block (a code block, a highlight block, a disclosure, a card, a table, a
|
|
11
|
+
* divider…) or a row of columns, as opposed to text and the boxes text flows in (a list, a quote, a
|
|
12
|
+
* section).
|
|
12
13
|
*/
|
|
13
14
|
export const selectsBeside = (unit) => BESIDE.has(unit?.getAttribute?.("data-ge-unit-kind"));
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Whether Enter on a block selected (the first of those selected) puts a new paragraph after them
|
|
17
18
|
* (blocks.mjs, insert.paragraphAfter): a block of no text of its own, those a click to their right
|
|
18
|
-
* selects and a 选项卡区块; not a text block among them (a
|
|
19
|
-
* block, whose editor it opens).
|
|
19
|
+
* selects and a 选项卡区块; not a text block among them (a formula block, whose editor it opens).
|
|
20
20
|
*/
|
|
21
21
|
export const entersAfter = (unit) => Boolean(unit) && !isHost(unit) && ENTERS_AFTER.has(unit.getAttribute("data-ge-unit-kind"));
|
|
@@ -12,7 +12,7 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
12
12
|
// show one of the nine with none of a family (a callout's background is blue: `defaults`), that
|
|
13
13
|
// family has no 默认: that color is ringed for none, and picking it takes the class away. Each
|
|
14
14
|
// swatch names its color in a tooltip. The swatches are drawn with the kit's own classes, so they
|
|
15
|
-
// match the text in either theme. A pick gives every target that color of its family, the other family's staying (one
|
|
15
|
+
// match the text in either theme, a background's as the block's fill (`--doc-fill`). A pick gives every target that color of its family, the other family's staying (one
|
|
16
16
|
// undo step: look, history.mjs; a table cell's one color: table/). Only the families the targets take show.
|
|
17
17
|
|
|
18
18
|
const STYLE = `
|
|
@@ -21,6 +21,8 @@ const STYLE = `
|
|
|
21
21
|
.ge-pop .ge-color-cell > span { display: flex; align-items: center; justify-content: center; box-sizing: border-box; width: 26px; height: 26px; border-radius: 6px;
|
|
22
22
|
font-size: 16px; font-weight: 500; line-height: 1; box-shadow: inset 0 0 0 1px var(--ge-ring); }
|
|
23
23
|
.ge-pop .ge-color-cell > span:not([class*="doc-text-"]) { color: var(--ge-tx); }
|
|
24
|
+
/* A block's background swatch shows the block's fill, not the words' deeper one the kit gives a span. */
|
|
25
|
+
.ge-pop .ge-color-cell > span[class*="doc-highlight-"] { background: var(--doc-fill); }
|
|
24
26
|
.ge-pop .ge-color-cell:hover > span, .ge-pop .ge-color-cell[data-active] > span { box-shadow: inset 0 0 0 2px var(--ge-ring); }
|
|
25
27
|
.ge-pop .ge-color-cell[aria-current="true"] > span { box-shadow: inset 0 0 0 2px var(--ge-ring-strong); }
|
|
26
28
|
${Object.entries(RINGS).map(([name, [light, dark, strong, strongDark]]) => `.ge-pop .ge-color-cell[data-ring="${name}"] { --ge-ring: light-dark(${light}, ${dark}); --ge-ring-strong: light-dark(${strong}, ${strongDark}); }`).join("\n ")}
|
|
@@ -54,7 +54,7 @@ const WIDTH = MENU_WIDTH; // the menu, as wide as its grid (no room left beside
|
|
|
54
54
|
* color rows). copyLink(target): the block's address on the clipboard, in the click on its row; null
|
|
55
55
|
* on a page no address leads to; linkAhead(): the menu opens with that row, what the click will want is read now.
|
|
56
56
|
*/
|
|
57
|
-
export function createBlockMenu({ info, menu, selection, remove, turnInto, convert, turnIntoAll = () => null, convertAll = () => {}, below, belowTypes = () => [], add, look, copyLink = () => {}, linkAhead = () => {} }) {
|
|
57
|
+
export function createBlockMenu({ info, menu, selection, remove, takenWith = (units) => units, turnInto, convert, turnIntoAll = () => null, convertAll = () => {}, below, belowTypes = () => [], add, look, copyLink = () => {}, linkAhead = () => {} }) {
|
|
58
58
|
const chapters = sectionOps();
|
|
59
59
|
const columns = columnOps();
|
|
60
60
|
const { kindOf, nameOf, keepText, boxKindOf } = info;
|
|
@@ -95,12 +95,13 @@ export function createBlockMenu({ info, menu, selection, remove, turnInto, conve
|
|
|
95
95
|
// where it is. What it holds comes out as it stands, so a block in a column keeps its column, the row
|
|
96
96
|
// of columns coming out whole, and one in a section in a Sheet panel ends in the panel. None for a
|
|
97
97
|
// block that has no 分区与分栏 › of its own (no columns row, in a section: a list item, a flowchart's
|
|
98
|
-
// node, a quote's later paragraph, a block an item holds)
|
|
98
|
+
// node, a quote's later paragraph, a block an item holds): asked of the block alone (a folded heading
|
|
99
|
+
// whose section a column cannot take keeps it).
|
|
99
100
|
const dissolveRow = (units) => {
|
|
100
101
|
const section = chapters?.dissolvesFrom(units[0]);
|
|
101
102
|
if (!section || units.some((unit) => chapters.dissolvesFrom(unit) !== section)) return [];
|
|
102
103
|
const own = units[0] === section;
|
|
103
|
-
if (!own && units.some((unit) => (columns?.columnsCheck([unit]) ?? null) === null)) return [];
|
|
104
|
+
if (!own && units.some((unit) => (columns?.columnsCheck([unit], (alone) => [alone]) ?? null) === null)) return [];
|
|
104
105
|
const reason = chapters.unwrapCheck(section);
|
|
105
106
|
return [{ label: own ? t("解散分区") : t("解散所在分区"), icon: ICONS.dissolve, disabled: Boolean(reason), note: reason || undefined, run: () => chapters.unwrap(section, own ? null : units) }];
|
|
106
107
|
};
|
|
@@ -149,8 +150,9 @@ export function createBlockMenu({ info, menu, selection, remove, turnInto, conve
|
|
|
149
150
|
const colors = colorRow(group);
|
|
150
151
|
items.push(...colors, ...(colors.length > 0 ? [{ separator: true }] : []));
|
|
151
152
|
// Blocks next to each other in the page or a Sheet panel become a section; a selection with gaps does not.
|
|
152
|
-
|
|
153
|
-
const
|
|
153
|
+
// A folded heading among them goes in with what it hides (fold/).
|
|
154
|
+
const wrap = chapters?.wrapCheck(takenWith(group)) ?? null;
|
|
155
|
+
const sections = [...(wrap !== null ? [{ label: t("组成新分区"), icon: ICONS.group, disabled: Boolean(wrap), note: wrap || undefined, run: () => chapters.wrap(takenWith(group)) }] : []), ...dissolveRow(group)];
|
|
154
156
|
// Neighbours in one container go side by side, a column each.
|
|
155
157
|
items.push(...groupRow(sections, splitRows(group)));
|
|
156
158
|
items.push({ label: t("删除"), icon: ICONS.trash, hint: "Del", disabled: Boolean(refused), note, run: () => remove(group) });
|
|
@@ -193,7 +195,7 @@ export function createBlockMenu({ info, menu, selection, remove, turnInto, conve
|
|
|
193
195
|
}
|
|
194
196
|
const sections = [];
|
|
195
197
|
if (chapter(target)) sections.push({ label: t("在下方新建分区"), icon: ICONS.section, run: () => add(target, "section") });
|
|
196
|
-
if (chapters?.wrapCheck([target]) === "") sections.push({ label: t("组成新分区"), icon: ICONS.group, run: () => chapters.wrap([target]) });
|
|
198
|
+
if (chapters?.wrapCheck(takenWith([target])) === "") sections.push({ label: t("组成新分区"), icon: ICONS.group, run: () => chapters.wrap(takenWith([target])) });
|
|
197
199
|
sections.push(...dissolveRow([target]));
|
|
198
200
|
const splits = splitRows([target]);
|
|
199
201
|
// 选中外层 joins 分区与分栏 › where the block has that submenu; a block with none gets none for it.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createBlockInfo } from "./block-info.mjs";
|
|
2
2
|
import { createBlockMenu } from "./block-menu/index.mjs";
|
|
3
3
|
import { createDrag } from "./drag/index.mjs";
|
|
4
|
+
import { createFolds } from "./fold/index.mjs";
|
|
4
5
|
import { createHandle } from "./handle/index.mjs";
|
|
5
6
|
import { createMenu } from "./menu.mjs";
|
|
6
7
|
import { createDelete } from "./selection/delete.mjs";
|
|
@@ -19,6 +20,7 @@ import { pageObjects } from "./page-objects.mjs";
|
|
|
19
20
|
// block-menu/ the menu a handle opens: turning a block into another type, layout, a new block below, columns, sections, deleting
|
|
20
21
|
// selection/ the selected blocks and their fills; box selection (marquee.mjs); deleting (delete.mjs)
|
|
21
22
|
// drag/ moving blocks: dragging a handle, where they would go (drop.mjs), Ctrl+Shift+arrows
|
|
23
|
+
// fold/ a heading's section folded: the triangle right of its words, what is hidden, the section going with its heading
|
|
22
24
|
//
|
|
23
25
|
// What they share: what the page knows of a block (block-info.mjs), the menu card (menu.mjs), one
|
|
24
26
|
// task of the editor's frames (frames.mjs) that keeps the handle, the bar and the fills where their
|
|
@@ -49,8 +51,9 @@ import { pageObjects } from "./page-objects.mjs";
|
|
|
49
51
|
* notes.write(units) the annotation begun.
|
|
50
52
|
* copyLink(unit): 复制块链接, the block menu's (the editor's copyBlockLink), or null for no such row.
|
|
51
53
|
* linkAhead(): a block's menu opens with that row in it: what the click will want is read now.
|
|
54
|
+
* slug: the report's (what is folded is kept by report: fold/).
|
|
52
55
|
*/
|
|
53
|
-
export function createBlocks({ units, boxes, active, struct, look, say, edit, turnInto, convert, turnIntoAll = () => null, convertAll = () => null, below, belowTypes = () => [], add, after = () => false, blank, nest, keepsHandle = () => false, addNear = () => {}, copyLink = () => {}, linkAhead = () => {}, notes }) {
|
|
56
|
+
export function createBlocks({ slug = "", units, boxes, active, struct, look, say, edit, turnInto, convert, turnIntoAll = () => null, convertAll = () => null, below, belowTypes = () => [], add, after = () => false, blank, nest, keepsHandle = () => false, addNear = () => {}, copyLink = () => {}, linkAhead = () => {}, notes }) {
|
|
54
57
|
const info = createBlockInfo({ units, boxes });
|
|
55
58
|
const menu = createMenu();
|
|
56
59
|
const closeMenu = () => menu.close();
|
|
@@ -59,10 +62,11 @@ export function createBlocks({ units, boxes, active, struct, look, say, edit, tu
|
|
|
59
62
|
const schedule = () => frames.wake();
|
|
60
63
|
|
|
61
64
|
const selection = createSelection({ info, active, say, closeMenu, schedule });
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
65
|
+
const folds = createFolds({ slug, selection, active, schedule });
|
|
66
|
+
const drag = createDrag({ info, selection, struct, say, closeMenu, schedule, folds });
|
|
67
|
+
const blockMenu = createBlockMenu({ info, menu, selection, remove: (targets) => remove(targets), takenWith: folds.takenWith, turnInto, convert, turnIntoAll, convertAll, below, belowTypes, add, look, copyLink, linkAhead });
|
|
68
|
+
const handle = createHandle({ info, active, selection, drag, menu, openMenu: blockMenu.open, drawing: () => marquee.drawing, schedule, keeps: (node) => keepsHandle(node) || folds.holds(node), aside: folds.holds, add: (target, where) => { selection.clear(); addNear(target, where); }, notes });
|
|
69
|
+
const remove = createDelete({ info, selection, struct, say, closeMenu, forget: () => handle.forget(), folds });
|
|
66
70
|
// What a component makes with these tools (`blockTools`): a Sheet's tabs as Notion's pills, a click, a drag, their menu, + (components/sheets/pills.mjs).
|
|
67
71
|
const parts = pageObjects().flatMap((page) => page.blockTools?.({ active, drag, menu, openMenu: blockMenu.open, struct, say }) ?? []);
|
|
68
72
|
// A press on empty space: a box selection, or a click there (marquee.mjs clears the selection).
|
|
@@ -86,8 +90,9 @@ export function createBlocks({ units, boxes, active, struct, look, say, edit, tu
|
|
|
86
90
|
step("drag", () => drag.step(), () => drag.end(false));
|
|
87
91
|
step("selection", () => selection.paint(drag.active), () => selection.unpaint());
|
|
88
92
|
step("handle", () => handle.place(), () => handle.hide());
|
|
93
|
+
step("fold", () => folds.place(handle.pointed), () => folds.hide());
|
|
89
94
|
if (drag.active) return true;
|
|
90
|
-
return handle.showing || selection.picked.length > 0 ? FOLLOW : false;
|
|
95
|
+
return handle.showing || folds.showing || selection.picked.length > 0 ? FOLLOW : false;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
// One tool's part of a frame; when it throws, what that tool draws goes, and it is said.
|
|
@@ -105,7 +110,7 @@ export function createBlocks({ units, boxes, active, struct, look, say, edit, tu
|
|
|
105
110
|
// A press anywhere but on the block tools closes the menu; on a divider line it selects the line,
|
|
106
111
|
// on empty space it may start a box selection, elsewhere it clears the selection.
|
|
107
112
|
document.addEventListener("pointerdown", (event) => {
|
|
108
|
-
if (handle.holds(event.target) || menu.contains(event.target) || parts.some((part) => part.holds(event.target))) return;
|
|
113
|
+
if (handle.holds(event.target) || folds.holds(event.target) || menu.contains(event.target) || parts.some((part) => part.holds(event.target))) return;
|
|
109
114
|
closeMenu();
|
|
110
115
|
const line = event.button === 0 && active() ? handle.lineAt(event.target, event.clientX, event.clientY) : null;
|
|
111
116
|
if (line) {
|
|
@@ -199,6 +204,7 @@ export function createBlocks({ units, boxes, active, struct, look, say, edit, tu
|
|
|
199
204
|
drag.end(false);
|
|
200
205
|
marquee.cancel();
|
|
201
206
|
handle.hide();
|
|
207
|
+
folds.hide();
|
|
202
208
|
selection.unpaint();
|
|
203
209
|
}
|
|
204
210
|
|
|
@@ -206,6 +212,7 @@ export function createBlocks({ units, boxes, active, struct, look, say, edit, tu
|
|
|
206
212
|
if (drag.active) drag.end(false);
|
|
207
213
|
marquee.cancel();
|
|
208
214
|
handle.hide();
|
|
215
|
+
folds.hide();
|
|
209
216
|
parts.forEach((part) => part.hide());
|
|
210
217
|
selection.hide();
|
|
211
218
|
closeMenu();
|
|
@@ -225,6 +232,8 @@ export function createBlocks({ units, boxes, active, struct, look, say, edit, tu
|
|
|
225
232
|
mark: selection.mark,
|
|
226
233
|
keepText: info.keepText,
|
|
227
234
|
moveBy: drag.moveBy,
|
|
235
|
+
/** Headings' sections folded (fold/). */
|
|
236
|
+
folds,
|
|
228
237
|
remove,
|
|
229
238
|
closeMenu,
|
|
230
239
|
/** A block selected and its menu open by `rect` (a component's own button: an image's 更多). */
|
|
@@ -507,7 +507,10 @@ export function reveal(host, line, margin = 24) {
|
|
|
507
507
|
const style = getComputedStyle(scroller ?? document.documentElement);
|
|
508
508
|
const above = kept(style.scrollPaddingTop, margin);
|
|
509
509
|
const below = kept(style.scrollPaddingBottom, margin);
|
|
510
|
-
const
|
|
510
|
+
const exact = line.bottom > view.bottom - below ? line.bottom - view.bottom + below : line.top < view.top + above ? line.top - view.top - above : 0;
|
|
511
|
+
// Whole pixels, away from the line: a scroll by a fraction is rounded by the browser, and rounded
|
|
512
|
+
// down it left a line at the window's bottom a fraction of a pixel past it.
|
|
513
|
+
const delta = exact > 0 ? Math.ceil(exact) : Math.floor(exact);
|
|
511
514
|
if (!delta) return;
|
|
512
515
|
if (scroller) scroller.scrollTop += delta;
|
|
513
516
|
else scrollBy(0, delta);
|