@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
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
|
-
@heycys/gallery 0.
|
|
3
|
+
@heycys/gallery 0.2.0 carries the following code and data of others, bundled into dist/ and web/, built into kit/, or as files in vendor/ and browser/.
|
|
4
4
|
|
|
5
5
|
## @asamuzakjp/css-color 5.1.11
|
|
6
6
|
|
|
@@ -3254,7 +3254,7 @@ SOFTWARE.
|
|
|
3254
3254
|
|
|
3255
3255
|
## Lucide icons
|
|
3256
3256
|
|
|
3257
|
-
A few line icons are Lucide's (https://lucide.dev), as lucide-react 1.67.0 has them: the workbench's sidebar toggle, search, 新建 and settings gear, the 写作偏好 book,
|
|
3257
|
+
A few line icons are Lucide's (https://lucide.dev), as lucide-react 1.67.0 has them: the workbench's sidebar toggle, search, 新建 and settings gear, the 写作偏好 book, the icon of a page with none (the workbench, the editor, the kit's page block, the browser tab), and the default home page's house.
|
|
3258
3258
|
|
|
3259
3259
|
```
|
|
3260
3260
|
ISC License
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// A block's alignment, and a text block's indent (对齐与缩进, the block menu's: Feishu's 缩进和对齐 ›;
|
|
2
|
+
// components that say `toggles: ALIGN_TOGGLES`, a paragraph and a heading; a table, whose frame is placed
|
|
3
|
+
// on the page the same way, says `toggles: ALIGN_PLACES`). Class switches the file keeps and the
|
|
4
|
+
// kit draws (src/render/kit/styles/flow.css, table.css): centred or right-aligned (doc-align-center,
|
|
5
|
+
// doc-align-right; left is neither, the kit's own), and for a text block moved in by levels of two body
|
|
6
|
+
// characters (doc-indent-1 … doc-indent-8). The page editor's block menu sets them (page/align.mjs), Enter carries them
|
|
7
|
+
// to the block it makes (core.mjs newBlock), and Backspace at a block's start takes them off one at a
|
|
8
|
+
// time before it joins or deletes (typing/: the alignment first, then a level of indent, Feishu's
|
|
9
|
+
// order).
|
|
10
|
+
|
|
11
|
+
import { TEXT_ALIGN_CLASSES } from "../../shared/components/paragraph.mjs";
|
|
12
|
+
|
|
13
|
+
/** The alignments a text block may have, by name: none is left (the kit's own). */
|
|
14
|
+
export const ALIGNS = { center: "doc-align-center", right: "doc-align-right" };
|
|
15
|
+
|
|
16
|
+
/** The indents' classes, level 1 first (the kit's: src/shared/components/paragraph.mjs). */
|
|
17
|
+
export const INDENTS = TEXT_ALIGN_CLASSES.filter((name) => name.startsWith("doc-indent-"));
|
|
18
|
+
|
|
19
|
+
/** The deepest indent. */
|
|
20
|
+
export const INDENT_MOST = INDENTS.length;
|
|
21
|
+
|
|
22
|
+
/** The classes of a text block's alignment and indent: its component's `toggles`. */
|
|
23
|
+
export const ALIGN_TOGGLES = TEXT_ALIGN_CLASSES;
|
|
24
|
+
|
|
25
|
+
/** The classes of a block's alignment alone (a table's frame: its component's `toggles`), the same two. */
|
|
26
|
+
export const ALIGN_PLACES = Object.values(ALIGNS);
|
|
27
|
+
|
|
28
|
+
/** The elements that take a text block's alignment and indent (a paragraph, a heading; a cell's alignment is its own classes'). */
|
|
29
|
+
export const TEXT_BLOCKS = "p, h1, h2, h3, h4";
|
|
30
|
+
|
|
31
|
+
/** A block's alignment: "left", "center" or "right". */
|
|
32
|
+
export const alignOf = (element) => Object.keys(ALIGNS).find((name) => element.classList.contains(ALIGNS[name])) ?? "left";
|
|
33
|
+
|
|
34
|
+
/** A block's indent: 0 to INDENT_MOST. */
|
|
35
|
+
export const indentOf = (element) => INDENTS.findIndex((name) => element.classList.contains(name)) + 1;
|
|
36
|
+
|
|
37
|
+
/** The look (class switches) that gives a block the alignment `name` ("left": none). */
|
|
38
|
+
export const alignLook = (name) => ({ classes: Object.fromEntries(Object.entries(ALIGNS).map(([each, cls]) => [cls, each === name])) });
|
|
39
|
+
|
|
40
|
+
/** The look that gives a block the indent `level` (0: none). */
|
|
41
|
+
export const indentLook = (level) => ({ classes: Object.fromEntries(INDENTS.map((cls, index) => [cls, index + 1 === level])) });
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The alignment and indent of text block `from` (a paragraph or a heading) given to `to`, a paragraph
|
|
45
|
+
* made from it (Enter's split: core.mjs newBlock, the half after the caret or the empty line before it,
|
|
46
|
+
* as a word processor's new line keeps its paragraph's). Returns `to`.
|
|
47
|
+
*/
|
|
48
|
+
export function carry(from, to) {
|
|
49
|
+
if (!from?.matches?.(TEXT_BLOCKS)) return to;
|
|
50
|
+
for (const name of ALIGN_TOGGLES) if (from.classList.contains(name)) to.classList.add(name);
|
|
51
|
+
return to;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Whether a block takes an alignment (its kind's toggles hold it; `toggles`: TOGGLES of its kind). */
|
|
55
|
+
export const aligns = (toggles) => Boolean(toggles?.includes(ALIGNS.center));
|
|
56
|
+
|
|
57
|
+
/** Whether a block takes an indent too, as a text block does (a table's alignment has none). */
|
|
58
|
+
export const indents = (toggles) => Boolean(toggles?.includes(INDENTS[0]));
|
|
@@ -67,7 +67,7 @@ export default {
|
|
|
67
67
|
components: ["doc-code"],
|
|
68
68
|
placeholders: { title: t("请输入代码块名称") }, // Feishu's
|
|
69
69
|
hosts: {
|
|
70
|
-
code: { name: t("代码块"), caps: CODE_CAPS, opensBefore:
|
|
70
|
+
code: { name: t("代码块"), caps: CODE_CAPS, opensBefore: false, textFilters: false, closed: true, inner: (element) => escapeText(codeText(element)) }, // Enter stays inside its code, including at the start
|
|
71
71
|
codeTitle: { name: t("代码块标题"), caps: CODE_CAPS, parts: [["code", "title"]] }, // one line; empty, the kit shows 代码块
|
|
72
72
|
},
|
|
73
73
|
types: {
|
|
@@ -37,7 +37,7 @@ const STYLE = `
|
|
|
37
37
|
${gestureLock("data-ge-embed-resize=\"right\"", { cursor: "nwse-resize" })}
|
|
38
38
|
${gestureLock("data-ge-embed-resize=\"left\"", { cursor: "nesw-resize" })}
|
|
39
39
|
.ge-layer > .ge-embed-corner { pointer-events: auto; }
|
|
40
|
-
/* A corner handle: a plain gray bar bent round the frame's corner, no border
|
|
40
|
+
/* A corner handle: a plain gray bar bent round the frame's corner, no border. */
|
|
41
41
|
.ge-embed-corner { width: 20px; height: 20px; cursor: nwse-resize; }
|
|
42
42
|
.ge-embed-corner[data-side="left"] { cursor: nesw-resize; }
|
|
43
43
|
.ge-embed-corner::after { content: ""; position: absolute; bottom: 5px; width: 9px; height: 9px; border: 0 solid light-dark(rgb(55 53 47 / 35%), rgb(255 255 255 / 38%)); border-bottom-width: 3px; transition: border-color 120ms ease; }
|
|
@@ -58,9 +58,9 @@ export function create({ pages, fileUrl }) {
|
|
|
58
58
|
onEmbed: (url) => fetchInto(figure, url),
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
async function uploadInto(figure, file) {
|
|
61
|
+
async function uploadInto(figure, file, options = {}) {
|
|
62
62
|
if (file.size > MAX_BYTES) return say(t("文件太大(最多 {0})。", sizeLimit("file")), "error");
|
|
63
|
-
return media.attempt(figure, t("正在上传 {0}…", file.name), t("无法上传这个文件"), async () => place(figure, await media.uploadFile(file)));
|
|
63
|
+
return media.attempt(figure, t("正在上传 {0}…", file.name), t("无法上传这个文件"), async () => place(figure, await media.uploadFile(file), options));
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const fetchInto = (figure, url) => media.attempt(figure, t("正在下载…"), t("无法下载这个文件"), async () => place(figure, await media.uploadUrl(url)));
|
|
@@ -69,13 +69,13 @@ export function create({ pages, fileUrl }) {
|
|
|
69
69
|
// with its caption), never the same element, so that undo brings back what stood there (a
|
|
70
70
|
// placeholder never written, a block as it was) and the file loses the new one. One undo step; the
|
|
71
71
|
// block selected.
|
|
72
|
-
function place(figure, answer) {
|
|
72
|
+
function place(figure, answer, { pasted = false } = {}) {
|
|
73
73
|
if (!figure.isConnected) return;
|
|
74
74
|
const doc = figure.ownerDocument;
|
|
75
75
|
const replacing = Boolean(linkOf(figure));
|
|
76
76
|
const next = newFile(doc);
|
|
77
77
|
next.replaceChildren(...fileParts(doc, answer, fileUrl(answer.path), captionOf(figure)?.innerHTML ?? ""));
|
|
78
|
-
media.place(figure, next, { replacing });
|
|
78
|
+
media.place(figure, next, { replacing, pasted });
|
|
79
79
|
if (replacing) say(t("已替换文件 · Ctrl+Z 撤销"));
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { headingIcon } from "../icons.mjs";
|
|
2
|
+
import { ALIGN_TOGGLES } from "../align.mjs";
|
|
2
3
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
3
4
|
|
|
4
5
|
// 标题 1–4: headings h1–h4 (the fourth is body-sized, set apart by its weight; an author's h5 or h6 is
|
|
@@ -10,7 +11,7 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
10
11
|
// heading into a paragraph), clipboard/own/ and src/transfer/markdown.mjs (written by its level: `clipboard`).
|
|
11
12
|
// Folding its section: page/fold/ (the triangle right of its words, what is hidden, the section going with
|
|
12
13
|
// it wherever blocks are taken away whole) and page.mjs (Ctrl/⌘+Enter in its words).
|
|
13
|
-
// Its color (颜色 in the block menu): class switches, colors.mjs.
|
|
14
|
+
// Its color (颜色 in the block menu): class switches, colors.mjs; its alignment and indent (对齐与缩进), align.mjs.
|
|
14
15
|
// Its look: the kit's styles/flow.css (src/render/kit/).
|
|
15
16
|
// How a heading of a level is written out: # … #### by level.
|
|
16
17
|
const writtenAs = (level) => ({ kind: "heading", level });
|
|
@@ -23,7 +24,7 @@ export default {
|
|
|
23
24
|
heading: {
|
|
24
25
|
name: t("标题"),
|
|
25
26
|
match: [{ rank: 190, tag: ["h1", "h2", "h3", "h4", "h5", "h6"] }],
|
|
26
|
-
colors: ["text", "background"],
|
|
27
|
+
colors: ["text", "background"], toggles: ALIGN_TOGGLES,
|
|
27
28
|
emptyIsNothing: true, fromWords: true,
|
|
28
29
|
clipboard: { of: (unit) => writtenAs(Math.min(4, Number(unit.localName.slice(1)) || 2)) }, // h5 and h6, which no type stands for, as heading 4
|
|
29
30
|
},
|
|
@@ -31,22 +32,22 @@ export default {
|
|
|
31
32
|
types: {
|
|
32
33
|
heading1: {
|
|
33
34
|
turn: 3, menu: ["basic", 3],
|
|
34
|
-
tag: "h1", kind: "heading", leaf: "heading1", text: true, empty: true, placeholder: t("标题 1"), clipboard: writtenAs(1),
|
|
35
|
+
tag: "h1", kind: "heading", leaf: "heading1", text: true, empty: true, emptyToText: true, placeholder: t("标题 1"), clipboard: writtenAs(1),
|
|
35
36
|
label: t("标题 1"), icon: headingIcon(1), hint: "#", markdown: [{ match: /^(#|#)[ \u00a0\u3000]$/ }], words: "标题1 一级标题 大标题 biaoti1 yiji bt1 bt heading1 h1 title",
|
|
36
37
|
},
|
|
37
38
|
heading2: {
|
|
38
39
|
turn: 4, menu: ["basic", 4],
|
|
39
|
-
tag: "h2", kind: "heading", leaf: "heading2", text: true, empty: true, placeholder: t("标题 2"), clipboard: writtenAs(2),
|
|
40
|
+
tag: "h2", kind: "heading", leaf: "heading2", text: true, empty: true, emptyToText: true, placeholder: t("标题 2"), clipboard: writtenAs(2),
|
|
40
41
|
label: t("标题 2"), icon: headingIcon(2), hint: "##", markdown: [{ match: /^(##|##)[ \u00a0\u3000]$/ }], words: "标题2 二级标题 biaoti2 erji bt2 bt heading2 h2",
|
|
41
42
|
},
|
|
42
43
|
heading3: {
|
|
43
44
|
turn: 5, menu: ["basic", 5],
|
|
44
|
-
tag: "h3", kind: "heading", leaf: "heading3", text: true, empty: true, placeholder: t("标题 3"), clipboard: writtenAs(3),
|
|
45
|
+
tag: "h3", kind: "heading", leaf: "heading3", text: true, empty: true, emptyToText: true, placeholder: t("标题 3"), clipboard: writtenAs(3),
|
|
45
46
|
label: t("标题 3"), icon: headingIcon(3), hint: "###", markdown: [{ match: /^(###|###)[ \u00a0\u3000]$/ }], words: "标题3 三级标题 小标题 biaoti3 sanji bt3 bt heading3 h3",
|
|
46
47
|
},
|
|
47
48
|
heading4: {
|
|
48
49
|
turn: 6, menu: ["basic", 6],
|
|
49
|
-
tag: "h4", kind: "heading", leaf: "heading4", text: true, empty: true, placeholder: t("标题 4"), clipboard: writtenAs(4),
|
|
50
|
+
tag: "h4", kind: "heading", leaf: "heading4", text: true, empty: true, emptyToText: true, placeholder: t("标题 4"), clipboard: writtenAs(4),
|
|
50
51
|
label: t("标题 4"), icon: headingIcon(4), hint: "####", markdown: [{ match: /^(####|####)[ \u00a0\u3000]$/ }], words: "标题4 四级标题 biaoti4 siji bt4 bt heading4 h4",
|
|
51
52
|
},
|
|
52
53
|
},
|
|
@@ -32,6 +32,9 @@ export const SECTION_ICON = svg('<path d="M6 3.75h-.75a2 2 0 0 0-2 2v8.5a2 2 0 0
|
|
|
32
32
|
*/
|
|
33
33
|
export const BLOCK_ICON = svg('<rect x="3.25" y="3.25" width="13.5" height="13.5" rx="2.5"/><path d="M6.75 7h3.5M6.75 10h6.5M6.75 13h4.5"/>');
|
|
34
34
|
|
|
35
|
+
/** 设置: a ring with eight short rays, the board's card menu and a custom block's handle menu. */
|
|
36
|
+
export const SETTINGS_ICON = svg('<circle cx="10" cy="10" r="2.25"/><path d="M10 3.25v1.5M10 15.25v1.5M3.25 10h1.5M15.25 10h1.5M5.2 5.2l1.06 1.06M13.74 13.74l1.06 1.06M5.2 14.8l1.06-1.06M13.74 6.26l1.06-1.06"/>');
|
|
37
|
+
|
|
35
38
|
/** A detail card (详情卡片): a card with two lines of text. */
|
|
36
39
|
export const DIALOG_ICON = svg('<rect x="3.25" y="3.75" width="13.5" height="12.5" rx="2.5"/><path d="M6.5 8h7M6.5 11.5h4.5"/>');
|
|
37
40
|
export const LINK_ICON = svg('<g transform="rotate(45 10 10)"><path d="M7 8.5V6a3 3 0 0 1 6 0v2.5M7 11.5V14a3 3 0 0 0 6 0v-2.5M10 7.5v5"/></g>'); // two U's and the line between them, turned 45°
|
|
@@ -36,6 +36,8 @@ export default {
|
|
|
36
36
|
name: t("图片"),
|
|
37
37
|
match: [{ rank: 45, tag: "figure", class: "doc-image" }],
|
|
38
38
|
boxed: true,
|
|
39
|
+
// Selected alone with its picture, its corner grips and toolbar show it: no selection fill (selection/index.mjs).
|
|
40
|
+
unfilled: ":scope > img",
|
|
39
41
|
toggles: ALIGN,
|
|
40
42
|
sizes: { [SPAN]: SPAN_SIZE },
|
|
41
43
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MEDIA_CLASS } from "../../../shared/components/index.mjs";
|
|
2
|
+
import { IN_LAYER } from "../../../shared/layers.mjs";
|
|
2
3
|
import { SPAN, SPAN_SIZE, UPLOAD_LIMITS, sizeLimit } from "../../../shared/media.mjs";
|
|
3
|
-
import { gestureLock } from "../../page/gesture.mjs";
|
|
4
|
+
import { cancelOnEscape, endsGesture, gestureLock } from "../../page/gesture.mjs";
|
|
4
5
|
import { openCrop } from "./crop.mjs";
|
|
5
6
|
import { openViewer } from "./viewer.mjs";
|
|
6
7
|
import { svg } from "../icons.mjs";
|
|
@@ -25,33 +26,68 @@ import { zoomable } from "../../../shared/zoom.mjs";
|
|
|
25
26
|
// a click (../media/page.mjs): a file (an image, 10 MB at most) goes to the report's folder, a link's picture is
|
|
26
27
|
// downloaded there by the server (an export never reaches the web); either is one undo step. The
|
|
27
28
|
// picture shows where the server serves the folder; what the file is stays in data-ge-src.
|
|
28
|
-
//
|
|
29
|
-
// the
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
29
|
+
// A click on a picture selects its image (`click`); while it is the one block selected (however it
|
|
30
|
+
// was: the click, its handle, the keys, a picture just put in) a short gray L stands at each of the
|
|
31
|
+
// picture's corners (the user's, 2026-11-09, feishu-lite's four corner handles drawn as Ls) and the
|
|
32
|
+
// toolbar at its top right, moved in clear of them (对齐, 标题, 展开, 更多: the block menu), both
|
|
33
|
+
// placed in the block tools' frames (`blockTools` follow, ../../page/blocks.mjs). Dragging a corner
|
|
34
|
+
// resizes the picture, its shape kept (feishu-lite's: by how far the pointer moves across, the left
|
|
35
|
+
// corners the other way, twice while centered), a share of the reading width, at most the width it
|
|
36
|
+
// stands in, snapping to the whole of that width (a share of 1: styles/sizes.css), one undo step on
|
|
37
|
+
// letting go (Esc puts it back). An L is a gray line with a thin edge of the page's color around it, so it
|
|
38
|
+
// reads on a black picture in light mode and a white one in dark mode (the old side bars, a gray of
|
|
39
|
+
// the page's ink, did not). 标题 shows its caption (empty ones show only then) with the caret in it;
|
|
40
|
+
// Enter there makes a paragraph after the image. A double-click on the picture (or on any other picture
|
|
41
|
+
// that opens large: a table's, a card's), or 展开, shows it alone over the page (a click or Esc closes
|
|
42
|
+
// it). The block menu has what the toolbar has not: 同步宽度到全局 first (every other image of the
|
|
43
|
+
// page as wide as this one, one undo step), 替换图片, 恢复原始大小, 裁剪图片 and 下载 (menuItems). A
|
|
44
|
+
// picture pasted (an image file on the clipboard) becomes an image below the block the caret is in
|
|
45
|
+
// (`acceptsFile`, `fromFiles`).
|
|
38
46
|
|
|
39
47
|
const MAX_BYTES = UPLOAD_LIMITS.image;
|
|
40
48
|
// 裁剪图片's row: a crop mark, drawn as the menus' other icons are.
|
|
41
49
|
const CROP_ICON = svg('<path d="M6 2.75V13a1 1 0 0 0 1 1h10.25"/><path d="M2.75 6H13a1 1 0 0 1 1 1v10.25"/>');
|
|
50
|
+
// 同步宽度到全局's row: a width (an arrow both ways) over two pictures as wide as each other.
|
|
51
|
+
const SYNC_ICON = svg('<path d="M4 4.25h12M6 2.5 4 4.25 6 6M14 2.5l2 1.75L14 6"/><rect x="3.75" y="8.25" width="12.5" height="3.5" rx="1"/><rect x="3.75" y="14" width="12.5" height="3.5" rx="1"/>');
|
|
52
|
+
|
|
53
|
+
// A corner's grip: a box of GRIP px, the picture's corner AT px in from the box's outer corner, an L
|
|
54
|
+
// whose lines run ARM px along the picture's two edges from its corner, centered a little outside them.
|
|
55
|
+
const GRIP = 24;
|
|
56
|
+
const AT = 8;
|
|
57
|
+
const ARM = 12;
|
|
58
|
+
const L_PATH = `M${AT - 1} ${AT - 1 + ARM}V${AT - 1}h${ARM}`; // drawn for the top left; the others mirror it
|
|
59
|
+
// The corners: which way each mirrors the top left's L, and its cursor.
|
|
60
|
+
const CORNERS = {
|
|
61
|
+
nw: { x: "left", y: "top", cursor: "nwse" },
|
|
62
|
+
ne: { x: "right", y: "top", cursor: "nesw" },
|
|
63
|
+
sw: { x: "left", y: "bottom", cursor: "nesw" },
|
|
64
|
+
se: { x: "right", y: "bottom", cursor: "nwse" },
|
|
65
|
+
};
|
|
66
|
+
const INSET = 16; // how far the toolbar stands from the picture's top and from its right edge, the same each way (the user's, 2026-11-09: the top as far as the right): clear of the L's arms and of the top right grip's box, which reaches GRIP - AT = 16px in
|
|
67
|
+
const GAP = 6; // how far it stands above the picture when it does not fit in
|
|
42
68
|
|
|
43
69
|
const STYLE = `
|
|
44
70
|
/* The placeholder of an image with no picture yet (Notion's 605415): every empty block's, served with the page (../placeholder.mjs). */
|
|
45
71
|
[data-ge-editing] .doc-image > img { cursor: default; }
|
|
46
|
-
/* The
|
|
47
|
-
.ge-layer > .ge-image-handle { pointer-events: auto; }
|
|
72
|
+
/* The toolbar is the media blocks' (../media/toolbar.mjs). */
|
|
48
73
|
.ge-image-align { display: flex; align-items: center; padding: 2px; border-radius: 6px; background: var(--c-popBac); box-shadow: var(--c-shaOutLg); }
|
|
49
74
|
.ge-image-align-sep { width: 1px; height: 16px; margin: 0 2px; background: var(--ca-borSecTra); }
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
/* The corners' grips: a short gray L, round at its ends, no edge round it (the user, 2026-11-09): a
|
|
76
|
+
neutral gray that reads on a black picture and a white one alike (4.5 to 1 on each), with a soft
|
|
77
|
+
shadow for a picture of its own gray; darker (lighter) under the pointer and while it is dragged.
|
|
78
|
+
No border, no fill. */
|
|
79
|
+
.ge-layer > .ge-image-grip { pointer-events: auto; z-index: ${IN_LAYER.button}; } /* over the image's fill: it is selected */
|
|
80
|
+
.ge-image-grip { width: ${GRIP}px; height: ${GRIP}px; --ge-grip-line: #767676; filter: drop-shadow(0 0 2px rgb(0 0 0 / 42%)); }
|
|
81
|
+
.ge-image-grip[data-cursor="nwse"] { cursor: nwse-resize; }
|
|
82
|
+
.ge-image-grip[data-cursor="nesw"] { cursor: nesw-resize; }
|
|
83
|
+
.ge-image-grip svg { display: block; width: ${GRIP}px; height: ${GRIP}px; fill: none; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
|
|
84
|
+
.ge-image-grip[data-corner="ne"] svg { transform: scaleX(-1); }
|
|
85
|
+
.ge-image-grip[data-corner="sw"] svg { transform: scaleY(-1); }
|
|
86
|
+
.ge-image-grip[data-corner="se"] svg { transform: scale(-1); }
|
|
87
|
+
.ge-image-grip-line { stroke: var(--ge-grip-line); stroke-width: 3; transition: stroke 120ms ease; }
|
|
88
|
+
.ge-image-grip:hover, .ge-image-grip[data-on] { --ge-grip-line: light-dark(#4f4d49, #d3d1cb); }
|
|
89
|
+
${gestureLock('data-ge-image-resize="nwse"', { cursor: "nwse-resize", select: true })}
|
|
90
|
+
${gestureLock('data-ge-image-resize="nesw"', { cursor: "nesw-resize", select: true })}
|
|
55
91
|
`;
|
|
56
92
|
|
|
57
93
|
const ALIGNS = [["left", t("左对齐"), "alignLeft"], ["center", t("居中对齐"), "alignCenter"], ["right", t("右对齐"), "alignRight"]];
|
|
@@ -156,103 +192,110 @@ export function create({ pages, assetUrl }) {
|
|
|
156
192
|
if (replacing && !quiet) say(t("已替换图片 · Ctrl+Z 撤销"));
|
|
157
193
|
}
|
|
158
194
|
|
|
159
|
-
// ---------- the toolbar and the
|
|
195
|
+
// ---------- the toolbar and the corners' grips ----------
|
|
160
196
|
|
|
161
|
-
let
|
|
162
|
-
let resizing = null; // { figure,
|
|
197
|
+
let shown = null; // the image whose tools show: the one block selected
|
|
198
|
+
let resizing = null; // { figure, corner, x, start, room, page, before, centered }
|
|
199
|
+
let aligning = null; // the 对齐 popover open, a press in which keeps the image selected
|
|
163
200
|
const bar = mediaBar(t("图片操作"));
|
|
164
|
-
const tool = (label, tip, icon, run) => toolButton({ label, tip, icon, run, alive: () =>
|
|
201
|
+
const tool = (label, tip, icon, run) => toolButton({ label, tip, icon, run, alive: () => shown?.isConnected });
|
|
165
202
|
const tools = {
|
|
166
|
-
align: tool(t("对齐方式"), t("设置区块对齐"), () => TOOL_ICONS[{ left: "alignLeft", center: "alignCenter", right: "alignRight" }[sideOf(
|
|
167
|
-
caption: tool(t("标题@caption"), t("添加标题"), TOOL_ICONS.caption, () => media.caption(
|
|
168
|
-
expand: tool(t("展开"), t("全屏查看"), TOOL_ICONS.expand, () => view(
|
|
203
|
+
align: tool(t("对齐方式"), t("设置区块对齐"), () => TOOL_ICONS[{ left: "alignLeft", center: "alignCenter", right: "alignRight" }[sideOf(shown)]], (button) => alignPopup(shown, button)),
|
|
204
|
+
caption: tool(t("标题@caption"), t("添加标题"), TOOL_ICONS.caption, () => media.caption(shown)),
|
|
205
|
+
expand: tool(t("展开"), t("全屏查看"), TOOL_ICONS.expand, () => view(shown)),
|
|
169
206
|
more: tool(t("更多"), t("打开区块操作菜单"), TOOL_ICONS.more, (button) => {
|
|
170
207
|
const rect = button.getBoundingClientRect();
|
|
171
|
-
openMenu(
|
|
208
|
+
openMenu(shown, { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom });
|
|
172
209
|
}),
|
|
173
210
|
};
|
|
174
211
|
bar.append(...Object.values(tools));
|
|
175
|
-
const
|
|
212
|
+
const lines = `<svg viewBox="0 0 ${GRIP} ${GRIP}" aria-hidden="true"><path class="ge-image-grip-line" d="${L_PATH}"/></svg>`;
|
|
213
|
+
const grips = Object.fromEntries(Object.entries(CORNERS).map(([corner, { cursor }]) => {
|
|
214
|
+
const grip = element("div", { class: "ge-image-grip", "data-corner": corner, "data-cursor": cursor, "aria-hidden": "true" });
|
|
215
|
+
grip.innerHTML = lines;
|
|
216
|
+
return [corner, grip];
|
|
217
|
+
}));
|
|
218
|
+
const nodes = () => [bar, ...Object.values(grips)];
|
|
176
219
|
|
|
177
|
-
// The toolbar and the
|
|
220
|
+
// The toolbar and the grips over the picture of `figure`, or away. The toolbar stands at the
|
|
221
|
+
// picture's top right, INSET from its top and from its right edge; on a picture too narrow to hold
|
|
222
|
+
// it with that room each side, above the picture, its right edge on the picture's.
|
|
178
223
|
function show(figure) {
|
|
179
|
-
|
|
224
|
+
shown = figure;
|
|
180
225
|
const img = figure && imageOf(figure);
|
|
181
226
|
const shape = img && shapeOf(img);
|
|
182
227
|
if (!shape) {
|
|
183
|
-
|
|
184
|
-
for (const node of
|
|
228
|
+
shown = null;
|
|
229
|
+
for (const node of nodes()) node.remove();
|
|
185
230
|
return;
|
|
186
231
|
}
|
|
187
|
-
const alive = () =>
|
|
188
|
-
for (const node of
|
|
232
|
+
const alive = () => shown === figure && figure.isConnected;
|
|
233
|
+
for (const node of nodes()) own(node, "image tools", { owner: figure, alive });
|
|
189
234
|
for (const button of Object.values(tools)) button.draw();
|
|
190
|
-
const { rect } = shape;
|
|
235
|
+
const { rect, layer } = shape;
|
|
191
236
|
tools.align.style.display = figure.style.getPropertyValue(SPAN).trim() === "1" ? "none" : ""; // a picture as wide as it can be has no side
|
|
192
|
-
if (bar.parentNode !==
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
237
|
+
if (bar.parentNode !== layer) layer.append(bar); // in the page before its width is measured
|
|
238
|
+
const fits = rect.right - rect.left >= bar.offsetWidth + 2 * INSET;
|
|
239
|
+
drawAt(bar, layer, fits ? rect.right - INSET - bar.offsetWidth : rect.right - bar.offsetWidth, fits ? rect.top + INSET : rect.top - AT - GAP - bar.offsetHeight);
|
|
240
|
+
for (const [corner, grip] of Object.entries(grips)) {
|
|
241
|
+
const { x, y } = CORNERS[corner];
|
|
242
|
+
drawAt(grip, layer, x === "left" ? rect.left - AT : rect.right - GRIP + AT, y === "top" ? rect.top - AT : rect.bottom - GRIP + AT);
|
|
197
243
|
}
|
|
198
244
|
}
|
|
199
245
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
246
|
+
// The image the tools stand for, from the blocks selected (the block tools' frame): one image with
|
|
247
|
+
// its picture, not one a component keeps; while a corner is dragged, the one dragged.
|
|
248
|
+
function follow(picked) {
|
|
249
|
+
if (resizing) return show(resizing.figure);
|
|
250
|
+
const [one] = picked.length === 1 ? picked : [];
|
|
251
|
+
const figure = one?.matches?.("figure.doc-image") && imageOf(one) && active() && !keptByComponent(one) ? one : null;
|
|
252
|
+
if (figure || shown) show(figure);
|
|
253
|
+
}
|
|
254
|
+
// The picture taken off the page by an undo, a redo or a commit: the tools go at once, not at the next frame.
|
|
255
|
+
onSettle(() => { if (shown && !shown.isConnected && !resizing) show(null); });
|
|
209
256
|
|
|
210
|
-
// Dragging a
|
|
211
|
-
// stands in (1 when it reaches that), live
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
257
|
+
// Dragging a corner: the picture's width as a share of the reading width, at most the width it
|
|
258
|
+
// stands in (1 when it reaches that), live, its height following (its shape kept); feishu-lite's
|
|
259
|
+
// rule (resize.ts): by how far the pointer moves across, the left corners the other way, twice while
|
|
260
|
+
// centered (it grows on both sides). One undo step on letting go; Esc, or the press cancelled, puts it back.
|
|
261
|
+
for (const [corner, grip] of Object.entries(grips)) {
|
|
262
|
+
grip.addEventListener("pointerdown", (event) => {
|
|
263
|
+
if (event.button !== 0 || !shown || resizing) return;
|
|
215
264
|
event.preventDefault();
|
|
216
|
-
const figure =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
document.documentElement.setAttribute("data-ge-image-resize",
|
|
265
|
+
const figure = shown;
|
|
266
|
+
resizing = { figure, corner, x: event.clientX, start: imageOf(figure).getBoundingClientRect().width, room: columnRoom(figure), page: readingRoom(figure), before: figure.style.getPropertyValue(SPAN), centered: sideOf(figure) === "center" };
|
|
267
|
+
grip.setPointerCapture?.(event.pointerId);
|
|
268
|
+
grip.setAttribute("data-on", "");
|
|
269
|
+
document.documentElement.setAttribute("data-ge-image-resize", CORNERS[corner].cursor);
|
|
221
270
|
});
|
|
222
|
-
|
|
223
|
-
if (
|
|
271
|
+
grip.addEventListener("pointermove", (event) => {
|
|
272
|
+
if (resizing?.corner !== corner) return;
|
|
224
273
|
const { figure, x, start, room, page, centered } = resizing;
|
|
225
|
-
const delta = (event.clientX - x) * (
|
|
274
|
+
const delta = (event.clientX - x) * (CORNERS[corner].x === "right" ? 1 : -1) * (centered ? 2 : 1);
|
|
226
275
|
const width = Math.max(Math.min(room, page * SPAN_SIZE.min), Math.min(room, start + delta));
|
|
227
276
|
figure.style.setProperty(SPAN, room - width <= SNAP ? "1" : String(Math.round((width / page) * 1000) / 1000));
|
|
228
277
|
show(figure);
|
|
229
278
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
resizing = null;
|
|
234
|
-
document.documentElement.removeAttribute("data-ge-image-resize");
|
|
235
|
-
const after = figure.style.getPropertyValue(SPAN);
|
|
236
|
-
if (before) figure.style.setProperty(SPAN, before);
|
|
237
|
-
else figure.style.removeProperty(SPAN);
|
|
238
|
-
if (keep && after !== before && look(figure, { sizes: { [SPAN]: after || null } })) say(t("图片宽度 {0}% · Ctrl+Z 撤销", Math.round(Number(after) * 100)));
|
|
239
|
-
show(figure);
|
|
240
|
-
};
|
|
241
|
-
handle.addEventListener("pointerup", () => end(true));
|
|
242
|
-
handle.addEventListener("pointercancel", () => end(false));
|
|
243
|
-
handle.addEventListener("lostpointercapture", () => end(true));
|
|
279
|
+
grip.addEventListener("pointerup", () => endResize(true));
|
|
280
|
+
grip.addEventListener("pointercancel", () => endResize(false));
|
|
281
|
+
grip.addEventListener("lostpointercapture", () => endResize(true));
|
|
244
282
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
event.stopPropagation();
|
|
283
|
+
// The drag over: the width it had back on the page, then (`keep`) the one dragged to as one undo step.
|
|
284
|
+
function endResize(keep) {
|
|
285
|
+
if (!resizing) return;
|
|
249
286
|
const { figure, before } = resizing;
|
|
250
287
|
resizing = null;
|
|
288
|
+
for (const grip of Object.values(grips)) grip.removeAttribute("data-on");
|
|
251
289
|
document.documentElement.removeAttribute("data-ge-image-resize");
|
|
290
|
+
const after = figure.style.getPropertyValue(SPAN);
|
|
252
291
|
if (before) figure.style.setProperty(SPAN, before);
|
|
253
292
|
else figure.style.removeProperty(SPAN);
|
|
293
|
+
if (keep && after !== before && look(figure, { sizes: { [SPAN]: after || null } })) say(t("图片宽度 {0}% · Ctrl+Z 撤销", Math.round(Number(after) * 100)));
|
|
254
294
|
show(figure);
|
|
255
|
-
}
|
|
295
|
+
}
|
|
296
|
+
// Esc gives the drag up and goes no further (the image stays selected); so do the window losing the focus and the page hidden.
|
|
297
|
+
cancelOnEscape({ active: () => Boolean(resizing), cancel: () => endResize(false) });
|
|
298
|
+
endsGesture({ active: () => Boolean(resizing), cancel: () => endResize(false), owns: () => false });
|
|
256
299
|
|
|
257
300
|
// 对齐: Notion's small popover of the three sides, under its button.
|
|
258
301
|
function alignPopup(figure, button) {
|
|
@@ -270,11 +313,29 @@ export function create({ pages, assetUrl }) {
|
|
|
270
313
|
});
|
|
271
314
|
const rect = button.getBoundingClientRect();
|
|
272
315
|
const popup = openPopup({ origin: { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }, placement: "bottom", align: "center", gap: 8, content: el });
|
|
316
|
+
aligning = el;
|
|
273
317
|
}
|
|
274
318
|
|
|
275
319
|
function align(figure, side) {
|
|
276
320
|
if (look(figure, sideLook(side))) say(t("已{0} · Ctrl+Z 撤销", ALIGNS.find(([each]) => each === side)[1]));
|
|
277
|
-
if (
|
|
321
|
+
if (shown === figure) show(figure);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 同步宽度到全局: every other image of the page (with its picture, not one a component keeps) as wide
|
|
325
|
+
// as this one shows, one undo step (undo selects this one again), written as one change. The width is
|
|
326
|
+
// what the picture shows at as a share of the reading width (styles/sizes.css), so a picture with no
|
|
327
|
+
// width of its own, or held narrower by its column, passes on the width it is seen at; the whole
|
|
328
|
+
// reading width is 1. This one is left as it is.
|
|
329
|
+
const others = (figure) => [...document.querySelectorAll("main figure.doc-image")].filter((each) => each !== figure && imageOf(each) && each.matches("[data-ge-unit], [data-ge-new]") && !keptByComponent(each));
|
|
330
|
+
function syncWidths(figure) {
|
|
331
|
+
const img = imageOf(figure);
|
|
332
|
+
if (!img?.isConnected) return;
|
|
333
|
+
const page = readingRoom(figure);
|
|
334
|
+
const width = img.getBoundingClientRect().width;
|
|
335
|
+
const share = page - width <= SNAP ? "1" : String(Math.max(SPAN_SIZE.min, Math.round((width / page) * 1000) / 1000));
|
|
336
|
+
const targets = others(figure).filter((each) => each.style.getPropertyValue(SPAN).trim() !== share);
|
|
337
|
+
if (targets.length === 0 || !look(targets, { sizes: { [SPAN]: share } }, figure)) return say(t("页面上的图片已经都是这个宽度。"));
|
|
338
|
+
say(t("已将 {0} 张图片设为同一宽度 · Ctrl+Z 撤销", targets.length));
|
|
278
339
|
}
|
|
279
340
|
|
|
280
341
|
// ---------- the viewer ----------
|
|
@@ -339,18 +400,18 @@ export function create({ pages, assetUrl }) {
|
|
|
339
400
|
}, true);
|
|
340
401
|
// ---------- the block menu, pasting ----------
|
|
341
402
|
|
|
342
|
-
/**
|
|
403
|
+
/**
|
|
404
|
+
* An image's rows in its block menu: what its toolbar has not (标题, 对齐 and 展开 are there, and the
|
|
405
|
+
* toolbar shows whenever the image is the one block selected, as it is while its menu is open):
|
|
406
|
+
* 同步宽度到全局 first (where the page has another picture), 替换图片, 恢复原始大小, 裁剪图片, 下载.
|
|
407
|
+
*/
|
|
343
408
|
function menuItems(target) {
|
|
344
409
|
if (!target?.matches?.("figure.doc-image") || !imageOf(target)) return [];
|
|
345
|
-
const rows = [
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
{ label: t("对齐"), icon: TOOL_ICONS[{ left: "alignLeft", center: "alignCenter", right: "alignRight" }[sideOf(target)]], width: 180,
|
|
349
|
-
submenu: () => ALIGNS.map(([side, label, name]) => ({ label, icon: TOOL_ICONS[name], current: side === sideOf(target), run: () => align(target, side) })) },
|
|
350
|
-
];
|
|
410
|
+
const rows = [];
|
|
411
|
+
if (others(target).length > 0) rows.push({ label: t("同步宽度到全局"), icon: SYNC_ICON, run: () => syncWidths(target) });
|
|
412
|
+
rows.push({ label: t("替换图片"), icon: ICONS.reset, run: () => setTimeout(() => pick(target)) });
|
|
351
413
|
if (target.style.getPropertyValue(SPAN)) rows.push({ label: t("恢复原始大小"), icon: ICONS.reset, run: () => { if (look(target, { sizes: { [SPAN]: null } })) say(t("已恢复原始大小 · Ctrl+Z 撤销")); } });
|
|
352
414
|
rows.push({ label: t("裁剪图片"), icon: CROP_ICON, run: () => setTimeout(() => crop(target)) });
|
|
353
|
-
rows.push({ label: t("全屏查看"), icon: TOOL_ICONS.expand, run: () => view(target) });
|
|
354
415
|
rows.push({ label: t("下载"), icon: TOOL_ICONS.download, run: () => download(imageOf(target)) });
|
|
355
416
|
return rows;
|
|
356
417
|
}
|
|
@@ -380,6 +441,27 @@ export function create({ pages, assetUrl }) {
|
|
|
380
441
|
pick(figure);
|
|
381
442
|
return true;
|
|
382
443
|
},
|
|
383
|
-
|
|
444
|
+
/**
|
|
445
|
+
* A click on an image's picture selects the image (its toolbar and grips then show: `blockTools`),
|
|
446
|
+
* the second of a double-click too (its press let go of it; the double-click itself is the
|
|
447
|
+
* viewer's). Returns whether it took the click.
|
|
448
|
+
*/
|
|
449
|
+
click(event) {
|
|
450
|
+
const img = event.target;
|
|
451
|
+
const figure = img?.localName === "img" ? img.parentElement : null;
|
|
452
|
+
if (event.button !== 0 || !figure?.matches("main figure.doc-image") || imageOf(figure) !== img || !active() || keptByComponent(figure)) return false;
|
|
453
|
+
media.select(figure);
|
|
454
|
+
return true;
|
|
455
|
+
},
|
|
456
|
+
/** The toolbar and the grips as a part of the block tools: placed in their frames by the blocks selected (`follow`), a press on them keeping the selection (`holds`). */
|
|
457
|
+
blockTools: () => [{
|
|
458
|
+
follow,
|
|
459
|
+
holds: (node) => nodes().some((each) => each.contains(node)) || Boolean(aligning?.isConnected && aligning.contains(node)),
|
|
460
|
+
hide() {
|
|
461
|
+
endResize(false);
|
|
462
|
+
show(null);
|
|
463
|
+
},
|
|
464
|
+
}],
|
|
465
|
+
get nodes() { return nodes().filter((node) => node.isConnected); },
|
|
384
466
|
};
|
|
385
467
|
}
|
|
@@ -2,7 +2,7 @@ import { escapeText } from "../../../shared/escape.mjs";
|
|
|
2
2
|
import { ATTRIBUTE, FALLBACK, INSTANCE, PACKAGE_ID, SLOT, SLOT_PART, componentAt, heldBy, instanceMatch, refusedInCopy, slotsIn } from "../../../shared/components/installed.mjs";
|
|
3
3
|
import { wordsIn } from "../../page/clipboard/own/writers.mjs";
|
|
4
4
|
import { BLOCK_ICON } from "../icons.mjs";
|
|
5
|
-
import { VIEW_FROM } from "../../../shared/table-chart.mjs";
|
|
5
|
+
import { CHART_KEY, VIEW_FROM } from "../../../shared/table-chart.mjs";
|
|
6
6
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
7
7
|
|
|
8
8
|
// 组件: the one kind of block every component package is to the engines, the host of a package's
|
|
@@ -109,6 +109,10 @@ export default {
|
|
|
109
109
|
boxed: true,
|
|
110
110
|
chapter: true,
|
|
111
111
|
copy: "itself",
|
|
112
|
+
// The key of the table a view reads (VIEW_FROM), which a view's 设置 changes on an instance the file
|
|
113
|
+
// has (the commit writes it in its start tag, as a table's CHART_SOURCE); one made on the page says it
|
|
114
|
+
// by `extras` below.
|
|
115
|
+
attributes: { [VIEW_FROM]: (value) => CHART_KEY.test(value) },
|
|
112
116
|
},
|
|
113
117
|
},
|
|
114
118
|
boxes: {
|
|
@@ -131,7 +135,7 @@ export default {
|
|
|
131
135
|
// Its markup is the file's, from its template or what was carried (`data-ge-copy`, its slots
|
|
132
136
|
// empty); what its slots hold goes apart, blocks made on the page as any (`children`, each in the
|
|
133
137
|
// slot `boxKeys` names), so that the commit's answer names each, as for a block made in a column.
|
|
134
|
-
// A view of a table (a
|
|
138
|
+
// A view of a table (a menu made it of its template, bound to the table above it, or a paste carried one) says the key
|
|
135
139
|
// of the table it reads as the page has it (`from`, its VIEW_FROM, which a paste may have given it
|
|
136
140
|
// anew: clipboard/own/chart-keys.mjs), which the file writes on it (file.mjs source); one carried
|
|
137
141
|
// with none on it says null (a view pasted into another document, its key taken off there), so
|