@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TYPE_COMPONENT, blocksHidden } from "../components/registry.mjs";
|
|
2
2
|
|
|
3
|
-
// What the menus that make a block offer of the reader's 设置 ›
|
|
3
|
+
// What the menus that make a block offer of the reader's 设置 › 组件列表 (docs/workbench.md): the one place
|
|
4
4
|
// that hears what is hidden, gallery.json's as the page's map holds it (`hidden`), then as its event
|
|
5
5
|
// stream says it (`doc-settings-changed`), which costs the page nothing. A block type goes with its
|
|
6
6
|
// component. "/", 在下方新建 (both slash-menu/ typeItems) and the handle's grid (block-menu/) read it; no
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
// What is folded in the outline, kept for each report (localStorage, so the page the workbench shows
|
|
2
2
|
// again after an Agent's write, which is a new page, folds what the reader folded): a node's key (a
|
|
3
|
-
// heading's block id, a tab's area and tab), the reports folded last kept and the oldest let go.
|
|
3
|
+
// heading's block id, a tab's area and tab), the reports folded last kept and the oldest let go. The
|
|
4
|
+
// headings folded on the page (fold/) are kept the same way under a key of their own (`key`).
|
|
4
5
|
|
|
5
6
|
import { KEYS } from "../../../shared/renamed-storage.mjs";
|
|
6
7
|
|
|
7
8
|
export const FOLDS_KEY = KEYS.outlineFolds;
|
|
8
9
|
const KEEP = 100; // reports
|
|
9
10
|
|
|
10
|
-
function read(storage) {
|
|
11
|
+
function read(storage, key) {
|
|
11
12
|
try {
|
|
12
|
-
const value = JSON.parse(storage.get(
|
|
13
|
+
const value = JSON.parse(storage.get(key) ?? "null");
|
|
13
14
|
if (value && typeof value === "object" && value.folds && typeof value.folds === "object") return value;
|
|
14
15
|
} catch {}
|
|
15
16
|
return { folds: {} };
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
/** The keys folded in report `slug` (`storage`: { get, set }, page/storage.mjs)
|
|
19
|
-
export function foldsOf(storage, slug) {
|
|
20
|
-
const keys = read(storage).folds[slug];
|
|
19
|
+
/** The keys folded in report `slug` (`storage`: { get, set }, page/storage.mjs), kept under `key`. */
|
|
20
|
+
export function foldsOf(storage, slug, key = FOLDS_KEY) {
|
|
21
|
+
const keys = read(storage, key).folds[slug];
|
|
21
22
|
return new Set(Array.isArray(keys) ? keys.filter((key) => typeof key === "string") : []);
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -25,14 +26,14 @@ export function foldsOf(storage, slug) {
|
|
|
25
26
|
* Keep `folded` for report `slug`: only the keys the page still has (`present`), the report the one
|
|
26
27
|
* folded last; none folded forgets the report. At most KEEP reports, the oldest going first.
|
|
27
28
|
*/
|
|
28
|
-
export function keepFolds(storage, slug, folded, present = null) {
|
|
29
|
-
const value = read(storage);
|
|
30
|
-
const keys = [...folded].filter((
|
|
29
|
+
export function keepFolds(storage, slug, folded, present = null, key = FOLDS_KEY) {
|
|
30
|
+
const value = read(storage, key);
|
|
31
|
+
const keys = [...folded].filter((each) => !present || present.has(each));
|
|
31
32
|
delete value.folds[slug]; // put back last: the report folded last
|
|
32
33
|
if (keys.length) value.folds[slug] = keys;
|
|
33
34
|
const slugs = Object.keys(value.folds);
|
|
34
35
|
for (const old of slugs.slice(0, Math.max(0, slugs.length - KEEP))) delete value.folds[old];
|
|
35
|
-
storage.set(
|
|
36
|
+
storage.set(key, JSON.stringify(value));
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -2,15 +2,17 @@ import { IN_LAYER, Z } from "../../../shared/layers.mjs";
|
|
|
2
2
|
import { onOutlineOpen, onPreferences, outlineOpen, preference, setPreference } from "../../../shared/preferences.mjs";
|
|
3
3
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
4
4
|
import { SHEETS } from "../../components/kit.mjs";
|
|
5
|
+
import { reveal } from "../folded.mjs";
|
|
5
6
|
import { task } from "../frames.mjs";
|
|
6
7
|
import { cancelOnEscape, endsGesture, gestureLock } from "../gesture.mjs";
|
|
7
|
-
import { MARGIN, onMargin, outlineMost, outlineWidth, setMargin } from "../margin.mjs";
|
|
8
|
+
import { MARGIN, listFits, onMargin, outlineMost, outlineWidth, setMargin } from "../margin.mjs";
|
|
8
9
|
import { drawOver, shapeOf } from "../overlay-layer.mjs";
|
|
9
10
|
import { onSettle, own } from "../overlays.mjs";
|
|
10
11
|
import { local } from "../storage.mjs";
|
|
12
|
+
import { hideTooltip, tooltipAt } from "../tooltip.mjs";
|
|
11
13
|
import { addStyle, element } from "../ui.mjs";
|
|
12
14
|
import { carryFolds, foldsOf, keepFolds } from "./folds.mjs";
|
|
13
|
-
import { ROW, createList } from "./list.mjs";
|
|
15
|
+
import { LIST, ROW, createList } from "./list.mjs";
|
|
14
16
|
import { entriesOf, touchesOutline } from "./read.mjs";
|
|
15
17
|
import { STRIP_TOP, createStrip } from "./strip.mjs";
|
|
16
18
|
import { currentOf, followTop, glideFrom, keysOf, markedRow, nodesOf, pathTo, rowsOf, signatureOf, spyLine, treeOf } from "./tree.mjs";
|
|
@@ -27,9 +29,9 @@ import { currentOf, followTop, glideFrom, keysOf, markedRow, nodesOf, pathTo, ro
|
|
|
27
29
|
// What it lists (read.mjs, tree.mjs): the heading blocks, nested by level, a tabs area's tabs as pills
|
|
28
30
|
// with their headings under them. Its form is margin.mjs's: the list in full at the window's right
|
|
29
31
|
// edge, the column in the middle of what it leaves (200px unless the reader dragged its guide: a quiet
|
|
30
|
-
// list on the page's background, a hairline guide down its left
|
|
31
|
-
// it), or, with less room, its strip (strip.mjs), which shows the list
|
|
32
|
-
// lines lighting its own row. A form that changes after the page came is faded in (the list sliding a
|
|
32
|
+
// list on the page's background, a hairline guide down its left close to its top level's titles, the
|
|
33
|
+
// current row's segment dark on it), or, with less room, its strip (strip.mjs), which shows the list
|
|
34
|
+
// as a card while the pointer is on it, each of its lines lighting its own row. A form that changes after the page came is faded in (the list sliding a
|
|
33
35
|
// little from the right), never there all at once.
|
|
34
36
|
//
|
|
35
37
|
// - A click on a heading's row scrolls the page smoothly (at once with reduced motion; from more than
|
|
@@ -55,24 +57,41 @@ import { currentOf, followTop, glideFrom, keysOf, markedRow, nodesOf, pathTo, ro
|
|
|
55
57
|
// reader scrolled it in the last 1.5s (the pointer gone after that, a row scrolled out of sight is
|
|
56
58
|
// brought back). The wheel over it scrolls it, and at its ends the page.
|
|
57
59
|
// - A triangle at a parent's left folds or unfolds it (a folded one always shows it; an open one while
|
|
58
|
-
// the pointer is over the
|
|
59
|
-
// (folds.mjs), so the page the workbench shows again after an Agent's write folds the same, and
|
|
60
|
+
// the pointer is over the rows or the keyboard is in the list, the guide fading out meanwhile: each
|
|
61
|
+
// just short of its title, a top-level one on the guide, the titles never moving); what is folded is kept for the report (folds.mjs), so the page the workbench shows again after an Agent's write folds the same, and
|
|
60
62
|
// follows a heading whose key changes (one with no block id, keyed by its text, typed in or given its
|
|
61
63
|
// id as it is saved). The top level's headings are bold.
|
|
62
64
|
// - Its guide line, dragged, makes the list wider or narrower (160 to 360px, as far as the room lets
|
|
63
65
|
// it while the column keeps its edge room at both sides), the column staying in the middle of what
|
|
64
66
|
// it leaves (margin.mjs: its two rooms giving or taking half each); let go, the width is the
|
|
65
67
|
// reader's for every report (shared/preferences.mjs outlineWidth); Esc puts it back.
|
|
68
|
+
// - Its guide line clicked (pressed and let go before the pointer went CLICK px across, the sidebar's
|
|
69
|
+
// edge's rule) collapses the list to its strip, even where the room gives the list: the reader's for
|
|
70
|
+
// every report, as the width (outlineCollapsed, which the page's head reads too: margin.mjs). The
|
|
71
|
+
// guide in the strip's card clicked takes it back: the list in full where the room allows it
|
|
72
|
+
// (margin.mjs listFits), else the strip stays (never forced) and the list comes once there is room.
|
|
73
|
+
// Each guide says so in a tooltip by the pointer (a tall target), the card's why nothing opened.
|
|
66
74
|
// - It follows the page as it changes (one reading a frame at most, the rows drawn again only when
|
|
67
75
|
// what they show changed): headings typed, renamed, added, deleted, moved, undone, tabs renamed or
|
|
68
76
|
// shown; not while an input method composes (its letters are no title yet: read once it commits).
|
|
69
77
|
// It is the editor's own UI (data-doc-dev): a press on it starts no box selection, the handle
|
|
70
78
|
// does not show by it, and while a block is dragged or a box drawn it lets the pointer through.
|
|
71
79
|
|
|
72
|
-
const DOCK = { top: 52, left: MARGIN.guide, right: 8 }; // the list's room in the dock: below the page's top room,
|
|
80
|
+
const DOCK = { top: 52, left: MARGIN.guide, right: 8 }; // the list's room in the dock: below the page's top room, from 8px in (where the column's right room ends: margin.mjs; its guide LIST.guide further in), 8px short of the scrollbar
|
|
73
81
|
const CARD = 56; // the strip's card: as wide as the list chosen, and this much more
|
|
82
|
+
const CARD_PAD = 8; // and its list this far in from its left edge (as far in as the card's right padding)
|
|
83
|
+
const GUIDE_FADE = 120; // ms the guide fades out as the triangles show, and in as they go (at once with reduced motion: the kit's base.css)
|
|
84
|
+
// The pointer's rest after it left the rows (for the grip, or along the guide from one top-level triangle to another,
|
|
85
|
+
// across the grip between them) before the triangles go and the guide comes back (the grip's darker bar comes as late).
|
|
86
|
+
const LEAVE = 120;
|
|
87
|
+
// What shows the triangles and fades the guide: the pointer on a row (its triangle included), the keys in the list (the
|
|
88
|
+
// focus seen). The pointer on the list but on no row (its gutter, left of the grip) shows nothing: crossing it to the grip
|
|
89
|
+
// changes nothing.
|
|
90
|
+
const OVER = ":has(.ge-outline-row:hover), :focus-visible, :has(:focus-visible)";
|
|
91
|
+
const GRIPS = ".ge-outline-grip, .ge-outline-card-grip"; // the guide's grips (in the list's scroller, the card's)
|
|
74
92
|
const ENTER = { ms: 160, slide: 8 }; // the list in full coming after the page did: faded in, sliding this far from the right
|
|
75
93
|
const RESIZING = "data-ge-outline-resize"; // on the document element while the guide is dragged
|
|
94
|
+
const CLICK = 3; // px across the pointer goes on the guide before a press is a drag, not a click (the workbench's sidebar edge: Sidebar.tsx Resizer)
|
|
76
95
|
const JUMP_TOP = 24; // a heading's top after a jump, below the window's
|
|
77
96
|
const STAY = 40; // px the page moves after a jump before the heading gone to stops being the current one
|
|
78
97
|
const FOLLOW_QUIET = 1500; // ms after the reader scrolled the list, it is not scrolled to the current row
|
|
@@ -105,42 +124,48 @@ const STYLE = `
|
|
|
105
124
|
.ge-outline-scroll[data-more="up"] { -webkit-mask-image: linear-gradient(transparent, #000 24px); mask-image: linear-gradient(transparent, #000 24px); }
|
|
106
125
|
.ge-outline-scroll[data-more="down"] { -webkit-mask-image: linear-gradient(#000 calc(100% - 32px), transparent); mask-image: linear-gradient(#000 calc(100% - 32px), transparent); }
|
|
107
126
|
.ge-outline-scroll[data-more="up down"] { -webkit-mask-image: linear-gradient(transparent, #000 24px, #000 calc(100% - 32px), transparent); mask-image: linear-gradient(transparent, #000 24px, #000 calc(100% - 32px), transparent); }
|
|
108
|
-
/* The guide's grip: dragged, the list wider or narrower; under the pointer (a moment after it came) and while dragged, the guide a wider, darker bar. */
|
|
109
|
-
.ge-outline-grip { position: absolute;
|
|
110
|
-
.ge-outline-grip
|
|
111
|
-
.ge-outline-grip
|
|
127
|
+
/* The guide's grip (MARGIN.grip left of its hairline, 5px right): dragged, the list wider or narrower; clicked, the list collapsed to its strip; under the pointer (a moment after it came) and while dragged, the guide a wider, darker bar. It stands in the list's scroller, placed by the frame (the card), so it lies under a top-level triangle shown (z-index): that triangle, its row's, takes its own box; the grip the rest of the guide (rows with no triangle shown, above and below one), and every one hidden (visibility: no target). */
|
|
128
|
+
.ge-outline-grip, .ge-outline-card-grip { position: absolute; z-index: 1; width: 9px; pointer-events: auto; }
|
|
129
|
+
.ge-outline-grip { left: ${LIST.guide - MARGIN.grip}px; top: 0; bottom: 24px; cursor: col-resize; }
|
|
130
|
+
.ge-outline-grip::after, .ge-outline-card-grip::after { content: ""; position: absolute; left: 3px; top: 4px; bottom: 4px; width: 3px; border-radius: 2px; transition: background-color 120ms ease; }
|
|
131
|
+
.ge-outline-grip:hover::after, .ge-outline-card-grip[data-fits]:hover::after { background: var(--ge-line-strong); transition-delay: 120ms; }
|
|
112
132
|
:root[${RESIZING}] .ge-outline-grip::after { background: color-mix(in srgb, var(--ge-tx3) 70%, transparent); transition-delay: 0s; }
|
|
113
133
|
:root[${RESIZING}] .ge-outline-scroll { pointer-events: none; }
|
|
114
134
|
${gestureLock(RESIZING, { cursor: "col-resize", select: true })}
|
|
115
135
|
|
|
116
|
-
/* The list: a hairline guide
|
|
117
|
-
.ge-outline-list { position: relative; padding-left:
|
|
118
|
-
.ge-outline-list::before { content: ""; position: absolute; left:
|
|
136
|
+
/* The list (list.mjs LIST): a hairline guide a little in from its left edge, close to the top level's titles, the current row's segment darker on it, the lit row's blue (whole pixels: crisp at any scale); the rows from right of the guide (a top-level one's fill reaching across it to take in its triangle: below). */
|
|
137
|
+
.ge-outline-list { position: relative; padding-left: ${LIST.row}px; outline: none; }
|
|
138
|
+
.ge-outline-list::before { content: ""; position: absolute; left: ${LIST.guide}px; top: 4px; bottom: 4px; width: 1px; background: var(--ge-line-strong); }
|
|
119
139
|
.ge-outline-list[data-empty]::before { display: none; }
|
|
120
|
-
.ge-outline-mark { position: absolute; left: -
|
|
140
|
+
.ge-outline-mark { position: absolute; left: ${LIST.guide - 1}px; top: 0; width: 2px; height: 16px; border-radius: 1px; background: var(--ge-tx); pointer-events: none; }
|
|
121
141
|
.ge-outline-mark[data-hot] { background: var(--ge-accent); }
|
|
122
142
|
.ge-outline-mark[hidden] { display: none; }
|
|
123
|
-
|
|
143
|
+
/* While the pointer is on the rows or the keys are in the list (OVER), the triangles show and the guide (its hairline, its segments) fades out at once; the rows left, it comes back a moment later (LEAVE), so the pointer going along the guide from one top-level triangle to another, across the grip, keeps them. */
|
|
144
|
+
.ge-outline-list::before, .ge-outline-list > .ge-outline-mark { transition: opacity ${GUIDE_FADE}ms ease ${LEAVE}ms; }
|
|
145
|
+
.ge-outline-list:is(${OVER})::before, .ge-outline-list:is(${OVER}) > .ge-outline-mark { opacity: 0; transition-delay: 0s; }
|
|
146
|
+
.ge-outline-row { position: relative; display: flex; align-items: center; height: ${ROW}px; box-sizing: border-box; padding: 0 6px 0 calc(${LIST.text}px + var(--ge-depth, 0) * ${LIST.step}px); border-radius: 6px; cursor: pointer; white-space: nowrap; outline: none; }
|
|
124
147
|
.ge-outline-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
125
|
-
|
|
126
|
-
.ge-outline-row
|
|
148
|
+
/* A row's fill (under the pointer, the keys' focus, lit from the strip, pressed) takes in its triangle's column: it starts at the row's left (2px clear of the hairline) or ${LIST.step + LIST.text - LIST.twist - LIST.gap}px left of the column, whichever is further left (that room is what a second-level row's fill leaves left of its triangle). Only a top-level row's column stands left of the row (across the guide), so only its fill reaches further, a step (LIST.step) further left, past the hairline, its triangle in the grey as a deeper one is (the user's of 2026-11-07), a leaf's as its siblings'. That part is the row's shadow, no target: the pointer there is still the list's gutter or the grip. */
|
|
149
|
+
.ge-outline-row { --ge-row-fill: transparent; background: var(--ge-row-fill); box-shadow: min(0px, calc((var(--ge-depth, 0) - 1) * ${LIST.step}px)) 0 var(--ge-row-fill); }
|
|
150
|
+
.ge-outline-row:is(:hover, :focus-visible, [data-hot]) { color: var(--ge-tx); --ge-row-fill: var(--ge-hover); }
|
|
151
|
+
.ge-outline-row:active { --ge-row-fill: var(--ge-press); }
|
|
127
152
|
.ge-outline-row[data-current] { color: var(--ge-tx); font-weight: ${MEDIUM}; }
|
|
128
153
|
/* The top level's headings bold: at the top, or right under a tab (its pill keeps its own look). */
|
|
129
154
|
.ge-outline-row[data-top] { font-weight: 600; }
|
|
130
|
-
/* The triangle at a parent's left,
|
|
131
|
-
.ge-outline-twist
|
|
132
|
-
.ge-outline-twist {
|
|
133
|
-
.ge-outline-twist svg { fill: currentColor; transition: transform 120ms ease; }
|
|
155
|
+
/* The triangle at a parent's left, out of the row's flow (its title where a leaf's is), in its indentation just short of its title, at every level the same: a top-level one on the guide, over its grip (above it: z-index). A folded one always shows (pointing right), an open one (pointing down) while the pointer is on the rows or the keys are in the list (the focus seen: a fold by the pointer leaves the focus on its row, unseen; the timing above). No grey under the pointer, only while pressed. */
|
|
156
|
+
.ge-outline-twist { position: absolute; z-index: 2; top: ${(ROW - 16) / 2}px; left: calc(var(--ge-depth, 0) * ${LIST.step}px + ${LIST.text - LIST.twist - LIST.gap}px); width: ${LIST.twist}px; height: 16px; border-radius: 4px; color: var(--ge-tx3); }
|
|
157
|
+
.ge-outline-twist svg { width: 16px; margin-left: ${(LIST.twist - 16) / 2}px; fill: currentColor; transition: transform 120ms ease; }
|
|
134
158
|
.ge-outline-twist:hover { color: var(--ge-tx2); }
|
|
135
159
|
.ge-outline-twist:active { background: var(--ge-press); }
|
|
136
160
|
.ge-outline-row[aria-expanded="true"] .ge-outline-twist svg { transform: rotate(90deg); }
|
|
137
|
-
.ge-outline-
|
|
161
|
+
.ge-outline-row[aria-expanded="true"] .ge-outline-twist { visibility: hidden; transition: visibility 0s linear ${LEAVE}ms; }
|
|
162
|
+
.ge-outline-list:is(${OVER}) .ge-outline-row[aria-expanded="true"] .ge-outline-twist { visibility: visible; transition-delay: 0s; }
|
|
138
163
|
/* A tabs area's tab: its name as a pill at the rows' size (its text in line with the rows beside it), the tab shown filled as the page's pills, the others ringed by a hairline (plain, they read as less than their own headings). */
|
|
139
164
|
.ge-outline-row[data-kind="tab"] .ge-outline-text { margin-left: -4px; }
|
|
140
165
|
.ge-outline-tab { display: inline-block; max-width: 100%; box-sizing: border-box; height: 22px; padding: 0 6px; border-radius: 6px; overflow: hidden; text-overflow: ellipsis; vertical-align: top; font-weight: ${MEDIUM}; line-height: 22px; box-shadow: inset 0 0 0 1px var(--ge-line-strong); }
|
|
141
166
|
.ge-outline-row[data-shown] .ge-outline-tab { background: var(--ge-hover); color: var(--ge-tx); box-shadow: none; }
|
|
142
167
|
.ge-outline-row[data-shown]:is(:hover, :focus-visible, [data-hot]) .ge-outline-tab { background: transparent; }
|
|
143
|
-
.ge-outline-empty { padding: 4px 0 4px
|
|
168
|
+
.ge-outline-empty { padding: 4px 0 4px ${LIST.text}px; color: var(--ge-tx3); white-space: normal; }
|
|
144
169
|
|
|
145
170
|
/* The strip: short lines, each its own target: the one under the pointer blue, longer and thicker. */
|
|
146
171
|
.ge-outline-strip { position: fixed; top: 0; right: 0; bottom: 0; z-index: ${Z.outline}; width: ${MARGIN.strip}px; pointer-events: none; }
|
|
@@ -152,10 +177,13 @@ const STYLE = `
|
|
|
152
177
|
.ge-outline-ticks i[data-hot] { width: calc(var(--ge-tick, 16px) + 6px); background: var(--ge-accent); transform: scaleY(1.5); }
|
|
153
178
|
.ge-outline-none { display: block; width: 16px; height: 16px; color: var(--ge-tx3); }
|
|
154
179
|
/* Its card: the list over the page beside the strip, its top level with the strip's lines, the menus' surface; the list scrolls inside it, faded at its ends as in full. */
|
|
155
|
-
.ge-outline-card { position: fixed; z-index: ${Z.menu}; top: ${STRIP_TOP}px; right: ${MARGIN.strip + 4}px; width: ${MARGIN.full + CARD}px; max-height: calc(100vh - ${STRIP_TOP + 16}px); box-sizing: border-box; display: flex; flex-direction: column; padding: 8px 8px 8px
|
|
180
|
+
.ge-outline-card { position: fixed; z-index: ${Z.menu}; top: ${STRIP_TOP}px; right: ${MARGIN.strip + 4}px; width: ${MARGIN.full + CARD}px; max-height: calc(100vh - ${STRIP_TOP + 16}px); box-sizing: border-box; display: flex; flex-direction: column; padding: 8px 8px 8px ${CARD_PAD}px; border-radius: 10px; background: var(--ge-bg); box-shadow: var(--ge-shadow-pop); }
|
|
181
|
+
/* Its guide's grip (about the hairline, CARD_PAD + LIST.guide in; in the card's scroller as the dock's is in its own, under a top-level triangle shown): clicked, the list in full again; a hand and the darker bar only where the room allows it (data-fits). */
|
|
182
|
+
.ge-outline-card-grip { left: ${CARD_PAD + LIST.guide - MARGIN.grip}px; top: 8px; bottom: 8px; }
|
|
183
|
+
.ge-outline-card-grip[data-fits] { cursor: pointer; }
|
|
156
184
|
|
|
157
185
|
/* While a block is dragged, a box drawn, text selected across blocks or something resized, the outline lets the pointer through. */
|
|
158
|
-
:root:is([data-ge-drag], [data-ge-marquee], [data-ge-cross], [data-ge-resize], [data-ge-gap-drag], [data-ge-tdrag], [data-ge-tresize], [data-ge-tcells]) :is(.ge-outline-scroll, .ge-outline-ticks, .ge-outline-grip) { pointer-events: none; }
|
|
186
|
+
:root:is([data-ge-drag], [data-ge-marquee], [data-ge-cross], [data-ge-resize], [data-ge-gap-drag], [data-ge-tdrag], [data-ge-tresize], [data-ge-tcells]) :is(.ge-outline-scroll, .ge-outline-ticks, .ge-outline-grip, .ge-outline-card-grip) { pointer-events: none; }
|
|
159
187
|
|
|
160
188
|
/* A heading lit for a moment after a jump: its fill's shape, a quiet gray. */
|
|
161
189
|
.ge-outline-flash { z-index: ${IN_LAYER.tint}; pointer-events: none; background: var(--ge-outline-flash); }
|
|
@@ -178,6 +206,7 @@ export function createOutline({ slug }) {
|
|
|
178
206
|
|
|
179
207
|
let on = outlineOpen(slug); // this report's switch (off until switched on here)
|
|
180
208
|
let chosen = preference("outlineWidth"); // the list's width in full as the reader dragged it (0: the default)
|
|
209
|
+
let collapsed = preference("outlineCollapsed"); // the reader collapsed the list to its strip (a click on its guide)
|
|
181
210
|
let form = null; // the form shown: "full", "strip" or null
|
|
182
211
|
let tree = [];
|
|
183
212
|
let rows = [];
|
|
@@ -199,9 +228,12 @@ export function createOutline({ slug }) {
|
|
|
199
228
|
let flashing = null; // { node, heading }: the heading lit after a jump
|
|
200
229
|
|
|
201
230
|
const list = createList({ jump: go, fold: toggle, escape: giveBack, page: (direction) => page(direction) });
|
|
202
|
-
|
|
231
|
+
// The grip in the scroller, before the list, placed by the frame (it neither scrolls nor counts in what
|
|
232
|
+
// scrolls): in one stacking context with the rows' triangles (the scroller's, masked at its ends), so a
|
|
233
|
+
// top-level one shown stands over it (STYLE).
|
|
203
234
|
const grip = element("div", { class: "ge-outline-grip", role: "separator", "aria-orientation": "vertical", "aria-label": t("拖动调整目录宽度") });
|
|
204
|
-
const
|
|
235
|
+
const scroller = element("div", { class: "ge-outline-scroll" }, grip, list.node);
|
|
236
|
+
const dock = element("nav", { class: "ge-outline", "aria-label": t("目录"), "data-doc-dev": "", hidden: "" }, element("div", { class: "ge-outline-frame" }, scroller));
|
|
205
237
|
const strip = createStrip({
|
|
206
238
|
list,
|
|
207
239
|
opened: () => frames.wake(),
|
|
@@ -318,9 +350,11 @@ export function createOutline({ slug }) {
|
|
|
318
350
|
}
|
|
319
351
|
}
|
|
320
352
|
|
|
353
|
+
// The grips stand in the scrollers but are not the list (placed by the frame, the card): the wheel over one scrolls the page.
|
|
354
|
+
const onList = (target) => holds(target) && !target.closest?.(GRIPS) ? target.closest?.(".ge-outline-scroll") ?? null : null;
|
|
321
355
|
for (const box of [scroller, strip.scroll]) {
|
|
322
356
|
box.addEventListener("scroll", edges, { passive: true });
|
|
323
|
-
for (const type of ["wheel", "touchmove"]) box.addEventListener(type, () => { listScrolled = Date.now(); }, { passive: true });
|
|
357
|
+
for (const type of ["wheel", "touchmove"]) box.addEventListener(type, (event) => { if (onList(event.target)) listScrolled = Date.now(); }, { passive: true });
|
|
324
358
|
}
|
|
325
359
|
for (const node of [dock, strip.strip, strip.card]) {
|
|
326
360
|
node.addEventListener("pointerenter", () => { pointerIn = true; });
|
|
@@ -454,7 +488,7 @@ export function createOutline({ slug }) {
|
|
|
454
488
|
const area = node.ref?.closest(SHEETS.match);
|
|
455
489
|
if (!area) return;
|
|
456
490
|
const key = node.ref.getAttribute(SHEETS.tabKey);
|
|
457
|
-
|
|
491
|
+
reveal([...area.children].find((child) => child.getAttribute(SHEETS.panelKey) === key) ?? area);
|
|
458
492
|
chosenKey = null;
|
|
459
493
|
landedAt = null;
|
|
460
494
|
jumping = true;
|
|
@@ -462,7 +496,7 @@ export function createOutline({ slug }) {
|
|
|
462
496
|
} else {
|
|
463
497
|
const heading = node.ref;
|
|
464
498
|
if (!heading?.isConnected) return;
|
|
465
|
-
|
|
499
|
+
reveal(heading); // a fold hiding it opened, not its own (fold/)
|
|
466
500
|
chosenKey = node.key;
|
|
467
501
|
landedAt = null;
|
|
468
502
|
jumping = true;
|
|
@@ -512,8 +546,8 @@ export function createOutline({ slug }) {
|
|
|
512
546
|
// The wheel over the list scrolls it, and at its end in the wheel's way the page: then it is the reader's.
|
|
513
547
|
const listEnd = (box, down) => (down ? box.scrollTop + box.clientHeight >= box.scrollHeight - 1 : box.scrollTop <= 0);
|
|
514
548
|
addEventListener("wheel", (event) => {
|
|
515
|
-
const box =
|
|
516
|
-
if (!
|
|
549
|
+
const box = onList(event.target);
|
|
550
|
+
if (!box || listEnd(box, event.deltaY > 0)) takeOver();
|
|
517
551
|
}, { passive: true, capture: true });
|
|
518
552
|
addEventListener("touchstart", (event) => { if (!holds(event.target)) takeOver(); }, { passive: true, capture: true });
|
|
519
553
|
addEventListener("keydown", (event) => {
|
|
@@ -604,13 +638,19 @@ export function createOutline({ slug }) {
|
|
|
604
638
|
cardWidth();
|
|
605
639
|
|
|
606
640
|
// Shown in the form the room gives it as the editor starts, at once (it comes with the page), then
|
|
607
|
-
// as the room changes (the window, the list's width) and switched on or off by
|
|
608
|
-
// reader, faded in (switched off, gone at once). Another page of the origin dragging the list's
|
|
609
|
-
// (every report's): followed. This report's outline switched by another page (the workbench's top bar,
|
|
641
|
+
// as the room changes (the window, the list's width, the reader's collapse) and switched on or off by
|
|
642
|
+
// the reader, faded in (switched off, gone at once). Another page of the origin dragging the list's
|
|
643
|
+
// width or collapsing it (every report's): followed. This report's outline switched by another page (the workbench's top bar,
|
|
610
644
|
// the report in another tab): followed; another report's switch changes nothing here.
|
|
611
|
-
show(setMargin({ outline: on, outlineWidth: chosen }));
|
|
645
|
+
show(setMargin({ outline: on, outlineWidth: chosen, collapsed }));
|
|
612
646
|
onMargin((layout) => show(layout, { fade: true }));
|
|
613
647
|
onPreferences(() => {
|
|
648
|
+
const fold = preference("outlineCollapsed");
|
|
649
|
+
if (fold !== collapsed) {
|
|
650
|
+
endSizing(false);
|
|
651
|
+
collapsed = fold;
|
|
652
|
+
setMargin({ collapsed });
|
|
653
|
+
}
|
|
614
654
|
const width = preference("outlineWidth");
|
|
615
655
|
if (width === chosen || sizing) return;
|
|
616
656
|
chosen = width;
|
|
@@ -624,37 +664,42 @@ export function createOutline({ slug }) {
|
|
|
624
664
|
show(setMargin({ outline: on }), { fade: true });
|
|
625
665
|
});
|
|
626
666
|
|
|
627
|
-
// ---------- the width ----------
|
|
667
|
+
// ---------- the width, collapsed ----------
|
|
628
668
|
|
|
629
669
|
// The guide dragged: the list wider or narrower as the pointer goes (the column staying in the middle
|
|
630
670
|
// of what it leaves: margin.mjs), within 160px and what leaves the column its edge room at both sides
|
|
631
|
-
// (outlineMost)
|
|
671
|
+
// (outlineMost), once the pointer has gone CLICK px across (let go short of that, it was a click: the
|
|
672
|
+
// list collapsed); let go, the
|
|
632
673
|
// width is the reader's (every report, every page of the origin); Esc, the window losing the focus or
|
|
633
674
|
// the pointer taken away put it back.
|
|
634
|
-
let sizing = null; // { x, width, most, now, before }
|
|
675
|
+
let sizing = null; // { x, width, most, now, before, moved }
|
|
635
676
|
grip.addEventListener("pointerdown", (event) => {
|
|
636
677
|
if (event.button !== 0 || form !== "full") return;
|
|
637
678
|
event.preventDefault();
|
|
638
679
|
const width = Math.round(dock.getBoundingClientRect().width);
|
|
639
|
-
sizing = { x: event.clientX, width, most: Math.max(width, outlineMost()), now: width, before: chosen };
|
|
680
|
+
sizing = { x: event.clientX, width, most: Math.max(width, outlineMost()), now: width, before: chosen, moved: false };
|
|
640
681
|
try { document.documentElement.setPointerCapture(event.pointerId); } catch {}
|
|
641
682
|
document.documentElement.setAttribute(RESIZING, "");
|
|
642
683
|
});
|
|
643
684
|
document.documentElement.addEventListener("pointermove", (event) => {
|
|
644
685
|
if (!sizing) return;
|
|
686
|
+
if (!sizing.moved && Math.abs(event.clientX - sizing.x) < CLICK) return;
|
|
687
|
+
sizing.moved = true;
|
|
645
688
|
const width = Math.round(Math.max(MARGIN.fullMin, Math.min(sizing.most, sizing.width + sizing.x - event.clientX)));
|
|
646
689
|
if (width === sizing.now) return;
|
|
647
690
|
sizing.now = width;
|
|
648
691
|
setMargin({ outlineWidth: width });
|
|
649
692
|
});
|
|
650
693
|
// Let go where it began (or never moved): nothing chosen, the width chosen before laid out again (the
|
|
651
|
-
// drag gave the layout the width shown, narrower than the one chosen where the room is short).
|
|
694
|
+
// drag gave the layout the width shown, narrower than the one chosen where the room is short). Let go
|
|
695
|
+
// before it moved: a click, the list collapsed.
|
|
652
696
|
function endSizing(keep) {
|
|
653
697
|
if (!sizing) return;
|
|
654
|
-
const { now, before, width } = sizing;
|
|
698
|
+
const { now, before, width, moved } = sizing;
|
|
655
699
|
sizing = null;
|
|
656
700
|
document.documentElement.removeAttribute(RESIZING);
|
|
657
|
-
if (
|
|
701
|
+
if (keep && !moved) setCollapsed(true);
|
|
702
|
+
else if (!keep || now === width) setMargin({ outlineWidth: before });
|
|
658
703
|
else {
|
|
659
704
|
chosen = now;
|
|
660
705
|
setPreference("outlineWidth", now);
|
|
@@ -665,6 +710,47 @@ export function createOutline({ slug }) {
|
|
|
665
710
|
endsGesture({ active: () => Boolean(sizing), cancel: () => endSizing(false) });
|
|
666
711
|
cancelOnEscape({ active: () => Boolean(sizing), cancel: () => endSizing(false) });
|
|
667
712
|
|
|
713
|
+
// The reader's collapse, kept for every report and every page of the origin (the page's head reads it
|
|
714
|
+
// before the page shows); the form is the room's to give (margin.mjs): taken back where the room is
|
|
715
|
+
// short, the strip stays and the list comes once there is room.
|
|
716
|
+
function setCollapsed(value) {
|
|
717
|
+
if (value === collapsed) return;
|
|
718
|
+
collapsed = value;
|
|
719
|
+
setPreference("outlineCollapsed", value);
|
|
720
|
+
setMargin({ collapsed: value });
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// A tooltip by the pointer on a guide (a tall target: over where the pointer rests, as the
|
|
724
|
+
// workbench's sidebar edge has it), once it has rested there a moment; none while the guide is pressed.
|
|
725
|
+
const tipAt = (event, text, hint) => tooltipAt({ left: event.clientX, right: event.clientX, top: event.clientY, bottom: event.clientY, width: 0, height: 0 }, text, hint);
|
|
726
|
+
function guideTip(node, says) {
|
|
727
|
+
const rest = (event) => {
|
|
728
|
+
if (sizing || event.buttons || document.querySelector(".ge-tip")) return; // shown: it stays where it came
|
|
729
|
+
tipAt(event, ...says());
|
|
730
|
+
};
|
|
731
|
+
node.addEventListener("pointerenter", rest);
|
|
732
|
+
node.addEventListener("pointermove", rest);
|
|
733
|
+
node.addEventListener("pointerleave", hideTooltip);
|
|
734
|
+
node.addEventListener("pointerdown", hideTooltip);
|
|
735
|
+
}
|
|
736
|
+
const waiting = () => t("页面不够宽,变宽后展开");
|
|
737
|
+
guideTip(grip, () => [t("收起目录"), t("拖动调整宽度")]);
|
|
738
|
+
// The card's guide: a hand and its darker bar only where the room gives the list (data-fits), else
|
|
739
|
+
// its tooltip says why a click opens nothing (the list comes once there is room).
|
|
740
|
+
const fits = () => {
|
|
741
|
+
const now = listFits();
|
|
742
|
+
strip.guide.toggleAttribute("data-fits", now);
|
|
743
|
+
return now;
|
|
744
|
+
};
|
|
745
|
+
guideTip(strip.guide, () => [t("展开目录"), fits() ? null : waiting()]);
|
|
746
|
+
strip.guide.addEventListener("pointerenter", fits);
|
|
747
|
+
strip.guide.addEventListener("click", (event) => {
|
|
748
|
+
if (event.button !== 0 || form !== "strip") return;
|
|
749
|
+
const room = fits();
|
|
750
|
+
setCollapsed(false);
|
|
751
|
+
if (!room) tipAt(event, t("展开目录"), waiting());
|
|
752
|
+
});
|
|
753
|
+
|
|
668
754
|
|
|
669
755
|
// The page changing: read again on the next frame when it may change what is listed (one reading
|
|
670
756
|
// a frame however much changed; words typed elsewhere only move the headings: the current one again).
|
|
@@ -6,16 +6,30 @@ import { keyAction } from "./tree.mjs";
|
|
|
6
6
|
// The outline's rows (outline/index.mjs puts them in its dock, or in the strip's card): a tree as
|
|
7
7
|
// aria has it (role tree, a treeitem a row with its level, open or folded, the current one
|
|
8
8
|
// aria-current), one tab stop (the current row, else the first), the keys of tree.mjs keyAction.
|
|
9
|
-
// A row's title, cut short, shows whole in the shared tooltip,
|
|
10
|
-
// not the rows above); a parent's triangle
|
|
11
|
-
//
|
|
12
|
-
// one while the pointer is
|
|
13
|
-
// elsewhere on the row goes to its heading. The strip (strip.mjs) lights a row
|
|
14
|
-
// one its line under the pointer stands for, filled as under the pointer, a
|
|
9
|
+
// A row's title, cut short, shows whole in the shared tooltip, left of the list (over the gutter,
|
|
10
|
+
// not the rows above); a parent's triangle just short of its title, out of the row's flow (titles of
|
|
11
|
+
// a level line up, a leaf's too), folds or unfolds it (a folded one always
|
|
12
|
+
// shows it, an open one while the pointer is on the rows or the keyboard is in the list: STYLE in
|
|
13
|
+
// index.mjs); a click elsewhere on the row goes to its heading. The strip (strip.mjs) lights a row
|
|
14
|
+
// from outside (hot): the one its line under the pointer stands for, filled as under the pointer, a
|
|
15
|
+
// blue segment on the guide.
|
|
15
16
|
|
|
16
17
|
export const ROW = 28; // a row's height
|
|
17
18
|
const MARK = 16; // the current row's segment on the guide (and the hot row's)
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The list's geometry (px from its left edge, where the column's right room ends: margin.mjs; index.mjs
|
|
22
|
+
* STYLE draws it): the guide's hairline at `guide` (its grip from MARGIN.grip left of it to 5px right);
|
|
23
|
+
* the rows from `row` (their fill 2px clear of the hairline, a top-level row's a `step` further left to take in
|
|
24
|
+
* its triangle: index.mjs STYLE); a title `text` into its row, `step`
|
|
25
|
+
* further a level (the top level's 26px in, 8px clear of the hairline; a tab's pill 22px in, 4px clear);
|
|
26
|
+
* a parent's triangle `twist` wide (its 16px glyph centered on it) in its row's indentation, `gap`
|
|
27
|
+
* short of its title at every level (the top level's from 11px in, on the hairline and its grip, which it
|
|
28
|
+
* stands over while shown; the next level's from 23px in). Until 2026-11-07 the top level's stood in the
|
|
29
|
+
* list's first 13px, left of the guide, which the user found too far from their titles.
|
|
30
|
+
*/
|
|
31
|
+
export const LIST = Object.freeze({ twist: 13, guide: 17, row: 20, text: 6, step: 12, gap: 2 });
|
|
32
|
+
|
|
19
33
|
// A small solid triangle on the 20-unit grid, pointing right (folded); turned down when open.
|
|
20
34
|
export const TRIANGLE = '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M8 6.5v7l5-3.5z"/></svg>';
|
|
21
35
|
|
|
@@ -43,16 +57,14 @@ export function createList({ jump, fold, escape, page }) {
|
|
|
43
57
|
if (item.kind === "tab") label.append(named);
|
|
44
58
|
else label.textContent = item.text;
|
|
45
59
|
const open = !folded.has(item.key);
|
|
46
|
-
const twist = item.children.length
|
|
47
|
-
|
|
48
|
-
: element("span", { class: "ge-outline-slot", "aria-hidden": "true" });
|
|
49
|
-
const row = element("div", { class: "ge-outline-row", role: "treeitem", tabindex: "-1", "aria-level": String(depth + 1), "data-index": String(index), "data-kind": item.kind }, twist, label);
|
|
60
|
+
const twist = item.children.length ? [element("span", { class: "ge-outline-twist", role: "button", "aria-label": open ? t("折叠") : t("展开"), html: TRIANGLE })] : [];
|
|
61
|
+
const row = element("div", { class: "ge-outline-row", role: "treeitem", tabindex: "-1", "aria-level": String(depth + 1), "data-index": String(index), "data-kind": item.kind }, ...twist, label);
|
|
50
62
|
row.style.setProperty("--ge-depth", String(depth));
|
|
51
63
|
if (item.kind === "tab" && item.shown) row.dataset.shown = "";
|
|
52
64
|
// The top level's headings (bold): at the top, or right under a tab (a tab's own top level).
|
|
53
65
|
if (item.kind === "heading" && (parent < 0 || rows[parent]?.node.kind === "tab")) row.dataset.top = "";
|
|
54
66
|
if (item.children.length) row.setAttribute("aria-expanded", String(open));
|
|
55
|
-
tooltip(row, () => (cut(named) ? item.text : null), null, { placement: "left", align: "center", gap:
|
|
67
|
+
tooltip(row, () => (cut(named) ? item.text : null), null, { placement: "left", align: "center", gap: LIST.row + 8 });
|
|
56
68
|
return row;
|
|
57
69
|
}
|
|
58
70
|
|
|
@@ -10,7 +10,7 @@ import { SHEETS } from "../../components/kit.mjs";
|
|
|
10
10
|
// ones too, each with its panel's headings. A heading with no text (just made) has no row yet.
|
|
11
11
|
|
|
12
12
|
export const HEADING = ':is(h1, h2, h3, h4)[data-ge-unit-kind="heading"]';
|
|
13
|
-
const LEFT_OUT = "dialog, figure, table, li, .doc-header, template, [data-doc-dev]";
|
|
13
|
+
export const LEFT_OUT = "dialog, figure, table, li, .doc-header, template, [data-doc-dev]";
|
|
14
14
|
const SCOPES = ".doc-columns, details";
|
|
15
15
|
|
|
16
16
|
// ---------- a formula as a row reads it ----------
|
|
@@ -21,6 +21,9 @@ import { ticksOf } from "./tree.mjs";
|
|
|
21
21
|
// or a heading chosen by a key closes it too). Esc on the strip itself leaves it as Esc in the list
|
|
22
22
|
// does (escape). Folding in the card keeps it open; it follows the page's scroll (its current row)
|
|
23
23
|
// while open, unless the pointer is on it. With no headings the strip shows the outline's icon.
|
|
24
|
+
//
|
|
25
|
+
// The card's guide (its list's hairline) has a grip of its own (`guide`, a few px either side of it):
|
|
26
|
+
// index.mjs makes a click on it take the reader's collapse back (the list in full where the room allows it).
|
|
24
27
|
|
|
25
28
|
// Three lines, the lower two set in: the 目录 icon (the workbench's 更多 row draws the same, icons.ts outline).
|
|
26
29
|
export const OUTLINE_ICON = '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3.75 5.25h12.5M7.25 10h9M7.25 14.75h9"/></svg>';
|
|
@@ -39,7 +42,9 @@ const still = () => globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").
|
|
|
39
42
|
export function createStrip({ list, opened, closed, escape, choose }) {
|
|
40
43
|
const button = element("div", { class: "ge-outline-ticks", role: "button", tabindex: "0", "aria-label": t("目录"), "aria-expanded": "false" });
|
|
41
44
|
const strip = element("div", { class: "ge-outline-strip", "data-doc-dev": "", hidden: "" }, button);
|
|
42
|
-
|
|
45
|
+
// Its guide's grip in its scroller, before the list, placed by the card (as the dock's: index.mjs).
|
|
46
|
+
const guide = element("div", { class: "ge-outline-card-grip", "aria-hidden": "true" });
|
|
47
|
+
const scroll = element("div", { class: "ge-outline-scroll" }, guide);
|
|
43
48
|
const card = element("nav", { class: "ge-outline-card", "aria-label": t("目录"), "data-doc-dev": "", hidden: "" }, scroll);
|
|
44
49
|
let open = false;
|
|
45
50
|
let keyed = false; // opened by a key (the focus in it), not by the pointer
|
|
@@ -246,6 +251,7 @@ export function createStrip({ list, opened, closed, escape, choose }) {
|
|
|
246
251
|
strip,
|
|
247
252
|
card,
|
|
248
253
|
scroll,
|
|
254
|
+
guide,
|
|
249
255
|
draw,
|
|
250
256
|
openCard,
|
|
251
257
|
closeCard,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Z } from "../../shared/layers.mjs";
|
|
2
2
|
|
|
3
|
-
// The editor's
|
|
4
|
-
// right) are the same card
|
|
3
|
+
// The editor's bottom panels: the conflicts' (conflicts.mjs, left) and the lost texts' (notices.mjs,
|
|
4
|
+
// right) are the same card, and the bar of the Agent's changes (changes/bar.mjs, in the middle) is its
|
|
5
|
+
// buttons in a row: a small muted head with a red dot, items under it, the reader's words as
|
|
5
6
|
// a quote, one filled primary button, the rest outlined or, looking only, quiet text. No notice covers
|
|
6
7
|
// one: while one stands, the toast stands above it (notices.mjs, and the workbench's, told the room).
|
|
7
8
|
|
|
8
|
-
const PANELS = ".ge-conflicts:not([hidden]), .ge-panel";
|
|
9
|
+
const PANELS = ".ge-conflicts:not([hidden]), .ge-panel, .ge-changes:not([hidden])";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* How far from the window's bottom the highest bottom panel shown reaches, 0 with none shown: what a
|
|
@@ -41,7 +42,7 @@ export function panelGone(panel) {
|
|
|
41
42
|
told();
|
|
42
43
|
}
|
|
43
44
|
export const PANEL_STYLE = `
|
|
44
|
-
:is(.ge-conflicts, .ge-panel) { position: fixed; bottom: 16px; z-index: ${Z.panel}; box-sizing: border-box; width: min(400px, calc(100vw - 32px)); max-height: min(70vh, 600px); overflow: auto; border-radius: 10px; background: var(--ge-bg); color: var(--ge-tx); box-shadow: var(--ge-shadow); font: 13px/1.55 var(--doc-font, system-ui, sans-serif); }
|
|
45
|
+
:is(.ge-conflicts, .ge-panel) { position: fixed; bottom: 16px; z-index: ${Z.panel}; box-sizing: border-box; width: min(400px, calc(100vw - 32px)); max-height: min(70vh, 600px); overflow: auto; border-radius: 10px; background: var(--ge-bg); color: var(--ge-tx); box-shadow: var(--ge-shadow); font: 13px/1.55 var(--doc-sans-font, var(--doc-font, system-ui, sans-serif)); }
|
|
45
46
|
.ge-conflicts { left: 16px; }
|
|
46
47
|
.ge-panel { right: 16px; }
|
|
47
48
|
.ge-conflicts[hidden] { display: none; }
|
|
@@ -30,7 +30,7 @@ const STYLE = `
|
|
|
30
30
|
${gestureLock("data-ge-resize=\"x\"", { cursor: "ew-resize" })}
|
|
31
31
|
${gestureLock("data-ge-resize=\"y\"", { cursor: "ns-resize" })}
|
|
32
32
|
.ge-resize-line { position: fixed; left: 0; top: 0; z-index: ${Z.guide}; border-radius: 2px; background: rgb(151 197 241); pointer-events: none; }
|
|
33
|
-
.ge-resize-label { position: fixed; left: 0; top: 0; z-index: ${Z.label}; padding: 2px 7px; border-radius: 5px; background: var(--doc-accent, #2678d8); color: #fff; font: 12px/18px var(--doc-font, system-ui, sans-serif); white-space: nowrap; pointer-events: none; }
|
|
33
|
+
.ge-resize-label { position: fixed; left: 0; top: 0; z-index: ${Z.label}; padding: 2px 7px; border-radius: 5px; background: var(--doc-accent, #2678d8); color: #fff; font: 12px/18px var(--doc-sans-font, var(--doc-font, system-ui, sans-serif)); white-space: nowrap; pointer-events: none; }
|
|
34
34
|
`;
|
|
35
35
|
const GRAB = { inside: 3, outside: 8 }; // how near an edge the pointer takes it, inside and outside the block (px)
|
|
36
36
|
const SNAP = 14; // px
|
|
@@ -10,19 +10,21 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
10
10
|
// tab is clicked (undo clicks it back). One undo step, which selects the blocks again. A box that
|
|
11
11
|
// always holds a block gets an empty paragraph once they are gone (core.mjs tidyEmptied), the caret
|
|
12
12
|
// in it. That one empty paragraph stays (keptAnyway: it would be put back as it is), so deleting
|
|
13
|
-
// only it does nothing and says nothing.
|
|
13
|
+
// only it does nothing and says nothing. A folded heading goes with what it hides, or as the section
|
|
14
|
+
// it covers whole (fold/ takenWith).
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* info: block-info.mjs. selection: selection/index.mjs. struct(parents, touched, mutate): the
|
|
17
18
|
* editor's undoable structural change (history.mjs). say: the status line. closeMenu(): the block
|
|
18
|
-
* menu closes. forget(): the handle forgets the block it stood beside.
|
|
19
|
+
* menu closes. forget(): the handle forgets the block it stood beside. folds: the headings' folds (fold/).
|
|
19
20
|
*/
|
|
20
|
-
export function createDelete({ info, selection, struct, say, closeMenu, forget }) {
|
|
21
|
-
const { kindOf, nameOf, keepText } = info;
|
|
21
|
+
export function createDelete({ info, selection, struct, say, closeMenu, forget, folds = null }) {
|
|
22
|
+
const { kindOf, nameOf, keepText, named } = info;
|
|
22
23
|
|
|
23
24
|
return function remove(targets = selection.picked) {
|
|
24
25
|
closeMenu();
|
|
25
|
-
const
|
|
26
|
+
const picked = ordered([targets].flat().filter(alive));
|
|
27
|
+
const list = (folds ? folds.takenWith(picked) : picked).filter((target) => !keptAnyway(target));
|
|
26
28
|
if (list.length === 0) return;
|
|
27
29
|
const refused = list.map((target) => [target, keepText(target, list)]).find(([, reason]) => reason);
|
|
28
30
|
if (refused) {
|
|
@@ -53,7 +55,7 @@ export function createDelete({ info, selection, struct, say, closeMenu, forget }
|
|
|
53
55
|
});
|
|
54
56
|
if (done?.caret) caretAt(done.caret, "start");
|
|
55
57
|
const emptied = nodes.some((node) => !list.includes(node));
|
|
56
|
-
const name = list.length === 1 ? nameOf(nodes[0]) : t("{0} 块", list.length);
|
|
58
|
+
const name = list.length === 1 ? nameOf(nodes[0]) : folds ? folds.namedWith(picked, list, named) : t("{0} 块", list.length);
|
|
57
59
|
say(t("已删除{0}{1}{2} · Ctrl+Z 撤销", name, emptied ? t("(外层已空,一并删除)") : "", tabs.length > 0 ? t(",连同其中的内容") : ""));
|
|
58
60
|
};
|
|
59
61
|
}
|