@heycys/gallery 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/browser/editor/components/align.mjs +58 -0
- package/browser/editor/components/code/index.mjs +1 -1
- package/browser/editor/components/embed/page.mjs +1 -1
- package/browser/editor/components/file/page.mjs +4 -4
- package/browser/editor/components/heading/index.mjs +7 -6
- package/browser/editor/components/icons.mjs +3 -0
- package/browser/editor/components/image/index.mjs +2 -0
- package/browser/editor/components/image/page.mjs +172 -90
- package/browser/editor/components/installed/index.mjs +6 -2
- package/browser/editor/components/installed/page.mjs +80 -13
- package/browser/editor/components/media/icons.mjs +13 -8
- package/browser/editor/components/media/page.mjs +5 -3
- package/browser/editor/components/media/toolbar.mjs +12 -3
- package/browser/editor/components/paragraph/index.mjs +4 -3
- package/browser/editor/components/registry.mjs +17 -5
- package/browser/editor/components/table/index.mjs +4 -2
- package/browser/editor/page/after/index.mjs +288 -0
- package/browser/editor/page/align.mjs +91 -0
- package/browser/editor/page/beside.mjs +5 -6
- package/browser/editor/page/block-menu/index.mjs +6 -3
- package/browser/editor/page/blocks.mjs +3 -1
- package/browser/editor/page/board/hint.mjs +64 -27
- package/browser/editor/page/board/index.mjs +160 -71
- package/browser/editor/page/board/motion.mjs +177 -0
- package/browser/editor/page/board/settings.mjs +4 -3
- package/browser/editor/page/clipboard/index.mjs +10 -3
- package/browser/editor/page/clipboard/own/blocks.mjs +3 -4
- package/browser/editor/page/clipboard/own/describe.mjs +6 -2
- package/browser/editor/page/commit.mjs +2 -2
- package/browser/editor/page/core.mjs +3 -1
- package/browser/editor/page/custom-page.mjs +15 -11
- package/browser/editor/page/editor.mjs +31 -14
- package/browser/editor/page/history.mjs +19 -4
- package/browser/editor/page/insert/index.mjs +20 -22
- package/browser/editor/page/menu.mjs +15 -7
- package/browser/editor/page/page-objects.mjs +3 -7
- package/browser/editor/page/present-links.mjs +5 -4
- package/browser/editor/page/present-shell.mjs +63 -0
- package/browser/editor/page/selection/index.mjs +10 -2
- package/browser/editor/page/table/binding.mjs +87 -0
- package/browser/editor/page/table/icons.mjs +0 -2
- package/browser/editor/page/table/index.mjs +10 -14
- package/browser/editor/page/table/views.mjs +5 -52
- package/browser/editor/page/table/widths.mjs +82 -10
- package/browser/editor/page/typing/index.mjs +44 -12
- package/browser/editor/page/typing/leave.mjs +19 -5
- package/browser/shared/block-packs.mjs +23 -8
- package/browser/shared/component-catalog.mjs +15 -8
- package/browser/shared/component-source.mjs +3 -1
- package/browser/shared/components/heading.mjs +2 -1
- package/browser/shared/components/installed.mjs +1 -1
- package/browser/shared/components/page.mjs +2 -0
- package/browser/shared/components/paragraph.mjs +8 -1
- package/browser/shared/components/table.mjs +1 -0
- package/browser/shared/emoji/line-icons.mjs +37 -0
- package/browser/shared/http.mjs +9 -0
- package/browser/shared/i18n/en.mjs +40 -17
- package/browser/shared/outline.mjs +30 -2
- package/browser/shared/pack-manifest.mjs +12 -1
- package/browser/shared/pages.mjs +169 -57
- package/browser/shared/pinyin.mjs +92 -0
- package/browser/shared/present-step.mjs +17 -0
- package/browser/shared/sidebar-head.mjs +70 -0
- package/browser/shared/web-links.mjs +5 -0
- package/dist/chunk-2OHANJ6Q.mjs +2 -0
- package/dist/{chunk-QOUEFUCH.mjs → chunk-3MBRL2PM.mjs} +1 -1
- package/dist/{chunk-NWR2LT5B.mjs → chunk-4OKWZY5E.mjs} +1 -1
- package/dist/{chunk-RMECZA2K.mjs → chunk-5K44HLTZ.mjs} +1 -1
- package/dist/{chunk-NFMCP2HI.mjs → chunk-5XA2ZBAE.mjs} +2 -2
- package/dist/{chunk-OUUP3WKT.mjs → chunk-67N2QJYT.mjs} +1 -1
- package/dist/chunk-6KBYRA2C.mjs +2 -0
- package/dist/{chunk-SX4LJYT6.mjs → chunk-6N3PX4XX.mjs} +1 -1
- package/dist/{chunk-RFDZXFJF.mjs → chunk-7JDX4E65.mjs} +1 -1
- package/dist/chunk-7M2BQQGZ.mjs +2 -0
- package/dist/{chunk-ZUNVAXLH.mjs → chunk-7OG26PGF.mjs} +1 -1
- package/dist/{chunk-B4P2CM2J.mjs → chunk-AHN3NFKJ.mjs} +1 -1
- package/dist/chunk-ALZ6ZENZ.mjs +3 -0
- package/dist/{chunk-UGWYKHBL.mjs → chunk-AR5D57ZD.mjs} +2 -2
- package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
- package/dist/{chunk-HLYV7RIH.mjs → chunk-CMA72MAG.mjs} +1 -1
- package/dist/{chunk-D4CKRD3V.mjs → chunk-DAZL73VK.mjs} +2 -2
- package/dist/{chunk-6VTU6KEO.mjs → chunk-DJISHPUL.mjs} +1 -1
- package/dist/{chunk-BPL64HGA.mjs → chunk-EAIFITAV.mjs} +1 -1
- package/dist/chunk-EP25HDAJ.mjs +2 -0
- package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
- package/dist/{chunk-3KJHLHUO.mjs → chunk-F6HVDZR4.mjs} +1 -1
- package/dist/{chunk-3NJACR4W.mjs → chunk-FDMAXMQA.mjs} +1 -1
- package/dist/chunk-FETSREUN.mjs +5 -0
- package/dist/{chunk-RKPWLGZ3.mjs → chunk-GHOYKMQ3.mjs} +1 -1
- package/dist/chunk-HDWTZ763.mjs +12 -0
- package/dist/{chunk-VTKNEYT3.mjs → chunk-HPOI4ZX7.mjs} +1 -1
- package/dist/{chunk-OW5NWPOJ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
- package/dist/{chunk-V6RIYQZZ.mjs → chunk-ITOH5EGO.mjs} +1 -1
- package/dist/{chunk-ZWPMCK7P.mjs → chunk-IV64FFI4.mjs} +1 -1
- package/dist/chunk-J43Z6LSS.mjs +2 -0
- package/dist/chunk-J5PUIV3Y.mjs +4 -0
- package/dist/{chunk-KTKHRC6H.mjs → chunk-JA7G53PC.mjs} +1 -1
- package/dist/{chunk-NWFBE2YA.mjs → chunk-JLTZF4LA.mjs} +1 -1
- package/dist/{chunk-GY3FAUVM.mjs → chunk-JZT7I2OJ.mjs} +1 -1
- package/dist/{chunk-QKXMPUXC.mjs → chunk-K5XATEOO.mjs} +1 -1
- package/dist/chunk-L2SUIS5S.mjs +2 -0
- package/dist/{chunk-TEZAWDSQ.mjs → chunk-LM4XFQHZ.mjs} +1 -1
- package/dist/{chunk-5ZJA7CXT.mjs → chunk-MN7BZL4R.mjs} +1 -1
- package/dist/chunk-MTYYHM2G.mjs +15 -0
- package/dist/{chunk-HMYTM3GT.mjs → chunk-MXUBKDRO.mjs} +1 -1
- package/dist/{chunk-MHW7ZKOU.mjs → chunk-OEXWRJME.mjs} +1 -1
- package/dist/{chunk-CRJEG7MT.mjs → chunk-OFHXGWOX.mjs} +1 -1
- package/dist/chunk-ORID3AE7.mjs +166 -0
- package/dist/{chunk-32ZUEFZ7.mjs → chunk-PGDE24N5.mjs} +1 -1
- package/dist/{chunk-UNOYVIYR.mjs → chunk-POTA2FV4.mjs} +1 -1
- package/dist/{chunk-N7AZZLGT.mjs → chunk-QE5RG76S.mjs} +18 -17
- package/dist/{chunk-N76HWBA3.mjs → chunk-R2ZDLJYT.mjs} +1 -1
- package/dist/chunk-SWUTTZCN.mjs +2 -0
- package/dist/{chunk-POQSE4C2.mjs → chunk-TR7TWGHB.mjs} +1 -1
- package/dist/chunk-U5XL7D6A.mjs +3 -0
- package/dist/{chunk-PVER7LSC.mjs → chunk-U7NZI6N4.mjs} +2 -2
- package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
- package/dist/chunk-UJQJKNGK.mjs +2 -0
- package/dist/chunk-UYVCQK3H.mjs +22 -0
- package/dist/{chunk-FJYN7RTH.mjs → chunk-VFX2KNHP.mjs} +1 -1
- package/dist/{chunk-UBBRTJ2R.mjs → chunk-VS7JIGJW.mjs} +1 -1
- package/dist/{chunk-NVRNLK25.mjs → chunk-W7665ZEC.mjs} +2 -2
- package/dist/{chunk-NG375ILQ.mjs → chunk-WQDQWCX5.mjs} +1 -1
- package/dist/chunk-YLPSMDIS.mjs +5 -0
- package/dist/{chunk-VXACTLO3.mjs → chunk-YNAWBAYN.mjs} +1 -1
- package/dist/{chunk-ZB4H7ZN4.mjs → chunk-YROX2O22.mjs} +1 -1
- package/dist/{chunk-YQGT4AB2.mjs → chunk-ZDLPUOAG.mjs} +1 -1
- package/dist/chunk-ZQS6H473.mjs +16 -0
- package/dist/{chunk-OJKMINIU.mjs → chunk-ZWWC4KLF.mjs} +15 -15
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +5 -5
- package/kit/code.js +15 -15
- package/kit/report.css +120 -41
- package/kit/runtime.js +45 -7
- package/kit/tables.js +2 -0
- package/kit/ui.js +2 -0
- package/package.json +1 -1
- package/packs/gallery/chart-view/kit.js +3 -3
- package/packs/gallery/chart-view/writing.md +1 -1
- package/packs/gallery/countdown/component.json +1 -1
- package/packs/gallery/countdown/kit.js +89 -29
- package/packs/gallery/countdown/preview.svg +1 -1
- package/packs/gallery/countdown/style.css +88 -100
- package/packs/gallery/countdown/writing.md +2 -2
- package/packs/gallery/habit/component.json +3 -3
- package/packs/gallery/habit/kit.js +114 -75
- package/packs/gallery/habit/preview.svg +1 -1
- package/packs/gallery/habit/style.css +133 -205
- package/packs/gallery/habit/writing.md +2 -2
- package/packs/gallery/heatmap/component.json +2 -2
- package/packs/gallery/heatmap/kit.js +335 -173
- package/packs/gallery/heatmap/preview.svg +1 -1
- package/packs/gallery/heatmap/style.css +158 -170
- package/packs/gallery/heatmap/writing.md +2 -2
- package/packs/gallery/kanban/kit.js +3 -3
- package/packs/gallery/kanban/writing.md +1 -1
- package/packs/gallery/metric/kit.js +3 -3
- package/packs/gallery/metric/writing.md +1 -1
- package/packs/gallery/note/component.json +3 -3
- package/packs/gallery/note/kit.js +40 -13
- package/packs/gallery/note/preview.svg +1 -1
- package/packs/gallery/note/style.css +51 -30
- package/packs/gallery/note/writing.md +2 -2
- package/packs/gallery/recent/component.json +2 -2
- package/packs/gallery/recent/kit.js +141 -86
- package/packs/gallery/recent/preview.svg +1 -1
- package/packs/gallery/recent/style.css +53 -126
- package/packs/gallery/recent/writing.md +2 -2
- package/packs/gallery/static/component.json +2 -2
- package/packs/gallery/static/preview.svg +1 -1
- package/packs/gallery/static/style.css +99 -66
- package/packs/gallery/static/writing.md +2 -2
- package/packs/gallery/stats/component.json +3 -3
- package/packs/gallery/stats/kit.js +253 -72
- package/packs/gallery/stats/preview.svg +1 -1
- package/packs/gallery/stats/style.css +64 -142
- package/packs/gallery/stats/writing.md +2 -2
- package/packs/gallery/today/component.json +2 -2
- package/packs/gallery/today/kit.js +62 -27
- package/packs/gallery/today/preview.svg +1 -1
- package/packs/gallery/today/style.css +101 -72
- package/packs/gallery/today/writing.md +2 -2
- package/packs/gallery/todos/component.json +1 -1
- package/packs/gallery/todos/kit.js +218 -71
- package/packs/gallery/todos/preview.svg +1 -1
- package/packs/gallery/todos/style.css +170 -223
- package/packs/gallery/todos/writing.md +1 -1
- package/packs/gallery/weather/component.json +2 -2
- package/packs/gallery/weather/kit.js +165 -90
- package/packs/gallery/weather/preview.svg +1 -1
- package/packs/gallery/weather/style.css +100 -218
- package/packs/gallery/weather/writing.md +1 -1
- package/packs/gallery-soft/countdown/component.json +18 -0
- package/packs/gallery-soft/countdown/example.html +3 -0
- package/packs/gallery-soft/countdown/kit.js +206 -0
- package/packs/gallery-soft/countdown/preview.svg +1 -0
- package/packs/gallery-soft/countdown/style.css +211 -0
- package/packs/gallery-soft/countdown/template.html +3 -0
- package/packs/gallery-soft/countdown/writing.md +24 -0
- package/packs/gallery-soft/habit/component.json +14 -0
- package/packs/gallery-soft/habit/example.html +3 -0
- package/packs/gallery-soft/habit/kit.js +366 -0
- package/packs/gallery-soft/habit/preview.svg +1 -0
- package/packs/gallery-soft/habit/style.css +251 -0
- package/packs/gallery-soft/habit/template.html +3 -0
- package/packs/gallery-soft/habit/writing.md +23 -0
- package/packs/gallery-soft/heatmap/component.json +18 -0
- package/packs/gallery-soft/heatmap/example.html +3 -0
- package/packs/gallery-soft/heatmap/kit.js +498 -0
- package/packs/gallery-soft/heatmap/preview.svg +1 -0
- package/packs/gallery-soft/heatmap/style.css +181 -0
- package/packs/gallery-soft/heatmap/template.html +3 -0
- package/packs/gallery-soft/heatmap/writing.md +23 -0
- package/packs/gallery-soft/note/component.json +14 -0
- package/packs/gallery-soft/note/example.html +3 -0
- package/packs/gallery-soft/note/kit.js +162 -0
- package/packs/gallery-soft/note/preview.svg +1 -0
- package/packs/gallery-soft/note/style.css +148 -0
- package/packs/gallery-soft/note/template.html +3 -0
- package/packs/gallery-soft/note/writing.md +20 -0
- package/packs/gallery-soft/recent/component.json +13 -0
- package/packs/gallery-soft/recent/example.html +1 -0
- package/packs/gallery-soft/recent/kit.js +335 -0
- package/packs/gallery-soft/recent/preview.svg +1 -0
- package/packs/gallery-soft/recent/style.css +160 -0
- package/packs/gallery-soft/recent/template.html +1 -0
- package/packs/gallery-soft/recent/writing.md +17 -0
- package/packs/gallery-soft/static/component.json +13 -0
- package/packs/gallery-soft/static/example.html +12 -0
- package/packs/gallery-soft/static/preview.svg +1 -0
- package/packs/gallery-soft/static/style.css +186 -0
- package/packs/gallery-soft/static/template.html +1 -0
- package/packs/gallery-soft/static/writing.md +20 -0
- package/packs/gallery-soft/stats/component.json +13 -0
- package/packs/gallery-soft/stats/example.html +1 -0
- package/packs/gallery-soft/stats/kit.js +341 -0
- package/packs/gallery-soft/stats/preview.svg +1 -0
- package/packs/gallery-soft/stats/style.css +119 -0
- package/packs/gallery-soft/stats/template.html +1 -0
- package/packs/gallery-soft/stats/writing.md +13 -0
- package/packs/gallery-soft/today/component.json +12 -0
- package/packs/gallery-soft/today/example.html +1 -0
- package/packs/gallery-soft/today/kit.js +222 -0
- package/packs/gallery-soft/today/preview.svg +1 -0
- package/packs/gallery-soft/today/style.css +178 -0
- package/packs/gallery-soft/today/template.html +1 -0
- package/packs/gallery-soft/today/writing.md +16 -0
- package/packs/gallery-soft/todos/component.json +19 -0
- package/packs/gallery-soft/todos/example.html +3 -0
- package/packs/gallery-soft/todos/kit.js +527 -0
- package/packs/gallery-soft/todos/preview.svg +1 -0
- package/packs/gallery-soft/todos/style.css +212 -0
- package/packs/gallery-soft/todos/template.html +3 -0
- package/packs/gallery-soft/todos/writing.md +32 -0
- package/packs/gallery-soft/weather/component.json +19 -0
- package/packs/gallery-soft/weather/example.html +3 -0
- package/packs/gallery-soft/weather/kit.js +411 -0
- package/packs/gallery-soft/weather/preview.svg +1 -0
- package/packs/gallery-soft/weather/style.css +185 -0
- package/packs/gallery-soft/weather/template.html +3 -0
- package/packs/gallery-soft/weather/writing.md +28 -0
- package/release.json +9 -9
- package/skill/SKILL.md +12 -12
- package/skill/references/building-blocks.md +4 -4
- package/skill/references/pages-and-data.md +13 -9
- package/skill/references/writing-html.md +2 -0
- package/web/assets/index-BM_K-Auu.css +1 -0
- package/web/assets/index-DRslOBiv.js +15 -0
- package/web/index.html +2 -2
- package/dist/chunk-2NRBY6AJ.mjs +0 -2
- package/dist/chunk-35NQ4PLQ.mjs +0 -2
- package/dist/chunk-4AV456RQ.mjs +0 -2
- package/dist/chunk-5XYHDBJ5.mjs +0 -2
- package/dist/chunk-6A53MZYZ.mjs +0 -2
- package/dist/chunk-6EXH3ORZ.mjs +0 -166
- package/dist/chunk-BVEFGE4B.mjs +0 -2
- package/dist/chunk-D4EURAX4.mjs +0 -4
- package/dist/chunk-DQCE7TJY.mjs +0 -5
- package/dist/chunk-FWNRPMCJ.mjs +0 -12
- package/dist/chunk-FWW4CSAZ.mjs +0 -2
- package/dist/chunk-JVHHHGEA.mjs +0 -3
- package/dist/chunk-PZDLJJTJ.mjs +0 -3
- package/dist/chunk-SXHBYZST.mjs +0 -16
- package/dist/chunk-UFNYFFKO.mjs +0 -20
- package/dist/chunk-UKSHDZZ6.mjs +0 -2
- package/dist/chunk-VJPFVNNW.mjs +0 -15
- package/dist/chunk-VS3ZMVGX.mjs +0 -5
- package/web/assets/index-BK2e_07w.js +0 -13
- package/web/assets/index-CWFI83mF.css +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { pageAddress } from "../../shared/page-address.mjs";
|
|
2
|
+
import { KEEPS_KEYS, stepSection } from "../../shared/present-step.mjs";
|
|
3
|
+
import { PROTOCOL, PROTOCOLS, VERSION } from "../../shared/session.mjs";
|
|
4
|
+
|
|
5
|
+
// What a page shown in 演示视图 (?present: src/server/routes.mjs, no editor) says to the presentation around it and
|
|
6
|
+
// hears from it. The presentation of a document of this workspace is a frame of the same origin, which it reaches into (its
|
|
7
|
+
// scroll, its keys, its theme); that of a draft of another workspace (docs/workbench.md "Drafts of other workspaces") is a frame
|
|
8
|
+
// of that workspace's server, named by the page's address (?shell=<the workbench's origin>) as the editor's page names it
|
|
9
|
+
// (session.mjs): this page then asks its own server whether that is a Gallery server of this computer (GET
|
|
10
|
+
// /__gallery/workspace/shell) and talks to it alone once it is, holding what it says until then; one that is not hears nothing.
|
|
11
|
+
// It tells where the page is scrolled (`present-view`: { x, y }, with `ready` once the page can be spoken to) and the keys pressed
|
|
12
|
+
// in it that are the presentation's (`present-key`: { key, ctrlKey, metaKey, altKey, kept (a control holds the key), prevented }),
|
|
13
|
+
// and does what it is asked: `theme` (the workbench's, light or dark), `go-section` (上一节 / 下一节) and `scroll` ({ left, top }: the wheel). Every message is the session
|
|
14
|
+
// protocol's envelope (src/shared/session.mjs).
|
|
15
|
+
|
|
16
|
+
/** The origin of a workbench of another origin showing this page (?shell=<origin>), or null: this one's. */
|
|
17
|
+
export const SHELL = (() => {
|
|
18
|
+
const named = new URLSearchParams(location.search).get("shell");
|
|
19
|
+
return named && named !== location.origin ? named : null;
|
|
20
|
+
})();
|
|
21
|
+
|
|
22
|
+
const vouched = (origin) => fetch(`/__gallery/workspace/shell?origin=${encodeURIComponent(origin)}`, { cache: "no-store" })
|
|
23
|
+
.then((answer) => answer.json()).then((answer) => answer?.shell === true, () => false);
|
|
24
|
+
|
|
25
|
+
let parentOrigin = SHELL ? null : location.origin;
|
|
26
|
+
let held = SHELL ? [] : null;
|
|
27
|
+
|
|
28
|
+
/** Tell the presentation around the page (`window.parent`) something; held until its origin is vouched for, dropped when it is not. */
|
|
29
|
+
export function tell(type, fields = {}) {
|
|
30
|
+
if (window.parent === window) return;
|
|
31
|
+
if (held) return void held.push([type, fields]);
|
|
32
|
+
if (!parentOrigin) return;
|
|
33
|
+
window.parent.postMessage({ protocol: PROTOCOL, v: VERSION, type, slug: pageAddress(location.href)?.slug ?? "", ...fields }, parentOrigin);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function bridge() {
|
|
37
|
+
let frame = 0;
|
|
38
|
+
const view = (ready = false) => tell("present-view", { x: scrollX, y: scrollY, ...(ready ? { ready: true } : {}) });
|
|
39
|
+
addEventListener("scroll", () => { frame ||= requestAnimationFrame(() => { frame = 0; view(); }); }, { passive: true });
|
|
40
|
+
addEventListener("keydown", (event) => {
|
|
41
|
+
if (event.isComposing) return;
|
|
42
|
+
tell("present-key", { key: event.key, ctrlKey: event.ctrlKey, metaKey: event.metaKey, altKey: event.altKey, kept: Boolean(event.target?.closest?.(KEEPS_KEYS)), prevented: event.defaultPrevented });
|
|
43
|
+
});
|
|
44
|
+
addEventListener("message", (event) => {
|
|
45
|
+
const message = event.data;
|
|
46
|
+
if (event.source !== window.parent || event.origin !== parentOrigin || !PROTOCOLS.includes(message?.protocol) || message.v !== VERSION || message.type !== "command") return;
|
|
47
|
+
if (message.name === "theme") dispatchEvent(new CustomEvent("doc-theme-request", { detail: { theme: message.theme === "dark" ? "dark" : "light" } })); // the kit's ui.js
|
|
48
|
+
else if (message.name === "go-section") stepSection(window, message.direction > 0 ? 1 : -1);
|
|
49
|
+
else if (message.name === "scroll") scrollBy({ left: Number(message.left) || 0, top: Number(message.top) || 0 }); // the wheel over the presentation's ink
|
|
50
|
+
});
|
|
51
|
+
view(true);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (SHELL && window.parent !== window) {
|
|
55
|
+
vouched(SHELL).then((ok) => {
|
|
56
|
+
const waiting = held;
|
|
57
|
+
held = null;
|
|
58
|
+
if (!ok) return;
|
|
59
|
+
parentOrigin = SHELL;
|
|
60
|
+
for (const [type, fields] of waiting) tell(type, fields);
|
|
61
|
+
bridge();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -5,6 +5,7 @@ import { createFills } from "../fills.mjs";
|
|
|
5
5
|
import { FOLDED } from "../folded.mjs";
|
|
6
6
|
import { describe, note, report } from "../overlays.mjs";
|
|
7
7
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
8
|
+
import { UNFILLED_ALONE } from "../../components/registry.mjs";
|
|
8
9
|
|
|
9
10
|
// Selected blocks (docs/editor.md, "Selecting blocks"): a set of blocks, none inside another, in
|
|
10
11
|
// document order. A box selection drawn from empty space gives blocks of one container
|
|
@@ -109,10 +110,17 @@ export function createSelection({ info, active, say, closeMenu, schedule }) {
|
|
|
109
110
|
// The fills made to match the selection now: those of blocks no longer selected (or no longer on
|
|
110
111
|
// the page) go at once; each selected block shown gets its fill where it is, reaching past it as
|
|
111
112
|
// reachOf says, never into a neighbour's (roomOf). One that cannot be drawn takes all the fills
|
|
112
|
-
// down (said once): better none than one in the wrong place.
|
|
113
|
+
// down (said once): better none than one in the wrong place. A kind whose own tools show it selected
|
|
114
|
+
// (UNFILLED_ALONE: the image with its picture, its corner grips and toolbar) draws no fill selected alone (the user, 2026-11-09: no pale
|
|
115
|
+
// blue over a picture clicked); among other blocks it is filled as they are.
|
|
116
|
+
const toolsShowIt = () => {
|
|
117
|
+
if (picked.length !== 1 || marked.length !== 0) return false;
|
|
118
|
+
const holds = UNFILLED_ALONE[picked[0].getAttribute?.("data-ge-unit-kind")];
|
|
119
|
+
return holds !== undefined && picked[0].querySelector?.(holds) !== null;
|
|
120
|
+
};
|
|
113
121
|
function render() {
|
|
114
122
|
try {
|
|
115
|
-
halos.paint(hidden || !active() ? [] : [...picked, ...marked].filter(alive));
|
|
123
|
+
halos.paint(hidden || !active() || toolsShowIt() ? [] : [...picked, ...marked].filter(alive));
|
|
116
124
|
} catch (error) {
|
|
117
125
|
unpaint();
|
|
118
126
|
report("selection fills", error);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TABLE } from "../../components/kit.mjs";
|
|
2
|
+
import { CHART_SOURCE, TABLE_READERS, VIEW_FROM, nextChartKey } from "../../../shared/table-chart.mjs";
|
|
3
|
+
import { t } from "../../../shared/i18n/index.mjs";
|
|
4
|
+
import { menuRow } from "../page-objects.mjs";
|
|
5
|
+
|
|
6
|
+
// Which table a view reads (docs/editor.md, "Table views"). A view of a table (an instance of a custom
|
|
7
|
+
// block whose needs hold "tables") names its table by a key: VIEW_FROM on the instance, CHART_SOURCE on
|
|
8
|
+
// the table's frame, never by an id or the blocks' order. A view made on the page ("/", 在下方新建, 转换成)
|
|
9
|
+
// is bound to the nearest table above it as it is made, in the same undo step; the table it reads is the
|
|
10
|
+
// reader's to change after that, in the block's 设置 (installed/page.mjs), which lists the document's tables.
|
|
11
|
+
// A table with no key gets one in the same step (the first of t1, t2 … the page does not hold), and so
|
|
12
|
+
// does the second of two an Agent wrote with one key: a view by that key would read the first.
|
|
13
|
+
|
|
14
|
+
const FRAME_WITH_TABLE = (frame) => frame.querySelector("table") !== null && !frame.closest("[data-doc-dev]");
|
|
15
|
+
|
|
16
|
+
/** The tables of the page, in the document's order: their frames (.doc-table-wrap). */
|
|
17
|
+
export const tableFrames = () => [...document.querySelectorAll(TABLE.frame)].filter(FRAME_WITH_TABLE);
|
|
18
|
+
|
|
19
|
+
/** The frame a key names, as the kit reads it (runtime/table-reading.js frameOf) and the checks do: the first that carries it. */
|
|
20
|
+
export const frameOf = (key) => tableFrames().find((frame) => frame.getAttribute(CHART_SOURCE) === key) ?? null;
|
|
21
|
+
|
|
22
|
+
/** The nearest table above `node`, in the document's order, that does not hold it; null where there is none. */
|
|
23
|
+
export function tableAbove(node) {
|
|
24
|
+
let found = null;
|
|
25
|
+
for (const frame of tableFrames()) {
|
|
26
|
+
if (frame.contains(node)) continue;
|
|
27
|
+
if (frame.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING) found = frame;
|
|
28
|
+
}
|
|
29
|
+
return found;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Every key a chart or a view names, or a frame carries.
|
|
33
|
+
const takenKeys = () => new Set([...document.querySelectorAll([CHART_SOURCE, ...TABLE_READERS].map((name) => `[${name}]`).join(", "))]
|
|
34
|
+
.flatMap((node) => [CHART_SOURCE, ...TABLE_READERS].map((name) => node.getAttribute(name)).filter((value) => value !== null)));
|
|
35
|
+
|
|
36
|
+
/** The key `frame` is read by: its own where it names this frame, else a new one (not yet written). */
|
|
37
|
+
export function keyOf(frame) {
|
|
38
|
+
const own = frame.getAttribute(CHART_SOURCE);
|
|
39
|
+
return own !== null && frameOf(own) === frame ? own : nextChartKey(takenKeys());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `instance` made a view of `frame` (or of none: null takes its key off): its VIEW_FROM set, and the key
|
|
44
|
+
* on the frame where it carried none that names it. Records nothing: the caller's undo step does
|
|
45
|
+
* (`viewFrames`, struct's `attributes`). Returns whether anything changed.
|
|
46
|
+
*/
|
|
47
|
+
export function bindView(instance, frame) {
|
|
48
|
+
if (!frame) {
|
|
49
|
+
if (!instance.hasAttribute(VIEW_FROM)) return false;
|
|
50
|
+
instance.removeAttribute(VIEW_FROM);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const key = keyOf(frame);
|
|
54
|
+
let changed = false;
|
|
55
|
+
if (instance.getAttribute(VIEW_FROM) !== key) {
|
|
56
|
+
instance.setAttribute(VIEW_FROM, key);
|
|
57
|
+
changed = true;
|
|
58
|
+
}
|
|
59
|
+
if (frame.getAttribute(CHART_SOURCE) !== key) {
|
|
60
|
+
frame.setAttribute(CHART_SOURCE, key);
|
|
61
|
+
changed = true;
|
|
62
|
+
}
|
|
63
|
+
return changed;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A view that was made bound to the table above it. */
|
|
67
|
+
export function bindAbove(instance) {
|
|
68
|
+
return bindView(instance, tableAbove(instance));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* What the undo step that makes a block of the menu's key records of the tables' keys (struct's
|
|
73
|
+
* `attributes`): every table's frame when the block reads tables, as the table it is bound to is only
|
|
74
|
+
* known as the block is made; nothing for any other block.
|
|
75
|
+
*/
|
|
76
|
+
export const viewFrames = (key) => (menuRow(key)?.readsTables ? tableFrames().map((frame) => [frame, [CHART_SOURCE]]) : []);
|
|
77
|
+
|
|
78
|
+
// A table named for a reader: its place among the document's tables and the words of its first row.
|
|
79
|
+
function labelOf(frame, place) {
|
|
80
|
+
const first = frame.querySelector("tr");
|
|
81
|
+
const words = first ? [...first.cells].map((cell) => cell.textContent.replace(/\s+/g, " ").trim()).filter(Boolean).slice(0, 4).join("、") : "";
|
|
82
|
+
const shown = words.length > 28 ? `${words.slice(0, 27)}…` : words;
|
|
83
|
+
return shown ? t("表格 {0} · {1}", place, shown) : t("表格 {0}", place);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The document's tables for a list to choose from: [{ frame, label }], the label naming the table by its place and its first row. */
|
|
87
|
+
export const tableChoices = () => tableFrames().map((frame, at) => ({ frame, label: labelOf(frame, at + 1) }));
|
|
@@ -37,6 +37,4 @@ export const TABLE_ICONS = {
|
|
|
37
37
|
// 一键优化: a magic wand and its sparks; 识别数字列: a number sign.
|
|
38
38
|
magic: svg('<path d="m3.75 16.25 9-9"/><path d="m11.25 5.75 3 3"/><path d="M15.25 2.5v3M13.75 4h3M5.5 3.25v2M4.5 4.25h2M16 10.75v2M15 11.75h2"/>'),
|
|
39
39
|
numbers: svg('<path d="M8.25 4 6.75 16M13.75 4l-1.5 12M4.5 8h12M3.75 12.25h12"/>'),
|
|
40
|
-
// 视图: three columns of cards, a board drawn from the table.
|
|
41
|
-
view: svg('<rect x="3.25" y="3.75" width="3.5" height="7.5" rx="1"/><rect x="8.25" y="3.75" width="3.5" height="12.5" rx="1"/><rect x="13.25" y="3.75" width="3.5" height="5" rx="1"/>'),
|
|
42
40
|
};
|
|
@@ -57,7 +57,7 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
57
57
|
// it (the geometry's view), the grips and + points of the rows and columns in sight.
|
|
58
58
|
// Each change is one undo step; the page writes it as one operation of the table (grid.mjs). The
|
|
59
59
|
// keys and the cells' bar are cells.mjs's; what a view of the table asks (a custom block reading it:
|
|
60
|
-
// set a cell, move, add or take out a row)
|
|
60
|
+
// set a cell, move, add or take out a row), views.mjs's.
|
|
61
61
|
|
|
62
62
|
const STYLE = `
|
|
63
63
|
.ge-tgrip { display: flex; align-items: center; justify-content: center; cursor: grab; pointer-events: auto; }
|
|
@@ -108,15 +108,14 @@ const REACH = { out: 12, in: 8 }; // the pointer this near the table's top (left
|
|
|
108
108
|
* active(): whether the tools may show. struct(parents, touched, mutate): the editor's undoable
|
|
109
109
|
* structural change; look(elements, change, select): a change of classes and sizes as one undo step. selectBlock(unit):
|
|
110
110
|
* select the table as a block (Esc). resizeTable(event, unit, axis): a drag of the table's width
|
|
111
|
-
* ("x") or scroll height ("y") from an armed edge (resize/). say: the status line. cellsOf(data): what
|
|
111
|
+
* ("x") or scroll height ("y") from an armed edge (resize/). say: the status line; settle(): takes it off. cellsOf(data): what
|
|
112
112
|
* the clipboard holds as rows of cells, { rows, from } (clipboard/ cellsOf), or null. upload(kind,
|
|
113
113
|
* body): a file sent to the report's folder (POST /__gallery/edit/upload), a picture of a cell's
|
|
114
114
|
* (pictures.mjs).
|
|
115
115
|
* typedKey(event): a key that is the "/" or "@" menu's, standing on a cell's text (slash-menu/, at-menu/), comes before the cell's.
|
|
116
|
-
* recordStep(host, before): a text step of a text block, what a view's set is (history.mjs).
|
|
117
|
-
* { attributes, done }): a block of a menu's key put in below a block in one undo step (editor.mjs), what 视图 › makes.
|
|
116
|
+
* recordStep(host, before): a text step of a text block, what a view's set is (history.mjs).
|
|
118
117
|
*/
|
|
119
|
-
export function createTableEditing({ active, struct, look, selectBlock, resizeTable = () => false, say, cellsOf = () => null, upload = async () => { throw new Error("upload"); }, typedKey = () => false, recordStep = () => {}
|
|
118
|
+
export function createTableEditing({ active, struct, look, selectBlock, resizeTable = () => false, say, settle = () => {}, cellsOf = () => null, upload = async () => { throw new Error("upload"); }, typedKey = () => false, recordStep = () => {} }) {
|
|
120
119
|
addStyle(STYLE);
|
|
121
120
|
const menu = createMenu();
|
|
122
121
|
let hover = null; // { unit, row, column, edges: { row, column } }: the cell under the pointer, and whether it is at the table's left (top) edge (edgesAt)
|
|
@@ -268,7 +267,7 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
268
267
|
select(null);
|
|
269
268
|
selectBlock(unit);
|
|
270
269
|
},
|
|
271
|
-
bounds, selectedCells, tableAt, placeOf, cellOf, cellAt, geometry, change, insert, removeRange, duplicate, merge, split, look, struct, say,
|
|
270
|
+
bounds, selectedCells, tableAt, placeOf, cellOf, cellAt, geometry, change, insert, removeRange, duplicate, merge, split, look, struct, say, settle,
|
|
272
271
|
active,
|
|
273
272
|
menuOpen: () => menu.isOpen,
|
|
274
273
|
typedKey,
|
|
@@ -927,15 +926,13 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
927
926
|
|
|
928
927
|
addEventListener("scroll", () => { if (!drag) draw(); }, { passive: true, capture: true });
|
|
929
928
|
|
|
930
|
-
// What a view of a table asks
|
|
931
|
-
|
|
929
|
+
// What a view of a table asks (views.mjs).
|
|
930
|
+
createViews({ active, change, tableAt, recordStep, drawn: () => draw() });
|
|
932
931
|
|
|
933
932
|
// The rows of a table the tools manage. 一键优化 is a split row (menu.mjs): its words run it, its ›
|
|
934
|
-
// opens two of its parts to run alone, 自动调整列宽 and 识别数字列 (the user's, 2026-11-07)
|
|
935
|
-
// where a custom block reads tables (views.mjs).
|
|
933
|
+
// opens two of its parts to run alone, 自动调整列宽 and 识别数字列 (the user's, 2026-11-07).
|
|
936
934
|
function toolRows(unit) {
|
|
937
935
|
const state = tableState(unit);
|
|
938
|
-
const view = views.menuRow(unit);
|
|
939
936
|
return [
|
|
940
937
|
{ label: t("标题行"), icon: ICONS.headerRow, checked: state.headerRow, run: () => headerRow(unit, state) },
|
|
941
938
|
{ label: t("标题列"), icon: ICONS.headerColumn, checked: state.headerColumn, run: () => change(unit, () => setHeaderColumn(unit, !state.headerColumn), state.headerColumn ? t("已取消标题列") : t("已设为标题列")) },
|
|
@@ -947,7 +944,6 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
947
944
|
],
|
|
948
945
|
},
|
|
949
946
|
{ label: t("均分列宽"), icon: ICONS.even, run: () => widths.even(unit) },
|
|
950
|
-
...(view ? [view] : []),
|
|
951
947
|
];
|
|
952
948
|
}
|
|
953
949
|
|
|
@@ -998,8 +994,8 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
998
994
|
* A table's rows of its block menu (notion-lite's 表格 section, under the menu's one title): a
|
|
999
995
|
* table of the kind these tools manage has 标题行 and 标题列, switches (checked when on); then
|
|
1000
996
|
* actions, each applied once (no check): 一键优化 › (自动调整列宽, 识别数字列 in its submenu),
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
997
|
+
* 均分列宽; any table in its frame has its size's rows after them (layoutRows). [] for any other block.
|
|
998
|
+
* (The table's 对齐 › is the block menu's own row, above 颜色: page/align.mjs, as a text block's.)
|
|
1003
999
|
*/
|
|
1004
1000
|
menuItems(target) {
|
|
1005
1001
|
if (!target?.matches?.('[data-ge-unit-kind="table"]')) return [];
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { TABLE } from "../../components/kit.mjs";
|
|
2
1
|
import { insertRow, moveRow, removeRows, tableState } from "../../components/table/page.mjs";
|
|
3
2
|
import { tableOf } from "../../components/table/grid.mjs";
|
|
4
3
|
import { ownHTML } from "../core.mjs";
|
|
5
|
-
import { menuEntry, viewKeys } from "../page-objects.mjs";
|
|
6
|
-
import { CHART_SOURCE, TABLE_READERS, VIEW_FROM, nextChartKey } from "../../../shared/table-chart.mjs";
|
|
7
|
-
import { t } from "../../../shared/i18n/index.mjs";
|
|
8
|
-
import { TABLE_ICONS } from "./icons.mjs";
|
|
9
4
|
|
|
10
5
|
// The views of a table (docs/editor.md, "Table views"; the plan's 阶段 4): an instance of a custom
|
|
11
6
|
// block whose needs hold "tables", bound to a table of the document by the key its frame carries
|
|
@@ -31,25 +26,15 @@ import { TABLE_ICONS } from "./icons.mjs";
|
|
|
31
26
|
// are those after its header row: the head's rows, or with no head its body's first row, which a view
|
|
32
27
|
// reads its columns' names from; no intent puts a row there or takes that one.
|
|
33
28
|
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// the 组件列表 switches; page-objects.mjs viewKeys: no other menu lists them, as one put in unbound only
|
|
37
|
-
// says it has no table) and puts one in right after the table bound to it, in one undo step: the table's key
|
|
38
|
-
// where it names this table (the first frame carrying it, as the kit and the checks read a key), or a new
|
|
39
|
-
// one (the first of t1, t2 … the page does not hold) put on its frame in that step (a table made from "/"
|
|
40
|
-
// has none; the second of two an Agent wrote with one key is not the one it names). The commit writes
|
|
41
|
-
// both, and the server makes sure of the key against the file (source/chart-keys.mjs relinkMade); a key
|
|
42
|
-
// it gives the pair anew the page follows, its undo steps too (page/commit.mjs apply, history.mjs rekeyed).
|
|
29
|
+
// Which table a view reads is binding.mjs's: a view made from a menu is bound to the table above it, and
|
|
30
|
+
// its 设置 (components/installed/page.mjs) changes it.
|
|
43
31
|
|
|
44
32
|
/**
|
|
45
33
|
* active(): whether the page edits now. change(unit, mutate, said, touched): the table tools' one undo
|
|
46
34
|
* step (index.mjs). tableAt(node): the table unit these tools manage that a node is in. recordStep(host,
|
|
47
|
-
* before): a text step of a text block (history.mjs).
|
|
48
|
-
* block of the menu's `key` put in below a block in one undo step, `attributes` ([element, names]) recorded
|
|
49
|
-
* with it and `done(made)` run in it on the new block (editor.mjs insertBelow). drawn(): the tools drawn
|
|
50
|
-
* again (a row taken out).
|
|
35
|
+
* before): a text step of a text block (history.mjs). drawn(): the tools drawn again (a row taken out).
|
|
51
36
|
*/
|
|
52
|
-
export function createViews({ active, change, tableAt, recordStep,
|
|
37
|
+
export function createViews({ active, change, tableAt, recordStep, drawn = () => {} }) {
|
|
53
38
|
const merged = (rows) => rows.some((row) => [...row.cells].some((cell) => cell.rowSpan !== 1 || cell.colSpan !== 1));
|
|
54
39
|
const writable = (cell) => cell?.getAttribute("contenteditable") === "true";
|
|
55
40
|
const holds = (cell, text) => cell.textContent === text && cell.childElementCount === 0;
|
|
@@ -114,37 +99,5 @@ export function createViews({ active, change, tableAt, recordStep, insertBelow,
|
|
|
114
99
|
if (run(event.detail ?? {})) event.preventDefault();
|
|
115
100
|
});
|
|
116
101
|
|
|
117
|
-
|
|
118
|
-
// first that carries it, none of a dev block's.
|
|
119
|
-
const frameOf = (key) => [...document.querySelectorAll(`[${CHART_SOURCE}]`)].find((node) => node.getAttribute(CHART_SOURCE) === key && node.matches(TABLE.frame) && !node.closest("[data-doc-dev]")) ?? null;
|
|
120
|
-
// A view of the block `key` put in right after `unit`, bound to its table: by its key where that names
|
|
121
|
-
// it, else by a new one put on it in the same step (a table made from "/" has none; one of two an Agent
|
|
122
|
-
// wrote with one key is not the one it names, the first: a view by it would read and change that one).
|
|
123
|
-
function insertView(unit, key) {
|
|
124
|
-
const own = unit.getAttribute(CHART_SOURCE);
|
|
125
|
-
const taken = new Set([...document.querySelectorAll([CHART_SOURCE, ...TABLE_READERS].map((name) => `[${name}]`).join(", "))].flatMap((node) => [CHART_SOURCE, ...TABLE_READERS].map((name) => node.getAttribute(name)).filter((value) => value !== null)));
|
|
126
|
-
const bound = own !== null && frameOf(own) === unit ? own : nextChartKey(taken);
|
|
127
|
-
insertBelow(unit, key, {
|
|
128
|
-
attributes: [[unit, [CHART_SOURCE]]],
|
|
129
|
-
done(made) {
|
|
130
|
-
made?.setAttribute(VIEW_FROM, bound);
|
|
131
|
-
if (bound !== own) unit.setAttribute(CHART_SOURCE, bound);
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
/** The 视图 › row of a table's block menu: one for each custom block that reads tables, or null where there is none or the table cannot be read as rows (no frame, a merged cell). */
|
|
138
|
-
menuRow(unit) {
|
|
139
|
-
const keys = viewKeys();
|
|
140
|
-
if (keys.length === 0 || !unit.matches(TABLE.frame) || merged(tableState(unit).rows)) return null;
|
|
141
|
-
return {
|
|
142
|
-
label: t("视图"), icon: TABLE_ICONS.view,
|
|
143
|
-
submenu: () => viewKeys().map((key) => {
|
|
144
|
-
const row = menuEntry(key).variant;
|
|
145
|
-
return { label: row.label, icon: row.icon, run: () => insertView(unit, key) };
|
|
146
|
-
}),
|
|
147
|
-
};
|
|
148
|
-
},
|
|
149
|
-
};
|
|
102
|
+
return {};
|
|
150
103
|
}
|
|
@@ -11,7 +11,8 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
11
11
|
|
|
12
12
|
// A table's column widths (notion-lite's: a 3px strip on each column's right edge; the plan's D4:
|
|
13
13
|
// the kit's own widths until the reader sizes a column). Dragging a strip sizes its column (35px at
|
|
14
|
-
// least
|
|
14
|
+
// least; the last one's, the table's right edge, snaps where the table is exactly as wide as the
|
|
15
|
+
// text: SNAP), shown as it goes (Esc puts it back), written when let go as one undo
|
|
15
16
|
// step: every column's width on the topmost cell of that column alone (--doc-column-width,
|
|
16
17
|
// index.mjs COLUMN_WIDTH; shared/table-slots.mjs widthHolder: a merged cell holds none), the kit laying the table
|
|
17
18
|
// out at them (tables/layout.js manualLayout); the first
|
|
@@ -50,6 +51,8 @@ const STYLE = `
|
|
|
50
51
|
.ge-trows > b > svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; transform: rotate(90deg); }
|
|
51
52
|
${gestureLock("data-ge-tresize", { cursor: "col-resize", select: true })}
|
|
52
53
|
`;
|
|
54
|
+
const SNAP = 6; // px: the last column's edge within this of the width that fills the text lands on it (as the resize tool's: resize/)
|
|
55
|
+
const LIVE = 60000; // ms: the notice saying the table is full stays while the drag lasts (taken off when it ends)
|
|
53
56
|
const DOUBLE = 500; // two presses on a strip within this many ms are a double click (a press on the armed edge after its click too)
|
|
54
57
|
const EDGE = 8; // the edges' strips reach this far past the table (the resize tool's grab)
|
|
55
58
|
const ARROWS = '<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1.5 6h9M3.5 4 1.5 6l2 2M8.5 4l2 2-2 2"/></svg>';
|
|
@@ -62,8 +65,29 @@ export function createColumnWidths(tools) {
|
|
|
62
65
|
let armed = null; // an edge clicked: { unit, axis: "x" (right) | "y" (bottom), at: when, shown }, the next drag of it the table's size
|
|
63
66
|
let quick = null; // the armed right edge pressed soon after its click: { unit, index, x } (let go there, a double click)
|
|
64
67
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Each column's width as the table shows it now, px: the width of its cells, not the distance
|
|
70
|
+
* between their edges. A table scrolled sideways keeps its first column in sight (position: sticky,
|
|
71
|
+
* tables.css: a header column or .doc-table--sticky-first), so the edges of those cells are not the
|
|
72
|
+
* columns' and the column under them would read as the part of it in sight: every width then sums
|
|
73
|
+
* to the frame's, the table seeming full, the columns before the border squeezed when it was pressed.
|
|
74
|
+
*/
|
|
75
|
+
const shown = (unit) => {
|
|
76
|
+
const { slots } = tableState(unit);
|
|
77
|
+
if (!slots) return tools.geometry(unit).columns.map((column) => Math.round((column.right - column.left) * 100) / 100);
|
|
78
|
+
const cells = [...slots.places].map(([cell, place]) => ({ ...place, width: cell.getBoundingClientRect().width }));
|
|
79
|
+
const widths = Array(slots.columns).fill(null);
|
|
80
|
+
for (const { c, cols, width } of cells) if (cols === 1) widths[c] ??= width;
|
|
81
|
+
// A column no single cell gives a width: what the cells merged over it leave, shared out.
|
|
82
|
+
for (const { c, cols, width } of cells.filter((cell) => cell.cols > 1).sort((a, b) => a.cols - b.cols)) {
|
|
83
|
+
const over = Array.from({ length: cols }, (_, i) => c + i);
|
|
84
|
+
const open = over.filter((i) => widths[i] === null);
|
|
85
|
+
if (open.length === 0) continue;
|
|
86
|
+
const left = Math.max(0, width - over.reduce((sum, i) => sum + (widths[i] ?? 0), 0));
|
|
87
|
+
for (const i of open) widths[i] = left / open.length;
|
|
88
|
+
}
|
|
89
|
+
return widths.map((width) => Math.round((width ?? 0) * 100) / 100);
|
|
90
|
+
};
|
|
67
91
|
/** The cells holding the columns' widths: per column, its topmost cell alone, or null. */
|
|
68
92
|
const holders = (unit) => {
|
|
69
93
|
const { slots } = tableState(unit);
|
|
@@ -101,20 +125,56 @@ export function createColumnWidths(tools) {
|
|
|
101
125
|
return done;
|
|
102
126
|
}
|
|
103
127
|
|
|
104
|
-
// Shown as it goes: the holders' widths set straight (taken back before the undo step)
|
|
105
|
-
|
|
128
|
+
// Shown as it goes: the holders' widths set straight (taken back before the undo step), the
|
|
129
|
+
// table's own width (--doc-span) off while it lasts, as the kit would else keep the columns in
|
|
130
|
+
// proportion to it and the table where it was.
|
|
131
|
+
function preview(drag, widths) {
|
|
132
|
+
const { unit } = drag;
|
|
133
|
+
if (drag.span === undefined) {
|
|
134
|
+
drag.span = unit.style.getPropertyValue(SPAN);
|
|
135
|
+
unit.style.removeProperty(SPAN);
|
|
136
|
+
}
|
|
106
137
|
holders(unit).forEach((cell, c) => cell?.style.setProperty(COLUMN_WIDTH, `${widths[c]}px`));
|
|
107
138
|
relayout(unit);
|
|
108
139
|
}
|
|
109
|
-
function putBack({ unit, before }) {
|
|
140
|
+
function putBack({ unit, before, span }) {
|
|
110
141
|
for (const [cell, width] of before) {
|
|
111
142
|
if (width) cell.style.setProperty(COLUMN_WIDTH, width);
|
|
112
143
|
else cell.style.removeProperty(COLUMN_WIDTH);
|
|
113
144
|
if (cell.getAttribute("style") === "") cell.removeAttribute("style");
|
|
114
145
|
}
|
|
146
|
+
if (span) unit.style.setProperty(SPAN, span);
|
|
115
147
|
relayout(unit);
|
|
116
148
|
}
|
|
117
149
|
|
|
150
|
+
// The columns' widths together (px) at which the table is exactly as wide as the text (or the
|
|
151
|
+
// column it stands in): the room its frame has, as the kit lays a table out at the reader's widths
|
|
152
|
+
// (tables/layout.js manualLayout: a table of that width or more has the frame at 100%, one less
|
|
153
|
+
// a frame as wide as it). Null where the frame has none.
|
|
154
|
+
function fillOf(unit) {
|
|
155
|
+
const parent = unit.parentElement;
|
|
156
|
+
if (!parent) return null;
|
|
157
|
+
const sides = (node) => {
|
|
158
|
+
const style = getComputedStyle(node);
|
|
159
|
+
return ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"].reduce((sum, name) => sum + (parseFloat(style[name]) || 0), 0);
|
|
160
|
+
};
|
|
161
|
+
const room = parent.getBoundingClientRect().width - sides(parent) - sides(unit);
|
|
162
|
+
return room > 0 ? room : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The last column's right edge is the table's: within SNAP of the width that fills the text, the
|
|
166
|
+
// last column takes what the others leave of it (rounded down: never a hair past the frame).
|
|
167
|
+
// True when it did. `widths` is changed in place.
|
|
168
|
+
function snapFull({ last, fill, start, index }, widths) {
|
|
169
|
+
if (!last || fill === null) return false;
|
|
170
|
+
const rest = start.reduce((sum, width, c) => (c === index ? sum : sum + width), 0);
|
|
171
|
+
const wanted = Math.floor((fill - rest) * 100) / 100;
|
|
172
|
+
if (wanted < MIN_COLUMN_WIDTH || Math.abs(rest + widths[index] - fill) > SNAP) return false;
|
|
173
|
+
widths[index] = wanted;
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
const FULL = () => [t("表格"), t("宽度"), t("铺满正文")];
|
|
177
|
+
|
|
118
178
|
// A column fitted to its widest cell of it alone, unwrapped (+1px): by its words, not the words an
|
|
119
179
|
// Agent took out that the page shows in it (changes/: out of the layout while it is measured).
|
|
120
180
|
function fit(unit, index) {
|
|
@@ -221,7 +281,7 @@ export function createColumnWidths(tools) {
|
|
|
221
281
|
return fit(unit, index);
|
|
222
282
|
}
|
|
223
283
|
const { rows } = tableParts(unit);
|
|
224
|
-
drag = { unit, index, x: event.clientX, last, fresh: true, start: shown(unit), before: new Map(rows.flatMap((row) => [...row.cells]).map((cell) => [cell, widthOf(cell)])) };
|
|
284
|
+
drag = { unit, index, x: event.clientX, last, fill: last ? fillOf(unit) : null, told: false, fresh: true, start: shown(unit), before: new Map(rows.flatMap((row) => [...row.cells]).map((cell) => [cell, widthOf(cell)])) };
|
|
225
285
|
document.documentElement.setAttribute("data-ge-tresize", "");
|
|
226
286
|
document.documentElement.setPointerCapture?.(event.pointerId);
|
|
227
287
|
});
|
|
@@ -262,8 +322,16 @@ export function createColumnWidths(tools) {
|
|
|
262
322
|
drag.moved = true;
|
|
263
323
|
const widths = [...drag.start];
|
|
264
324
|
widths[drag.index] = Math.max(MIN_COLUMN_WIDTH, Math.round(drag.start[drag.index] + event.clientX - drag.x));
|
|
325
|
+
// The last column's edge snaps to the table that fills the text; the bottom notice says so while it holds.
|
|
326
|
+
const snapped = snapFull(drag, widths);
|
|
265
327
|
drag.widths = widths;
|
|
266
|
-
|
|
328
|
+
drag.snapped = snapped;
|
|
329
|
+
preview(drag, widths);
|
|
330
|
+
if (snapped !== drag.told) {
|
|
331
|
+
drag.told = snapped;
|
|
332
|
+
if (snapped) tools.say(t("{0}{1}:{2}", ...FULL()), "note", LIVE);
|
|
333
|
+
else tools.settle();
|
|
334
|
+
}
|
|
267
335
|
tools.draw();
|
|
268
336
|
}, true);
|
|
269
337
|
|
|
@@ -273,9 +341,13 @@ export function createColumnWidths(tools) {
|
|
|
273
341
|
document.documentElement.removeAttribute("data-ge-tresize");
|
|
274
342
|
if (!done) return;
|
|
275
343
|
putBack(done);
|
|
276
|
-
if (
|
|
344
|
+
if (done.told) tools.settle(); // the notice of the table filling the text: the result's follows, or nothing
|
|
345
|
+
// Back at the widths it had, snapped to a table that was full already (or dragged out and back): nothing to write.
|
|
346
|
+
const unchanged = done.widths?.every((width, c) => Math.abs(width - done.start[c]) < 0.5);
|
|
347
|
+
if (!cancel && done.widths) {
|
|
348
|
+
if (!unchanged && set(done.unit, done.widths) && done.snapped) tools.say(t("已调整{0}{1}:{2} · Ctrl+Z 撤销", ...FULL()));
|
|
277
349
|
// The last strip clicked (pressed, not moved): the table's right edge armed.
|
|
278
|
-
else if (!cancel && done.last) return arm(done.unit, "x");
|
|
350
|
+
} else if (!cancel && done.last) return arm(done.unit, "x");
|
|
279
351
|
tools.draw();
|
|
280
352
|
}
|
|
281
353
|
document.addEventListener("pointerup", () => end(false), true);
|
|
@@ -7,6 +7,7 @@ import { joinCode } from "../marks.mjs";
|
|
|
7
7
|
import { numberWithUnit, sideOf, WORD } from "../../../shared/inline-spacing.mjs";
|
|
8
8
|
import { pageOf, pagesAround } from "../page-objects.mjs";
|
|
9
9
|
import { BLOCK_TYPES, HOSTS, accepts, firstSaysOf, placeholderOf, textFilters, textTypeOf } from "../../components/registry.mjs";
|
|
10
|
+
import { unalign } from "../align.mjs";
|
|
10
11
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
11
12
|
|
|
12
13
|
// Typing in a block (docs/editor.md, "Browser side"): every key goes first to the component the block
|
|
@@ -14,7 +15,7 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
14
15
|
// `if (!ctx.def.keys?.enter?.(ctx)) splitBlock(...)`, and then to the rules of every block. Enter
|
|
15
16
|
// splits it, Backspace and Delete at its edges merge it with the block before or after, or remove it
|
|
16
17
|
// once empty, the caret going to the neighbouring block. Backspace in a container with nothing
|
|
17
|
-
// written in it (`removedEmpty`)
|
|
18
|
+
// written in it (`removedEmpty`) or in an empty heading turns it into an empty paragraph. Chinese typed next to a Latin letter or
|
|
18
19
|
// digit gets a space between them (autoSpace; not a number and its unit, nor in a block written without), and an empty block under the caret says what it takes
|
|
19
20
|
// (placeholder). Plain typing and input methods stay with the browser; the keys are routed here by
|
|
20
21
|
// editor.mjs.
|
|
@@ -53,8 +54,11 @@ function writtenTogether(host, node, start, end) {
|
|
|
53
54
|
* deleted as the selected ones are (selection/delete.mjs). adding(): the block the "/" menu the
|
|
54
55
|
* handle's "+" opened is open in (slash-menu/), or null. leave: where Enter and Backspace leave a
|
|
55
56
|
* container (leave.mjs). showHidden(node): the folds hiding a block opened (fold/ showAround).
|
|
57
|
+
* look(element, change): the editor's undoable change of classes (a block's alignment, align.mjs).
|
|
58
|
+
* afterAbove(host): Backspace at the start of a text block right after a block of no text, the caret
|
|
59
|
+
* put after that block (after/ back); whether it was.
|
|
56
60
|
*/
|
|
57
|
-
export function createTyping({ struct, say, keepText, paragraphBefore, removeBlocks, selectBlock = () => {}, adding = () => null, leave, showHidden = () => {} }) {
|
|
61
|
+
export function createTyping({ struct, say, keepText, paragraphBefore, removeBlocks, selectBlock = () => {}, adding = () => null, leave, showHidden = () => {}, look = () => false, afterAbove = () => false }) {
|
|
58
62
|
// A key, asked of the component of the text block `host` and then of what stands around it, nearest
|
|
59
63
|
// first (docs/editor.md, "The order of keys"): whether it was theirs. `ctx` is notion-lite's, as
|
|
60
64
|
// much of it as a key reads, the block being an element here.
|
|
@@ -101,8 +105,8 @@ export function createTyping({ struct, say, keepText, paragraphBefore, removeBlo
|
|
|
101
105
|
function split(host) {
|
|
102
106
|
if (HOSTS[host.getAttribute("data-ge-kind")]?.opensBody) return enterBody(host);
|
|
103
107
|
// The caret at the very start of a block with text: an empty paragraph goes before the block and
|
|
104
|
-
// the caret stays (a block that splits does this itself, splitBlock).
|
|
105
|
-
//
|
|
108
|
+
// the caret stays (a block that splits does this itself, splitBlock). Only hosts that opt in take
|
|
109
|
+
// this rule; code keeps its own Enter, which breaks the line even at the start.
|
|
106
110
|
const own = HOSTS[host.getAttribute("data-ge-kind")]?.opensBefore; // not a cell, a card's field, the title
|
|
107
111
|
if (own && !can(host, "split") && !isEmpty(host) && atEdge(host, false) && paragraphBefore(host)) return;
|
|
108
112
|
// Its component's Enter (a code block's line, a nested item out a level, a title going into its
|
|
@@ -152,10 +156,33 @@ export function createTyping({ struct, say, keepText, paragraphBefore, removeBlo
|
|
|
152
156
|
return true;
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
// Whether a block may not go (keepText): the reason said, a note that stays a while.
|
|
160
|
+
function kept(node) {
|
|
161
|
+
const reason = keepText(node);
|
|
162
|
+
if (reason) say(reason, "note", 6000);
|
|
163
|
+
return Boolean(reason);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// An empty container turned into an empty paragraph where it stands, the caret in it (the user's,
|
|
167
|
+
// 2026-11-09: as an empty heading, an empty item and every other empty block go back to body text,
|
|
168
|
+
// not away with the caret on the block before); one undo step, which brings the container back.
|
|
169
|
+
// Where its box takes no paragraph it is deleted whole (one undo step, which selects it again), the
|
|
170
|
+
// caret at the end of the block before it, or at the start of the one after it when it was first (a
|
|
171
|
+
// formula block passed over: textNear).
|
|
158
172
|
function removeContainer(container) {
|
|
173
|
+
const box = container.parentElement;
|
|
174
|
+
const paragraph = newParagraphHost(document);
|
|
175
|
+
if (box && accepts(boxKind(box), paragraph.getAttribute("data-ge-type"))) {
|
|
176
|
+
if (kept(container)) return true;
|
|
177
|
+
const parts = withLeads(container);
|
|
178
|
+
struct([box], [], () => {
|
|
179
|
+
parts[0].before(paragraph);
|
|
180
|
+
parts.forEach((part) => part.remove());
|
|
181
|
+
return { touched: [paragraph], focus: paragraph };
|
|
182
|
+
});
|
|
183
|
+
caretAt(paragraph, "start");
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
159
186
|
const list = hosts();
|
|
160
187
|
const inside = list.filter((node) => container.contains(node));
|
|
161
188
|
const before = textNear(inside[0], -1, list);
|
|
@@ -181,11 +208,7 @@ export function createTyping({ struct, say, keepText, paragraphBefore, removeBlo
|
|
|
181
208
|
? (next ? [next, "start"] : [previous, "end"])
|
|
182
209
|
: (previous ? [previous, "end"] : [next, "start"]);
|
|
183
210
|
const node = removalTarget(host);
|
|
184
|
-
|
|
185
|
-
if (kept) {
|
|
186
|
-
say(kept, "note", 6000);
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
211
|
+
if (kept(node)) return true;
|
|
189
212
|
struct([node.parentNode], [host], () => {
|
|
190
213
|
takeOut(node);
|
|
191
214
|
return { touched: [], focus: target };
|
|
@@ -203,6 +226,9 @@ export function createTyping({ struct, say, keepText, paragraphBefore, removeBlo
|
|
|
203
226
|
// Backspace at the start of a block. Returns whether it was handled.
|
|
204
227
|
function backspace(host) {
|
|
205
228
|
opened(previousLine(host));
|
|
229
|
+
// A paragraph's or a heading's alignment, then its indent a level at a time, goes before anything
|
|
230
|
+
// else, one undo step each (align.mjs unalign: Feishu's 退对齐 and 反缩进 before a join).
|
|
231
|
+
if (unalign(host, look)) return true;
|
|
206
232
|
// The block's component (a list item's: a nested item moves out a level, the first or last item of
|
|
207
233
|
// a top-level list leaves it as body text, an emptied item holding lists stays), then what stands
|
|
208
234
|
// around it (the item a block stands under; a node, at the start of its first line, holding
|
|
@@ -212,6 +238,12 @@ export function createTyping({ struct, say, keepText, paragraphBefore, removeBlo
|
|
|
212
238
|
const container = emptyContainer(host);
|
|
213
239
|
if (container) return removeContainer(container);
|
|
214
240
|
if (leave.backspace(host)) return true;
|
|
241
|
+
// An empty heading (or small text) is an empty paragraph, where it stands (leave.mjs emptied).
|
|
242
|
+
if (leave.emptied(host)) return true;
|
|
243
|
+
// Right after a block of no text (a picture, a table, a code block, a callout, a divider, a card…):
|
|
244
|
+
// the caret after it, at its bottom right (after/), an empty block going (the user's, 2026-11-09:
|
|
245
|
+
// Backspace there went into the block, or past a picture to the text before it); never a join.
|
|
246
|
+
if (afterAbove(host)) return true;
|
|
215
247
|
if (isEmpty(host)) return removeHost(host, false);
|
|
216
248
|
if (merge(host)) return true;
|
|
217
249
|
// A block holding blocks that did not join the line above says why, as its component puts it.
|