@heycys/gallery 0.1.1 → 0.1.2
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 +1 -1
- package/browser/editor/components/columns/page.mjs +4 -4
- package/browser/editor/components/embed/index.mjs +2 -2
- package/browser/editor/components/flowchart/add.mjs +7 -6
- package/browser/editor/components/flowchart/page.mjs +1 -1
- package/browser/editor/components/icons.mjs +8 -0
- package/browser/editor/components/image/crop.mjs +6 -9
- package/browser/editor/components/installed/index.mjs +11 -4
- package/browser/editor/components/installed/page.mjs +27 -16
- package/browser/editor/components/installed/served.mjs +6 -5
- package/browser/editor/components/instances.mjs +18 -0
- package/browser/editor/components/list/page.mjs +3 -6
- package/browser/editor/components/registry.mjs +13 -4
- package/browser/editor/components/table/index.mjs +59 -2
- package/browser/editor/components/table/page.mjs +1 -1
- package/browser/editor/page/annotate/card.mjs +3 -6
- package/browser/editor/page/autosave.mjs +310 -37
- package/browser/editor/page/board/client.mjs +57 -0
- package/browser/editor/page/board/hint.mjs +145 -0
- package/browser/editor/page/board/history.mjs +119 -0
- package/browser/editor/page/board/index.mjs +970 -0
- package/browser/editor/page/board/settings.mjs +209 -0
- package/browser/editor/page/caret.mjs +1 -1
- package/browser/editor/page/changes/bar.mjs +6 -14
- package/browser/editor/page/changes/index.mjs +2 -2
- package/browser/editor/page/clipboard/import/html.mjs +29 -6
- package/browser/editor/page/clipboard/import/inline.mjs +32 -0
- package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
- package/browser/editor/page/clipboard/import/read.mjs +6 -1
- package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
- package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
- package/browser/editor/page/clipboard/index.mjs +124 -13
- package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
- package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
- package/browser/editor/page/clipboard/own/selection.mjs +1 -1
- package/browser/editor/page/columns/index.mjs +48 -46
- package/browser/editor/page/commit.mjs +80 -22
- package/browser/editor/page/conflicts.mjs +7 -7
- package/browser/editor/page/core.mjs +31 -15
- package/browser/editor/page/custom-page.mjs +106 -0
- package/browser/editor/page/editor.mjs +51 -14
- package/browser/editor/page/emoji-picker/styles.mjs +7 -12
- package/browser/editor/page/emoji-picker/upload.mjs +2 -2
- package/browser/editor/page/handle/index.mjs +5 -8
- package/browser/editor/page/history.mjs +24 -4
- package/browser/editor/page/links/index.mjs +5 -3
- package/browser/editor/page/links/pages.mjs +27 -9
- package/browser/editor/page/look.mjs +54 -3
- package/browser/editor/page/margin.mjs +18 -3
- package/browser/editor/page/media-menu/index.mjs +6 -7
- package/browser/editor/page/menu.mjs +18 -7
- package/browser/editor/page/notices.mjs +2 -2
- package/browser/editor/page/page-objects.mjs +13 -2
- package/browser/editor/page/panel.mjs +4 -9
- package/browser/editor/page/present-links.mjs +11 -2
- package/browser/editor/page/resize/index.mjs +42 -29
- package/browser/editor/page/served.mjs +14 -0
- package/browser/editor/page/session.mjs +48 -4
- package/browser/editor/page/slash-menu/index.mjs +3 -3
- package/browser/editor/page/table/cells.mjs +102 -46
- package/browser/editor/page/table/icons.mjs +2 -0
- package/browser/editor/page/table/index.mjs +29 -10
- package/browser/editor/page/table/pictures.mjs +299 -0
- package/browser/editor/page/table/views.mjs +150 -0
- package/browser/editor/page/text-select/index.mjs +26 -12
- package/browser/editor/page/todo-writes.mjs +75 -0
- package/browser/editor/page/typing/index.mjs +53 -3
- package/browser/editor/page/ui.mjs +13 -0
- package/browser/shared/block-packs.mjs +404 -0
- package/browser/shared/board-places.mjs +224 -0
- package/browser/shared/card-size.mjs +59 -0
- package/browser/shared/colors.mjs +3 -2
- package/browser/shared/component-catalog.mjs +323 -46
- package/browser/shared/component-source.mjs +37 -0
- package/browser/shared/components/index.mjs +12 -3
- package/browser/shared/components/installed.mjs +35 -3
- package/browser/shared/components/page.mjs +18 -8
- package/browser/shared/components/state.mjs +5 -1
- package/browser/shared/document-look.mjs +10 -5
- package/browser/shared/drafts.mjs +432 -0
- package/browser/shared/extension-sidebar.mjs +126 -0
- package/browser/shared/extensions.mjs +79 -0
- package/browser/shared/file-table.mjs +101 -0
- package/browser/shared/i18n/en.mjs +307 -27
- package/browser/shared/json-layout.mjs +47 -0
- package/browser/shared/net-cache.mjs +133 -0
- package/browser/shared/net-keys.mjs +32 -0
- package/browser/shared/net-policy.mjs +177 -0
- package/browser/shared/outline-shape.mjs +128 -0
- package/browser/shared/outline.mjs +331 -67
- package/browser/shared/pack-manifest.mjs +77 -0
- package/browser/shared/page-address.mjs +6 -3
- package/browser/shared/page-cards.mjs +308 -0
- package/browser/shared/page-links.mjs +69 -6
- package/browser/shared/pages.mjs +388 -0
- package/browser/shared/paths.mjs +6 -0
- package/browser/shared/preview-server.mjs +3 -7
- package/browser/shared/session.mjs +8 -1
- package/browser/shared/shipped-blocks.mjs +299 -0
- package/browser/shared/table-chart.mjs +33 -1
- package/browser/shared/todos.mjs +83 -0
- package/browser/shared/user-directory.mjs +24 -0
- package/browser/shared/versions.mjs +14 -0
- package/browser/shared/web-links.mjs +134 -0
- package/browser/shared/workspace-columns.mjs +120 -0
- package/browser/shared/workspace-name.mjs +154 -0
- package/browser/shared/workspace-query.mjs +125 -0
- package/browser/shared/workspace-registry.mjs +272 -0
- package/browser/shared/workspace.mjs +171 -20
- package/dist/chunk-2NRBY6AJ.mjs +2 -0
- package/dist/chunk-32ZUEFZ7.mjs +2 -0
- package/dist/chunk-35NQ4PLQ.mjs +2 -0
- package/dist/chunk-3KJHLHUO.mjs +2 -0
- package/dist/chunk-3NJACR4W.mjs +2 -0
- package/dist/chunk-4AV456RQ.mjs +2 -0
- package/dist/chunk-4OZWXZE5.mjs +5 -0
- package/dist/chunk-5XYHDBJ5.mjs +2 -0
- package/dist/chunk-5ZJA7CXT.mjs +2 -0
- package/dist/chunk-6A53MZYZ.mjs +2 -0
- package/dist/chunk-6EXH3ORZ.mjs +166 -0
- package/dist/chunk-6EZ7FNIK.mjs +3 -0
- package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
- package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
- package/dist/chunk-B4P2CM2J.mjs +3 -0
- package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
- package/dist/chunk-BVEFGE4B.mjs +2 -0
- package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
- package/dist/chunk-CRJEG7MT.mjs +2 -0
- package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
- package/dist/chunk-D4EURAX4.mjs +4 -0
- package/dist/chunk-DQCE7TJY.mjs +5 -0
- package/dist/chunk-DR73Y2UJ.mjs +3 -0
- package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
- package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
- package/dist/chunk-EURLTOTZ.mjs +4 -0
- package/dist/chunk-FJYN7RTH.mjs +4 -0
- package/dist/chunk-FWNRPMCJ.mjs +12 -0
- package/dist/chunk-FWW4CSAZ.mjs +2 -0
- package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
- package/dist/chunk-GY3FAUVM.mjs +2 -0
- package/dist/chunk-HLYV7RIH.mjs +2 -0
- package/dist/chunk-HMYTM3GT.mjs +15 -0
- package/dist/chunk-IA4PSIMD.mjs +2 -0
- package/dist/chunk-JEUK3ZO7.mjs +2 -0
- package/dist/chunk-KTKHRC6H.mjs +2 -0
- package/dist/chunk-L7UP52JO.mjs +2 -0
- package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
- package/dist/chunk-N76HWBA3.mjs +10 -0
- package/dist/chunk-N7AZZLGT.mjs +548 -0
- package/dist/chunk-NAMN3RAF.mjs +2 -0
- package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
- package/dist/chunk-NG375ILQ.mjs +2 -0
- package/dist/chunk-NVRNLK25.mjs +6 -0
- package/dist/chunk-NWFBE2YA.mjs +2 -0
- package/dist/chunk-NWR2LT5B.mjs +2 -0
- package/dist/chunk-OJKMINIU.mjs +20 -0
- package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
- package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
- package/dist/chunk-PAREWQGB.mjs +4 -0
- package/dist/chunk-POQSE4C2.mjs +2 -0
- package/dist/chunk-PUOQ65NC.mjs +2 -0
- package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
- package/dist/chunk-PZDLJJTJ.mjs +3 -0
- package/dist/chunk-Q47PLKIJ.mjs +15 -0
- package/dist/chunk-Q4UEPUWF.mjs +2 -0
- package/dist/chunk-QKXMPUXC.mjs +2 -0
- package/dist/chunk-QOUEFUCH.mjs +16 -0
- package/dist/chunk-RFDZXFJF.mjs +13 -0
- package/dist/chunk-RKPWLGZ3.mjs +4 -0
- package/dist/chunk-RMECZA2K.mjs +2 -0
- package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
- package/dist/chunk-SX4LJYT6.mjs +8 -0
- package/dist/chunk-SXHBYZST.mjs +16 -0
- package/dist/chunk-TANZRINB.mjs +2 -0
- package/dist/chunk-TEZAWDSQ.mjs +2 -0
- package/dist/chunk-UBBRTJ2R.mjs +24 -0
- package/dist/chunk-UFNYFFKO.mjs +20 -0
- package/dist/chunk-UGWYKHBL.mjs +170 -0
- package/dist/chunk-UKSHDZZ6.mjs +2 -0
- package/dist/chunk-UNOYVIYR.mjs +2 -0
- package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
- package/dist/chunk-VJPFVNNW.mjs +15 -0
- package/dist/chunk-VS3ZMVGX.mjs +5 -0
- package/dist/chunk-VTKNEYT3.mjs +6 -0
- package/dist/chunk-VXACTLO3.mjs +3 -0
- package/dist/chunk-YI43RG4P.mjs +2 -0
- package/dist/chunk-YQGT4AB2.mjs +6 -0
- package/dist/chunk-ZB4H7ZN4.mjs +2 -0
- package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
- package/dist/chunk-ZTHICSSF.mjs +2 -0
- package/dist/chunk-ZUNVAXLH.mjs +2 -0
- package/dist/chunk-ZWPMCK7P.mjs +2 -0
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +95 -0
- package/kit/board.js +250 -0
- package/kit/charts.js +5 -71
- package/kit/code.js +14 -14
- package/kit/report.css +255 -17
- package/kit/runtime.js +1600 -125
- package/kit/state.js +10 -1
- package/kit/tables.js +128 -43
- package/kit/ui.js +77 -35
- package/package.json +2 -1
- package/packs/gallery/card/component.json +14 -0
- package/packs/gallery/card/example.html +10 -0
- package/packs/gallery/card/preview.svg +1 -0
- package/packs/gallery/card/style.css +6 -0
- package/packs/gallery/card/template.html +6 -0
- package/packs/gallery/card/writing.md +14 -0
- package/packs/gallery/chart-card/component.json +15 -0
- package/packs/gallery/chart-card/example.html +14 -0
- package/packs/gallery/chart-card/kit.js +130 -0
- package/packs/gallery/chart-card/preview.svg +1 -0
- package/packs/gallery/chart-card/style.css +42 -0
- package/packs/gallery/chart-card/template.html +13 -0
- package/packs/gallery/chart-card/writing.md +20 -0
- package/packs/gallery/chart-view/component.json +20 -0
- package/packs/gallery/chart-view/example.html +13 -0
- package/packs/gallery/chart-view/kit.js +355 -0
- package/packs/gallery/chart-view/preview.svg +1 -0
- package/packs/gallery/chart-view/style.css +94 -0
- package/packs/gallery/chart-view/template.html +3 -0
- package/packs/gallery/chart-view/writing.md +22 -0
- package/packs/gallery/countdown/component.json +18 -0
- package/packs/gallery/countdown/example.html +3 -0
- package/packs/gallery/countdown/kit.js +156 -0
- package/packs/gallery/countdown/preview.svg +1 -0
- package/packs/gallery/countdown/style.css +130 -0
- package/packs/gallery/countdown/template.html +3 -0
- package/packs/gallery/countdown/writing.md +22 -0
- package/packs/gallery/habit/component.json +14 -0
- package/packs/gallery/habit/example.html +3 -0
- package/packs/gallery/habit/kit.js +295 -0
- package/packs/gallery/habit/preview.svg +1 -0
- package/packs/gallery/habit/style.css +246 -0
- package/packs/gallery/habit/template.html +3 -0
- package/packs/gallery/habit/writing.md +21 -0
- package/packs/gallery/heatmap/component.json +18 -0
- package/packs/gallery/heatmap/example.html +3 -0
- package/packs/gallery/heatmap/kit.js +351 -0
- package/packs/gallery/heatmap/preview.svg +1 -0
- package/packs/gallery/heatmap/style.css +201 -0
- package/packs/gallery/heatmap/template.html +3 -0
- package/packs/gallery/heatmap/writing.md +21 -0
- package/packs/gallery/kanban/component.json +18 -0
- package/packs/gallery/kanban/example.html +12 -0
- package/packs/gallery/kanban/kit.js +540 -0
- package/packs/gallery/kanban/preview.svg +1 -0
- package/packs/gallery/kanban/style.css +212 -0
- package/packs/gallery/kanban/template.html +3 -0
- package/packs/gallery/kanban/writing.md +20 -0
- package/packs/gallery/metric/component.json +22 -0
- package/packs/gallery/metric/example.html +15 -0
- package/packs/gallery/metric/kit.js +248 -0
- package/packs/gallery/metric/preview.svg +1 -0
- package/packs/gallery/metric/style.css +92 -0
- package/packs/gallery/metric/template.html +3 -0
- package/packs/gallery/metric/writing.md +23 -0
- package/packs/gallery/note/component.json +14 -0
- package/packs/gallery/note/example.html +3 -0
- package/packs/gallery/note/kit.js +102 -0
- package/packs/gallery/note/preview.svg +1 -0
- package/packs/gallery/note/style.css +76 -0
- package/packs/gallery/note/template.html +3 -0
- package/packs/gallery/note/writing.md +18 -0
- package/packs/gallery/pack.json +8 -0
- package/packs/gallery/recent/component.json +13 -0
- package/packs/gallery/recent/example.html +1 -0
- package/packs/gallery/recent/kit.js +238 -0
- package/packs/gallery/recent/preview.svg +1 -0
- package/packs/gallery/recent/style.css +158 -0
- package/packs/gallery/recent/template.html +1 -0
- package/packs/gallery/recent/writing.md +15 -0
- package/packs/gallery/static/component.json +13 -0
- package/packs/gallery/static/example.html +11 -0
- package/packs/gallery/static/preview.svg +1 -0
- package/packs/gallery/static/style.css +138 -0
- package/packs/gallery/static/template.html +1 -0
- package/packs/gallery/static/writing.md +18 -0
- package/packs/gallery/stats/component.json +13 -0
- package/packs/gallery/stats/example.html +1 -0
- package/packs/gallery/stats/kit.js +166 -0
- package/packs/gallery/stats/preview.svg +1 -0
- package/packs/gallery/stats/style.css +151 -0
- package/packs/gallery/stats/template.html +1 -0
- package/packs/gallery/stats/writing.md +11 -0
- package/packs/gallery/today/component.json +12 -0
- package/packs/gallery/today/example.html +1 -0
- package/packs/gallery/today/kit.js +185 -0
- package/packs/gallery/today/preview.svg +1 -0
- package/packs/gallery/today/style.css +120 -0
- package/packs/gallery/today/template.html +1 -0
- package/packs/gallery/today/writing.md +14 -0
- package/packs/gallery/todos/component.json +19 -0
- package/packs/gallery/todos/example.html +3 -0
- package/packs/gallery/todos/kit.js +342 -0
- package/packs/gallery/todos/preview.svg +1 -0
- package/packs/gallery/todos/style.css +267 -0
- package/packs/gallery/todos/template.html +3 -0
- package/packs/gallery/todos/writing.md +30 -0
- package/packs/gallery/weather/component.json +19 -0
- package/packs/gallery/weather/example.html +3 -0
- package/packs/gallery/weather/kit.js +312 -0
- package/packs/gallery/weather/preview.svg +1 -0
- package/packs/gallery/weather/style.css +258 -0
- package/packs/gallery/weather/template.html +3 -0
- package/packs/gallery/weather/writing.md +26 -0
- package/packs/gallery-soft/pack.json +8 -0
- package/release.json +12 -10
- package/sdk/doc-kit.d.ts +396 -5
- package/sdk/doc-state.d.ts +2 -0
- package/skill/SKILL.md +56 -23
- package/skill/references/block-script.md +140 -0
- package/skill/references/building-blocks.md +257 -0
- package/skill/references/extensions.md +59 -0
- package/skill/references/pages-and-data.md +221 -0
- package/skill/references/writing-html.md +6 -4
- package/skill/sdk/doc-kit.d.ts +396 -5
- package/skill/sdk/doc-state.d.ts +2 -0
- package/web/assets/index-BK2e_07w.js +13 -0
- package/web/assets/index-CWFI83mF.css +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-3HIRPVLU.mjs +0 -2
- package/dist/chunk-5MHMDLZD.mjs +0 -4
- package/dist/chunk-64K7IPLO.mjs +0 -255
- package/dist/chunk-6QXGZM36.mjs +0 -8
- package/dist/chunk-6RGJOMJP.mjs +0 -16
- package/dist/chunk-AJ73XJF3.mjs +0 -2
- package/dist/chunk-AUZGSZJH.mjs +0 -13
- package/dist/chunk-BFE45AKL.mjs +0 -2
- package/dist/chunk-BXO2ZY5P.mjs +0 -2
- package/dist/chunk-CEH4MRCC.mjs +0 -2
- package/dist/chunk-CY2FNNAB.mjs +0 -16
- package/dist/chunk-DFUDV4LX.mjs +0 -2
- package/dist/chunk-DKXG4XDG.mjs +0 -2
- package/dist/chunk-EZQ34M3F.mjs +0 -124
- package/dist/chunk-EZY3WC6Z.mjs +0 -15
- package/dist/chunk-FUKQNOQS.mjs +0 -9
- package/dist/chunk-JPOSLGOC.mjs +0 -6
- package/dist/chunk-L3QKH4OK.mjs +0 -26
- package/dist/chunk-LAE5NQ4B.mjs +0 -2
- package/dist/chunk-LEF6DRCK.mjs +0 -59
- package/dist/chunk-NO5NJCHO.mjs +0 -2
- package/dist/chunk-NQPVAU63.mjs +0 -2
- package/dist/chunk-NSKUTQ5J.mjs +0 -5
- package/dist/chunk-O35FIXRB.mjs +0 -2
- package/dist/chunk-P2CAZPDP.mjs +0 -3
- package/dist/chunk-PL6GHLGD.mjs +0 -15
- package/dist/chunk-QJIR2BLP.mjs +0 -6
- package/dist/chunk-QUPLVPB2.mjs +0 -7
- package/dist/chunk-R7BNENLY.mjs +0 -4
- package/dist/chunk-S6O4UHLC.mjs +0 -2
- package/dist/chunk-SGJWQKEI.mjs +0 -2
- package/dist/chunk-TX6JH7TR.mjs +0 -2
- package/dist/chunk-TYK6VNAB.mjs +0 -10
- package/dist/chunk-UALRNVSE.mjs +0 -4
- package/dist/chunk-V3HXVQEM.mjs +0 -2
- package/dist/chunk-V5VXUJKG.mjs +0 -2
- package/dist/chunk-VBXMADMP.mjs +0 -2
- package/dist/chunk-VNV55RUA.mjs +0 -2
- package/dist/chunk-VPD2PS35.mjs +0 -2
- package/dist/chunk-W4VKN3GF.mjs +0 -7
- package/dist/chunk-W5BXXPDJ.mjs +0 -3
- package/dist/chunk-XUNHRCOG.mjs +0 -6
- package/skill/references/building-components.md +0 -367
- package/web/assets/index-Bh74DfsV.css +0 -1
- package/web/assets/index-UW0RyFFt.js +0 -13
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { escapeText } from "../../../shared/escape.mjs";
|
|
2
2
|
import { BLOCK_COLORS, cellColorOf, colorLook } from "../../components/colors.mjs";
|
|
3
|
-
import { CELLS_MIME as MIME, CELL_BOLD, OLD_CELLS_MIME,
|
|
3
|
+
import { CELLS_MIME as MIME, CELL_BOLD, OLD_CELLS_MIME, pictureFile } from "../../components/table/index.mjs";
|
|
4
4
|
import { alignItems, alignLook } from "./aligns.mjs";
|
|
5
5
|
import { insertColumn, insertRow, tableParts, tableSlots, tableState } from "../../components/table/page.mjs";
|
|
6
6
|
import { atEdge, editable } from "../caret.mjs";
|
|
7
7
|
import { pastedLines, serializeInner } from "../core.mjs";
|
|
8
8
|
import { formulaMaker, loadKatex } from "../formula/index.mjs";
|
|
9
9
|
import { FORMAT_KEYS } from "../format-bar/index.mjs";
|
|
10
|
-
import { formatRange, markValue } from "../marks.mjs";
|
|
10
|
+
import { formatRange, markValue, rangeAt, rangeOffsets } from "../marks.mjs";
|
|
11
11
|
import { gestureLock } from "../gesture.mjs";
|
|
12
12
|
import { addStyle } from "../ui.mjs";
|
|
13
13
|
import { createCellBar } from "./cell-bar.mjs";
|
|
@@ -31,9 +31,12 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
31
31
|
// a typed character goes at the end of the last cell, editing it; Ctrl+D fills down (copies rows or
|
|
32
32
|
// columns), Ctrl+R right; Ctrl+B / I / U / E and Ctrl+Shift+S format whole cells.
|
|
33
33
|
// - Copied, cells are the page's own (their inline HTML), an HTML table and lines of tab-separated
|
|
34
|
-
// text
|
|
35
|
-
//
|
|
36
|
-
//
|
|
34
|
+
// text (a cell in quotes holding its lines, as Excel writes one); pasted onto selected cells or with
|
|
35
|
+
// the caret in one, what the clipboard holds as cells (clipboard/ cellsOf: the page's own, another
|
|
36
|
+
// program's table with its marks and pictures, a Markdown table, tab-separated lines) fills them
|
|
37
|
+
// from the top left or the caret's cell (repeated over a selection a whole number of times larger),
|
|
38
|
+
// rows and columns put in where they overflow; one undo step. With the caret in a cell a single
|
|
39
|
+
// value is the editor's paste at the caret (but one holding a picture: pictures.mjs).
|
|
37
40
|
// A cell's color (one, as a block's: components/colors.mjs) and alignment are its classes (its look:
|
|
38
41
|
// components/table/index.mjs); its text changes are the editor's (struct, history.mjs). Where cells
|
|
39
42
|
// are merged (shared/table-slots.mjs), Tab goes from cell to cell, the arrows and Enter step
|
|
@@ -407,6 +410,17 @@ export function createCells(tools) {
|
|
|
407
410
|
|
|
408
411
|
// A cell's inline HTML as copied (a locked cell's words alone: what a script drew is not its text).
|
|
409
412
|
const htmlOf = (cell) => (writable(cell) ? serializeInner(cell) : escapeText(cell.textContent));
|
|
413
|
+
// A cell's words as text: its lines (its line breaks, its list's items) apart, a picture its alt.
|
|
414
|
+
const textOf = (cell) => {
|
|
415
|
+
const copy = cell.cloneNode(true);
|
|
416
|
+
copy.querySelectorAll("[data-doc-dev]").forEach((node) => node.remove());
|
|
417
|
+
copy.querySelectorAll("br").forEach((node) => node.replaceWith("\n"));
|
|
418
|
+
copy.querySelectorAll("li").forEach((node, index) => index > 0 && node.prepend("\n"));
|
|
419
|
+
copy.querySelectorAll("img").forEach((node) => node.replaceWith(node.getAttribute("alt") ?? ""));
|
|
420
|
+
return copy.textContent.replace(/[ \t]*\n[ \t]*/g, "\n").trim();
|
|
421
|
+
};
|
|
422
|
+
// A cell of tab-separated text as Excel writes one: in quotes when it holds a tab, a line break or a quote, its quotes doubled.
|
|
423
|
+
const tsvCell = (text) => (/[\t\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text);
|
|
410
424
|
|
|
411
425
|
function copy(event) {
|
|
412
426
|
const sel = tools.sel;
|
|
@@ -426,64 +440,108 @@ export function createCells(tools) {
|
|
|
426
440
|
}
|
|
427
441
|
}
|
|
428
442
|
const html = grid.map((row) => row.map((cell) => (cell ? htmlOf(cell) : "")));
|
|
429
|
-
|
|
443
|
+
// The report it was copied from: a paste in another one copies its pictures' files.
|
|
444
|
+
const payload = JSON.stringify({ v: 1, cells: html, report: document.querySelector(".doc-page")?.dataset.docId ?? null });
|
|
430
445
|
event.clipboardData.setData(MIME, payload);
|
|
431
446
|
event.clipboardData.setData(OLD_CELLS_MIME, payload); // a page of the old version (before 2026-10-26) reads this one
|
|
432
|
-
|
|
433
|
-
|
|
447
|
+
// For other programs (Excel, Google Sheets, another editor): an HTML table, its pictures where the
|
|
448
|
+
// server serves them (an address of this computer), and tab-separated text.
|
|
449
|
+
const shown = (cell) => (cell ? htmlOf(cell).replace(/<img src="(assets\/[^"]+)"/g, (all, file) => `<img src="${escapeText(new URL(cell.querySelector(`img[data-ge-src="${file}"]`)?.getAttribute("src") ?? file, location.href).href)}"`) : "");
|
|
450
|
+
event.clipboardData.setData("text/html", `<table>${grid.map((row) => `<tr>${row.map((cell) => `<td>${shown(cell)}</td>`).join("")}</tr>`).join("")}</table>`);
|
|
451
|
+
event.clipboardData.setData("text/plain", grid.map((row) => row.map((cell) => tsvCell(cell ? textOf(cell) : "")).join("\t")).join("\n"));
|
|
434
452
|
if (event.type === "cut") clearCells(tools, grid.flat().filter(Boolean));
|
|
435
453
|
else tools.say(t("已复制 {0} 格", grid.length * grid[0].length));
|
|
436
454
|
}
|
|
437
455
|
document.addEventListener("copy", copy, true);
|
|
438
456
|
document.addEventListener("cut", copy, true);
|
|
439
457
|
|
|
440
|
-
//
|
|
441
|
-
// the
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
} catch {
|
|
450
|
-
// not the page's own
|
|
451
|
-
}
|
|
452
|
-
const rows = tools.ownCells(data);
|
|
453
|
-
if (rows) return rows.map((row) => row.map((html) => ({ html })));
|
|
454
|
-
const html = data.getData("text/html");
|
|
455
|
-
const table = html ? new DOMParser().parseFromString(html, "text/html").querySelector("table") : null;
|
|
456
|
-
if (table?.rows.length) return [...table.rows].map((row) => [...row.cells].map((cell) => ({ text: cell.textContent.replace(/\s+/g, " ").trim() })));
|
|
457
|
-
const text = (data.getData("text/plain") || "").replace(/\r\n?/g, "\n").replace(/\n$/, "");
|
|
458
|
-
const lines = text.split("\n");
|
|
459
|
-
if (lines.length > 1) {
|
|
460
|
-
const width = lines[0].split("\t").length;
|
|
461
|
-
if (width > 1 && lines.every((line) => line.split("\t").length === width)) return lines.map((line) => line.split("\t").map((part) => ({ text: part })));
|
|
462
|
-
}
|
|
463
|
-
return text ? [[{ text: text.replace(/\s*\n\s*/g, " ") }]] : null;
|
|
458
|
+
// The cell the caret (or a selection within it) is in, where a paste goes (`target`: the event's,
|
|
459
|
+
// the cell or in it), in a table the tools manage, with where it is: { unit, at, cell }, or null.
|
|
460
|
+
function caretCell(target) {
|
|
461
|
+
const selection = getSelection();
|
|
462
|
+
const node = selection.rangeCount ? selection.getRangeAt(0).commonAncestorContainer : null;
|
|
463
|
+
const cell = (node?.nodeType === 1 ? node : node?.parentElement)?.closest?.("td, th");
|
|
464
|
+
const unit = cell && writable(cell) && cell.contains(target) ? tools.tableAt(cell) : null;
|
|
465
|
+
const at = unit ? tools.placeOf(tableSlots(unit), cell) : null;
|
|
466
|
+
return at ? { unit, at, cell } : null;
|
|
464
467
|
}
|
|
468
|
+
// Whether a pasted cell holds nothing: no words, no picture.
|
|
469
|
+
const blank = (html) => !html.includes("<img") && !html.replace(/<[^>]*>/g, "").replace(/ /g, " ").trim();
|
|
465
470
|
|
|
471
|
+
// A paste into the cells (notion-lite's 872952, the user's 2026-11-09: cells pasted with the caret in
|
|
472
|
+
// a cell are cells, not one cell's text): what the clipboard holds as cells (tools.cellsOf: the
|
|
473
|
+
// page's own, another program's table, a Markdown table, tab-separated lines, else its text) goes
|
|
474
|
+
// from the selected cells' top left, or the caret's cell. With the caret in a cell, a single value
|
|
475
|
+
// is the editor's paste at the caret (its marks, an address a link: clipboard/), but one holding a
|
|
476
|
+
// picture, which goes in at the caret here (its file copied first); with cells selected, it fills
|
|
477
|
+
// them (notion-lite's), as do picture files pasted on them (in the first one).
|
|
466
478
|
function paste(event) {
|
|
467
479
|
const sel = tools.sel;
|
|
468
|
-
if (!
|
|
480
|
+
if (!tools.active()) return;
|
|
481
|
+
const caret = !sel || sel.editing ? caretCell(event.target) : null;
|
|
482
|
+
if (!(caret || (sel && !sel.editing && sel.unit.isConnected))) return;
|
|
483
|
+
const data = event.clipboardData;
|
|
484
|
+
const pasted = data ? tools.cellsOf(data) : null;
|
|
485
|
+
const many = pasted && (pasted.rows.length > 1 || pasted.rows[0].length > 1);
|
|
486
|
+
const files = [...(data?.files ?? [])].filter((file) => /^image\//.test(file.type));
|
|
487
|
+
if (caret && !many && !(pasted?.rows[0][0].includes("<img") && !files.length)) return; // the editor's
|
|
469
488
|
event.preventDefault();
|
|
470
489
|
event.stopImmediatePropagation();
|
|
471
|
-
const
|
|
472
|
-
if (!
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
490
|
+
const target = caret ? { unit: caret.unit, type: "cell", anchor: caret.at, focus: caret.at, editing: false } : sel;
|
|
491
|
+
if (!pasted && !files.length) return;
|
|
492
|
+
const offsets = caret && !many ? rangeOffsets(caret.cell, getSelection().getRangeAt(0)) : null;
|
|
493
|
+
(async () => {
|
|
494
|
+
let rows = pasted?.rows;
|
|
495
|
+
let found = new Map();
|
|
496
|
+
if (!rows) {
|
|
497
|
+
// Picture files on selected cells: the first one's pictures.
|
|
498
|
+
const made = await tools.pictures.fromFiles(files);
|
|
499
|
+
if (!made.length) return;
|
|
500
|
+
rows = [[made.map((img) => `<img src="${pictureFile(img)}" alt="">`).join("")]];
|
|
501
|
+
made.forEach((img) => found.set(pictureFile(img), { path: pictureFile(img) }));
|
|
502
|
+
} else found = await tools.pictures.resolve(rows.flat(), pasted.from);
|
|
503
|
+
// Formulas in what is pasted are drawn at once, KaTeX loaded first where the page has none yet.
|
|
504
|
+
const formula = rows.some((row) => row.some((html) => html.includes("doc-math-inline"))) && (await loadKatex()) ? formulaMaker() : null;
|
|
505
|
+
const lines = (html) => {
|
|
506
|
+
const made = pastedLines(document, html, { links: true, formula, picture: tools.pictures.maker(found) });
|
|
507
|
+
return made.flatMap((line, index) => (index > 0 ? [document.createElement("br"), ...line] : line));
|
|
508
|
+
};
|
|
509
|
+
if (offsets) atCaret(caret, offsets, lines(rows[0][0]));
|
|
510
|
+
else pasteGrid(target, rows, lines);
|
|
511
|
+
})();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// A value holding a picture pasted at the caret in a cell (its file copied first): in at the caret,
|
|
515
|
+
// one undo step, the caret after it.
|
|
516
|
+
function atCaret({ unit, cell }, offsets, nodes) {
|
|
517
|
+
if (!cell.isConnected || nodes.length === 0) return;
|
|
518
|
+
let last = null;
|
|
519
|
+
const made = tools.change(unit, () => {
|
|
520
|
+
const range = rangeAt(cell, offsets);
|
|
521
|
+
range.deleteContents();
|
|
522
|
+
const fragment = document.createDocumentFragment();
|
|
523
|
+
fragment.append(...nodes);
|
|
524
|
+
last = nodes.at(-1);
|
|
525
|
+
range.insertNode(fragment);
|
|
526
|
+
return true;
|
|
527
|
+
}, null, [cell]);
|
|
528
|
+
if (made === null || !last?.isConnected) return;
|
|
529
|
+
const range = document.createRange();
|
|
530
|
+
range.setStartAfter(last);
|
|
531
|
+
range.collapse(true);
|
|
532
|
+
getSelection().removeAllRanges();
|
|
533
|
+
getSelection().addRange(range);
|
|
476
534
|
}
|
|
477
535
|
|
|
478
|
-
|
|
536
|
+
// `pasted`: rows of cells' inline HTML; `lines(html)`: a cell's nodes of one of them.
|
|
537
|
+
function pasteGrid(sel, pasted, lines) {
|
|
479
538
|
if (!sel.unit.isConnected) return;
|
|
480
539
|
let grid = pasted;
|
|
481
540
|
// Its empty last rows and columns go.
|
|
482
|
-
|
|
483
|
-
while (grid.length > 1 && grid.at(-1).every(empty)) grid = grid.slice(0, -1);
|
|
541
|
+
while (grid.length > 1 && grid.at(-1).every(blank)) grid = grid.slice(0, -1);
|
|
484
542
|
const width = Math.max(...grid.map((row) => row.length));
|
|
485
|
-
grid = grid.map((row) => [...row, ...Array.from({ length: width - row.length }, () =>
|
|
486
|
-
while (grid[0].length > 1 && grid.every((row) =>
|
|
543
|
+
grid = grid.map((row) => [...row, ...Array.from({ length: width - row.length }, () => "")]);
|
|
544
|
+
while (grid[0].length > 1 && grid.every((row) => blank(row.at(-1)))) grid = grid.map((row) => row.slice(0, -1));
|
|
487
545
|
const { unit } = sel;
|
|
488
546
|
const { r0, r1, c0, c1 } = tools.bounds(sel);
|
|
489
547
|
const times = [Math.max(1, Math.floor((r1 - r0 + 1) / grid.length)), Math.max(1, Math.floor((c1 - c0 + 1) / grid[0].length))];
|
|
@@ -504,9 +562,7 @@ export function createCells(tools) {
|
|
|
504
562
|
// A merged cell takes what goes in its top left slot.
|
|
505
563
|
const place = tools.placeOf(slotsNow, cell);
|
|
506
564
|
if (!cell || !writable(cell) || place[0] !== r0 + r || place[1] !== c0 + c) continue;
|
|
507
|
-
|
|
508
|
-
if (from.html !== undefined) cell.replaceChildren(...pastedLines(document, from.html, { links: true, formula }).flatMap((line, index) => (index > 0 ? [document.createTextNode(" "), ...line] : line)));
|
|
509
|
-
else cell.textContent = from.text;
|
|
565
|
+
cell.replaceChildren(...lines(grid[r % grid.length][c % grid[0].length]));
|
|
510
566
|
}
|
|
511
567
|
}
|
|
512
568
|
return true;
|
|
@@ -37,4 +37,6 @@ 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"/>'),
|
|
40
42
|
};
|
|
@@ -19,7 +19,9 @@ import { MENU_WIDTH } from "../type-grid.mjs";
|
|
|
19
19
|
import { addStyle, element } from "../ui.mjs";
|
|
20
20
|
import { alignItems, alignLook } from "./aligns.mjs";
|
|
21
21
|
import { clearCells, createCells } from "./cells.mjs";
|
|
22
|
+
import { createCellPictures } from "./pictures.mjs";
|
|
22
23
|
import { TABLE_ICONS } from "./icons.mjs";
|
|
24
|
+
import { createViews } from "./views.mjs";
|
|
23
25
|
import { createColumnWidths } from "./widths.mjs";
|
|
24
26
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
25
27
|
|
|
@@ -54,7 +56,8 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
54
56
|
// A table wider or taller than its frame scrolls in it: the tools stand by what the frame shows of
|
|
55
57
|
// it (the geometry's view), the grips and + points of the rows and columns in sight.
|
|
56
58
|
// Each change is one undo step; the page writes it as one operation of the table (grid.mjs). The
|
|
57
|
-
// keys and the cells' bar are cells.mjs's
|
|
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) and 视图 › of its menu, views.mjs's.
|
|
58
61
|
|
|
59
62
|
const STYLE = `
|
|
60
63
|
.ge-tgrip { display: flex; align-items: center; justify-content: center; cursor: grab; pointer-events: auto; }
|
|
@@ -78,7 +81,7 @@ const STYLE = `
|
|
|
78
81
|
.ge-tplus:hover > i { width: 18px; height: 18px; background: var(--ge-accent); box-shadow: 0 0 0 2px light-dark(#ffffff, #191919); }
|
|
79
82
|
.ge-tplus:hover > i > svg { display: block; }
|
|
80
83
|
.ge-tline { border-radius: 1px; background: var(--ge-accent); pointer-events: none; }
|
|
81
|
-
.ge-tgrow { box-sizing: border-box; display: grid; place-items: center; border:
|
|
84
|
+
.ge-tgrow { box-sizing: border-box; display: grid; place-items: center; border: 0; border-radius: 50%; background: var(--ge-bg); color: var(--ge-tx3); box-shadow: 0 1px 3px light-dark(rgb(25 25 25 / 18%), rgb(0 0 0 / 45%)); cursor: nwse-resize; pointer-events: auto; transition: background 80ms, color 80ms; }
|
|
82
85
|
.ge-tgrow:hover, .ge-tgrow[data-on] { background: light-dark(#f1f1ef, #2f2f2f); color: light-dark(#55534e, #c4c1bb); }
|
|
83
86
|
.ge-tgrow > svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
|
|
84
87
|
.ge-tframe { box-sizing: border-box; border: 2px solid var(--ge-accent); border-radius: 2px; background: color-mix(in srgb, var(--ge-accent) 9%, transparent); pointer-events: none; }
|
|
@@ -105,11 +108,15 @@ const REACH = { out: 12, in: 8 }; // the pointer this near the table's top (left
|
|
|
105
108
|
* active(): whether the tools may show. struct(parents, touched, mutate): the editor's undoable
|
|
106
109
|
* structural change; look(elements, change, select): a change of classes and sizes as one undo step. selectBlock(unit):
|
|
107
110
|
* select the table as a block (Esc). resizeTable(event, unit, axis): a drag of the table's width
|
|
108
|
-
* ("x") or scroll height ("y") from an armed edge (resize/). say: the status line.
|
|
109
|
-
*
|
|
111
|
+
* ("x") or scroll height ("y") from an armed edge (resize/). say: the status line. cellsOf(data): what
|
|
112
|
+
* the clipboard holds as rows of cells, { rows, from } (clipboard/ cellsOf), or null. upload(kind,
|
|
113
|
+
* body): a file sent to the report's folder (POST /__gallery/edit/upload), a picture of a cell's
|
|
114
|
+
* (pictures.mjs).
|
|
110
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). insertBelow(unit, key,
|
|
117
|
+
* { attributes, done }): a block of a menu's key put in below a block in one undo step (editor.mjs), what 视图 › makes.
|
|
111
118
|
*/
|
|
112
|
-
export function createTableEditing({ active, struct, look, selectBlock, resizeTable = () => false, say,
|
|
119
|
+
export function createTableEditing({ active, struct, look, selectBlock, resizeTable = () => false, say, cellsOf = () => null, upload = async () => { throw new Error("upload"); }, typedKey = () => false, recordStep = () => {}, insertBelow = () => {} }) {
|
|
113
120
|
addStyle(STYLE);
|
|
114
121
|
const menu = createMenu();
|
|
115
122
|
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)
|
|
@@ -266,7 +273,7 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
266
273
|
menuOpen: () => menu.isOpen,
|
|
267
274
|
typedKey,
|
|
268
275
|
resizeTable,
|
|
269
|
-
|
|
276
|
+
cellsOf,
|
|
270
277
|
dragging: () => drag !== null,
|
|
271
278
|
keys: (on) => {
|
|
272
279
|
keyboard = on;
|
|
@@ -274,6 +281,8 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
274
281
|
},
|
|
275
282
|
draw: () => draw(),
|
|
276
283
|
};
|
|
284
|
+
const pictures = createCellPictures({ active, recordStep, say, upload });
|
|
285
|
+
tools.pictures = pictures;
|
|
277
286
|
const cells = createCells(tools);
|
|
278
287
|
const widths = createColumnWidths(tools);
|
|
279
288
|
|
|
@@ -918,10 +927,15 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
918
927
|
|
|
919
928
|
addEventListener("scroll", () => { if (!drag) draw(); }, { passive: true, capture: true });
|
|
920
929
|
|
|
930
|
+
// What a view of a table asks, and 视图 › (views.mjs).
|
|
931
|
+
const views = createViews({ active, change, tableAt, recordStep, insertBelow, drawn: () => draw() });
|
|
932
|
+
|
|
921
933
|
// The rows of a table the tools manage. 一键优化 is a split row (menu.mjs): its words run it, its ›
|
|
922
|
-
// opens two of its parts to run alone, 自动调整列宽 and 识别数字列 (the user's, 2026-11-07)
|
|
934
|
+
// opens two of its parts to run alone, 自动调整列宽 and 识别数字列 (the user's, 2026-11-07); 视图 › last,
|
|
935
|
+
// where a custom block reads tables (views.mjs).
|
|
923
936
|
function toolRows(unit) {
|
|
924
937
|
const state = tableState(unit);
|
|
938
|
+
const view = views.menuRow(unit);
|
|
925
939
|
return [
|
|
926
940
|
{ label: t("标题行"), icon: ICONS.headerRow, checked: state.headerRow, run: () => headerRow(unit, state) },
|
|
927
941
|
{ label: t("标题列"), icon: ICONS.headerColumn, checked: state.headerColumn, run: () => change(unit, () => setHeaderColumn(unit, !state.headerColumn), state.headerColumn ? t("已取消标题列") : t("已设为标题列")) },
|
|
@@ -933,6 +947,7 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
933
947
|
],
|
|
934
948
|
},
|
|
935
949
|
{ label: t("均分列宽"), icon: ICONS.even, run: () => widths.even(unit) },
|
|
950
|
+
...(view ? [view] : []),
|
|
936
951
|
];
|
|
937
952
|
}
|
|
938
953
|
|
|
@@ -958,7 +973,10 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
958
973
|
menu.close();
|
|
959
974
|
clear();
|
|
960
975
|
cells.update();
|
|
976
|
+
pictures.hide();
|
|
961
977
|
},
|
|
978
|
+
/** The pictures of cells (pictures.mjs): files pasted into a cell, those of pasted cells made the folder's. */
|
|
979
|
+
pictures,
|
|
962
980
|
/** The table whose cells, rows or columns are selected (no caret in one of them), or null: Ctrl+A's step to the table whole (select-all/). */
|
|
963
981
|
get selectedTable() { return sel && !sel.editing && sel.unit.isConnected ? sel.unit : null; },
|
|
964
982
|
/** The selection let go (blocks selected instead: select-all/). */
|
|
@@ -973,14 +991,15 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
|
|
|
973
991
|
return Boolean(unit) && /^(TD|TH)$/.test(from.tagName) && /^(TD|TH)$/.test(to?.tagName ?? "") && tableAt(to) === unit;
|
|
974
992
|
},
|
|
975
993
|
/** The editor's UI nodes to keep in the top layer when a modal dialog opens. */
|
|
976
|
-
get nodes() { return [...nodes, ...cells.nodes]; },
|
|
994
|
+
get nodes() { return [...nodes, ...cells.nodes, ...pictures.nodes]; },
|
|
977
995
|
/** Whether a node is one of the tools' (a grip, an edge's strip, the corner's +, the cells' bar). */
|
|
978
|
-
holds: (node) => Boolean(node) && [...nodes, ...cells.nodes].some((each) => each.contains(node)),
|
|
996
|
+
holds: (node) => Boolean(node) && [...nodes, ...cells.nodes, ...pictures.nodes].some((each) => each.contains(node)),
|
|
979
997
|
/**
|
|
980
998
|
* A table's rows of its block menu (notion-lite's 表格 section, under the menu's one title): a
|
|
981
999
|
* table of the kind these tools manage has 标题行 and 标题列, switches (checked when on); then
|
|
982
1000
|
* actions, each applied once (no check): 一键优化 › (自动调整列宽, 识别数字列 in its submenu),
|
|
983
|
-
*
|
|
1001
|
+
* 均分列宽, 视图 › (where a custom block reads tables: views.mjs); any table in its frame has its
|
|
1002
|
+
* size's rows after them (layoutRows). [] for any other block.
|
|
984
1003
|
*/
|
|
985
1004
|
menuItems(target) {
|
|
986
1005
|
if (!target?.matches?.('[data-ge-unit-kind="table"]')) return [];
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { UPLOAD_LIMITS, mediaUrl, sizeLimit } from "../../../shared/media.mjs";
|
|
2
|
+
import { cellPicture, pictureFile } from "../../components/table/index.mjs";
|
|
3
|
+
import { ownHTML } from "../core.mjs";
|
|
4
|
+
import { gestureLock } from "../gesture.mjs";
|
|
5
|
+
import { drawAt, drawBox, layerOf } from "../overlay-layer.mjs";
|
|
6
|
+
import { onSettle, own } from "../overlays.mjs";
|
|
7
|
+
import { blobBase64 } from "../post.mjs";
|
|
8
|
+
import { addStyle, element } from "../ui.mjs";
|
|
9
|
+
import { t } from "../../../shared/i18n/index.mjs";
|
|
10
|
+
|
|
11
|
+
// 单元格里的图片 (the user's, 2026-11-09: no image block, a light one; Markdown tables hold pictures):
|
|
12
|
+
// an <img> amid a table cell's words, a picture of the document's folder (components/table/ cellPicture; the
|
|
13
|
+
// file takes it as source/blocks.mjs cellPictureProblem says, the kit lays it out: table.css). notion-
|
|
14
|
+
// lite has none; this is the plainest that works, flat:
|
|
15
|
+
// - A press on one selects it (the selection around it, a 2px blue frame over it, no caret): Delete
|
|
16
|
+
// or Backspace then takes it out, a typed character takes its place, Esc puts the caret after it;
|
|
17
|
+
// the arrows go past it as past a character (the browser's: an <img> in an editable text is one).
|
|
18
|
+
// - A gray bar at its right edge (the image block's handle) under the pointer or while it is
|
|
19
|
+
// selected: dragged, the picture's width in pixels (its height by its ratio), at least MIN_WIDTH,
|
|
20
|
+
// at most its table's frame, written as its width attribute; one undo step on letting go, Esc
|
|
21
|
+
// putting it back.
|
|
22
|
+
// - Where pictures come from (clipboard/, table/cells.mjs): a picture file pasted into a cell (a
|
|
23
|
+
// screenshot, a copied picture) sent to the document's folder as an image block's (fromFiles); the
|
|
24
|
+
// pictures of cells pasted (another program's table, a Markdown table, the page's own cells of
|
|
25
|
+
// another document) each made a file of the folder first (resolve: one of the folder as it is,
|
|
26
|
+
// another document's, a data: one and a web one copied in, the server downloading a web one as it
|
|
27
|
+
// does an image block's link), then put in by what pastedLines makes of each (maker): a picture of
|
|
28
|
+
// no file a link to it (a web one) or its alt words.
|
|
29
|
+
|
|
30
|
+
const MIN_WIDTH = 16;
|
|
31
|
+
const MAX_PICTURES = 50; // the pictures a paste copies in at most (each a download)
|
|
32
|
+
const WIDTH = /^[1-9]\d{0,3}$/;
|
|
33
|
+
const WEB = /^https?:\/\/[^\s"<>]+$/i;
|
|
34
|
+
const DATA = /^data:image\/[a-z0-9.+-]+;base64,/i;
|
|
35
|
+
// A file of a document's folder as the preview serves it (a copy of the page's HTML), or as a cell's text writes it.
|
|
36
|
+
const SERVED = /^(?:https?:\/\/[^/]+)?\/__gallery\/edit\/asset\/([^/?#]+)\/(assets\/[^/?#]+)$/;
|
|
37
|
+
const FOLDER_FILE = /^assets\/[A-Za-z0-9][A-Za-z0-9._-]{0,80}$/;
|
|
38
|
+
const CELL = '[data-ge-kind="cell"]';
|
|
39
|
+
// The name of a picture's file in its address (its last part, its escapes read).
|
|
40
|
+
const nameOf = (src) => {
|
|
41
|
+
const name = src.split(/[?#]/)[0].split("/").pop() || src;
|
|
42
|
+
try {
|
|
43
|
+
return decodeURIComponent(name);
|
|
44
|
+
} catch {
|
|
45
|
+
return name;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const STYLE = `
|
|
50
|
+
[data-ge-editing] ${CELL}[contenteditable="true"] img { cursor: default; }
|
|
51
|
+
.ge-layer > .ge-cellpic-handle { pointer-events: auto; }
|
|
52
|
+
.ge-cellpic-frame { box-sizing: border-box; border: 2px solid var(--ge-accent); border-radius: 2px; pointer-events: none; }
|
|
53
|
+
/* The image block's handle (image/page.mjs): a plain gray bar, no border. */
|
|
54
|
+
.ge-cellpic-handle { display: flex; align-items: center; justify-content: center; width: 12px; cursor: col-resize; }
|
|
55
|
+
.ge-cellpic-handle::after { content: ""; width: 4px; height: 32px; max-height: 70%; border-radius: 4px; background: light-dark(rgb(55 53 47 / 35%), rgb(255 255 255 / 38%)); transition: background 120ms ease; }
|
|
56
|
+
.ge-cellpic-handle:hover::after, html[data-ge-cellpic-resize] .ge-cellpic-handle::after { background: light-dark(rgb(55 53 47 / 60%), rgb(255 255 255 / 62%)); }
|
|
57
|
+
${gestureLock("data-ge-cellpic-resize", { cursor: "col-resize" })}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
// The report this page shows (its main's data-doc-id: its folder's name).
|
|
61
|
+
const thisReport = () => document.querySelector(".doc-page")?.dataset.docId ?? null;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* active(): whether the tools may act. recordStep(host, before): a text step of a text block
|
|
65
|
+
* (history.mjs). say: the status line. upload(kind, body): POST /__gallery/edit/upload, the server's
|
|
66
|
+
* answer (an Error when it fails).
|
|
67
|
+
*/
|
|
68
|
+
export function createCellPictures({ active, recordStep, say, upload }) {
|
|
69
|
+
addStyle(STYLE);
|
|
70
|
+
let hovered = null; // the picture under the pointer
|
|
71
|
+
let picked = null; // the picture selected
|
|
72
|
+
let drag = null; // its edge dragged: { img, host, x, start, before, had }
|
|
73
|
+
const frame = element("div", { class: "ge-cellpic-frame", "data-doc-dev": "" });
|
|
74
|
+
const handle = element("div", { class: "ge-cellpic-handle", "data-doc-dev": "" });
|
|
75
|
+
// The picture they are drawn for: the frame the selected one's (or the dragged one's), the bar any of them.
|
|
76
|
+
const framed = () => drag?.img ?? picked;
|
|
77
|
+
const barred = () => drag?.img ?? picked ?? hovered;
|
|
78
|
+
own(frame, "cell picture", { alive: () => Boolean(framed()?.isConnected) });
|
|
79
|
+
own(handle, "cell picture", { alive: () => Boolean(barred()?.isConnected) });
|
|
80
|
+
// The cell of the picture drawn for, watched: a picture taken out (Delete, undo, someone else's
|
|
81
|
+
// words merged) takes them away at once, before the page draws again.
|
|
82
|
+
const watch = new MutationObserver(() => {
|
|
83
|
+
if (!barred()?.isConnected) draw();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// A picture of a cell the reader writes in, and that cell; or null.
|
|
87
|
+
const pictureAt = (node) => {
|
|
88
|
+
const img = node?.closest?.("img");
|
|
89
|
+
const host = img?.closest(CELL);
|
|
90
|
+
return img && host?.getAttribute("contenteditable") === "true" && host.closest("main") && !host.closest("[data-ge-lock], [inert]") && pictureFile(img) ? { img, host } : null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function draw() {
|
|
94
|
+
if (picked && !picked.isConnected) picked = null;
|
|
95
|
+
if (hovered && !hovered.isConnected) hovered = null;
|
|
96
|
+
const img = barred();
|
|
97
|
+
watch.disconnect();
|
|
98
|
+
if (!img?.isConnected || !active()) {
|
|
99
|
+
frame.remove();
|
|
100
|
+
handle.remove();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
watch.observe(img.closest(CELL) ?? img.parentNode, { childList: true, subtree: true });
|
|
104
|
+
const layer = layerOf(img);
|
|
105
|
+
const rect = img.getBoundingClientRect();
|
|
106
|
+
if (img === framed()) drawBox(frame, layer, rect);
|
|
107
|
+
else frame.remove();
|
|
108
|
+
const origin = layer.getBoundingClientRect();
|
|
109
|
+
handle.style.height = `${Math.max(20, Math.round(rect.height))}px`;
|
|
110
|
+
drawAt(handle, layer, rect.right - origin.left - 2, rect.top - origin.top + Math.min(0, (rect.height - 20) / 2));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Whether the selection is the picture alone (a press on it, or the browser's own).
|
|
114
|
+
const around = (img) => {
|
|
115
|
+
const selection = getSelection();
|
|
116
|
+
if (!selection.rangeCount || !img.parentNode) return false;
|
|
117
|
+
const range = selection.getRangeAt(0);
|
|
118
|
+
const at = [...img.parentNode.childNodes].indexOf(img);
|
|
119
|
+
return range.startContainer === img.parentNode && range.endContainer === img.parentNode && range.startOffset === at && range.endOffset === at + 1;
|
|
120
|
+
};
|
|
121
|
+
function pick({ img, host }) {
|
|
122
|
+
if (document.activeElement !== host) host.focus({ preventScroll: true });
|
|
123
|
+
const range = document.createRange();
|
|
124
|
+
range.selectNode(img);
|
|
125
|
+
getSelection().removeAllRanges();
|
|
126
|
+
getSelection().addRange(range);
|
|
127
|
+
picked = img;
|
|
128
|
+
draw();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------- the pointer ----------
|
|
132
|
+
|
|
133
|
+
document.addEventListener("pointermove", (event) => {
|
|
134
|
+
if (drag) return;
|
|
135
|
+
const at = pictureAt(event.target);
|
|
136
|
+
const next = at ? at.img : event.target === handle ? hovered : null;
|
|
137
|
+
if (next !== hovered) {
|
|
138
|
+
hovered = next;
|
|
139
|
+
draw();
|
|
140
|
+
}
|
|
141
|
+
}, { passive: true });
|
|
142
|
+
// A press on a picture selects it, before the cells' tools (a drag between cells) and the caret hear it.
|
|
143
|
+
window.addEventListener("pointerdown", (event) => {
|
|
144
|
+
const at = event.button === 0 && active() ? pictureAt(event.target) : null;
|
|
145
|
+
if (!at) return;
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
event.stopImmediatePropagation();
|
|
148
|
+
pick(at);
|
|
149
|
+
}, true);
|
|
150
|
+
window.addEventListener("mousedown", (event) => {
|
|
151
|
+
if (event.button === 0 && active() && (pictureAt(event.target) || event.target === handle)) event.preventDefault(); // no caret, no drag of the picture
|
|
152
|
+
}, true);
|
|
153
|
+
document.addEventListener("selectionchange", () => {
|
|
154
|
+
if (picked && !(picked.isConnected && around(picked))) {
|
|
155
|
+
picked = null;
|
|
156
|
+
draw();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
document.addEventListener("keydown", (event) => {
|
|
160
|
+
if (event.key !== "Escape" || event.isComposing) return;
|
|
161
|
+
if (drag) return void (event.preventDefault(), event.stopImmediatePropagation(), end(false));
|
|
162
|
+
if (!picked || !around(picked)) return;
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
event.stopImmediatePropagation();
|
|
165
|
+
const range = document.createRange();
|
|
166
|
+
range.setStartAfter(picked);
|
|
167
|
+
range.collapse(true);
|
|
168
|
+
getSelection().removeAllRanges();
|
|
169
|
+
getSelection().addRange(range);
|
|
170
|
+
}, true);
|
|
171
|
+
for (const type of ["scroll", "resize"]) window.addEventListener(type, () => { if (drag || picked || hovered) draw(); }, { capture: true, passive: true });
|
|
172
|
+
onSettle(draw);
|
|
173
|
+
|
|
174
|
+
// ---------- its width ----------
|
|
175
|
+
|
|
176
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
177
|
+
const img = picked ?? hovered;
|
|
178
|
+
const host = img?.closest(CELL);
|
|
179
|
+
if (event.button !== 0 || !host || !active()) return;
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
event.stopPropagation();
|
|
182
|
+
handle.setPointerCapture(event.pointerId);
|
|
183
|
+
drag = { img, host, x: event.clientX, start: img.getBoundingClientRect().width, before: ownHTML(host), had: img.getAttribute("width") };
|
|
184
|
+
document.documentElement.setAttribute("data-ge-cellpic-resize", "");
|
|
185
|
+
});
|
|
186
|
+
handle.addEventListener("pointermove", (event) => {
|
|
187
|
+
if (!drag) return;
|
|
188
|
+
const room = drag.host.closest(".doc-table-wrap")?.clientWidth || 2000; // never wider than its table's frame
|
|
189
|
+
drag.img.setAttribute("width", String(Math.round(Math.max(MIN_WIDTH, Math.min(room, drag.start + event.clientX - drag.x)))));
|
|
190
|
+
draw();
|
|
191
|
+
});
|
|
192
|
+
function end(keep) {
|
|
193
|
+
if (!drag) return;
|
|
194
|
+
const { img, host, before, had } = drag;
|
|
195
|
+
drag = null;
|
|
196
|
+
document.documentElement.removeAttribute("data-ge-cellpic-resize");
|
|
197
|
+
if (!keep) {
|
|
198
|
+
if (had === null) img.removeAttribute("width");
|
|
199
|
+
else img.setAttribute("width", had);
|
|
200
|
+
} else if (img.getAttribute("width") !== had) recordStep(host, before);
|
|
201
|
+
draw();
|
|
202
|
+
}
|
|
203
|
+
handle.addEventListener("pointerup", () => end(true));
|
|
204
|
+
handle.addEventListener("pointercancel", () => end(false));
|
|
205
|
+
|
|
206
|
+
// ---------- pictures pasted ----------
|
|
207
|
+
|
|
208
|
+
const sent = async (blob, name) => (await upload("image", { name, type: blob.type, data: await blobBase64(blob) })).path;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Picture files pasted into a cell (a screenshot, a copied picture), each sent to the document's
|
|
212
|
+
* folder as an image block's (10 MB at most): the cell's pictures, in order, those that could not
|
|
213
|
+
* go said.
|
|
214
|
+
*/
|
|
215
|
+
async function fromFiles(files) {
|
|
216
|
+
const pictures = [...files].filter((file) => /^image\//.test(file.type));
|
|
217
|
+
if (pictures.some((file) => file.size > UPLOAD_LIMITS.image)) say(t("图片太大(最多 {0})。", sizeLimit("image")), "error");
|
|
218
|
+
const made = await Promise.all(pictures.filter((file) => file.size <= UPLOAD_LIMITS.image).map(async (file) => {
|
|
219
|
+
try {
|
|
220
|
+
return cellPicture(document, await sent(file, file.name || `image.${file.type.split("/")[1] ?? "png"}`), { alt: "" });
|
|
221
|
+
} catch (error) {
|
|
222
|
+
say(t("{0}:{1}", t("无法上传这张图片"), error.message), "error");
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}));
|
|
226
|
+
return made.filter(Boolean);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// A pasted picture's address as a file of this document's folder ({ path }), or null.
|
|
230
|
+
async function fileOf(src, from, here) {
|
|
231
|
+
const served = SERVED.exec(src);
|
|
232
|
+
const [report, file] = served ? [decodeURIComponent(served[1]), served[2]] : FOLDER_FILE.test(src) ? [from ?? here, src] : [null, null];
|
|
233
|
+
if (file && report === here) return { path: file };
|
|
234
|
+
const copied = async (address, name) => {
|
|
235
|
+
const response = await fetch(address);
|
|
236
|
+
const blob = response.ok ? await response.blob() : null;
|
|
237
|
+
return blob && /^image\//.test(blob.type) && blob.size <= UPLOAD_LIMITS.image ? { path: await sent(blob, name) } : null;
|
|
238
|
+
};
|
|
239
|
+
if (file && report) return copied(mediaUrl("asset", encodeURIComponent(report), file), file.slice("assets/".length));
|
|
240
|
+
if (DATA.test(src)) return copied(src, "image");
|
|
241
|
+
if (WEB.test(src)) return { path: (await upload("image", { url: src })).path };
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The pictures of pasted cells (`htmls`: their inline HTML) as files of this document's folder: a
|
|
247
|
+
* Map of each address to { path } (assets/<file>), or null where none could be had. `from`: the
|
|
248
|
+
* report the page's own cells came from (their assets/ that report's).
|
|
249
|
+
*/
|
|
250
|
+
async function resolve(htmls, from = null) {
|
|
251
|
+
const addresses = new Set();
|
|
252
|
+
const template = document.createElement("template");
|
|
253
|
+
for (const html of htmls) {
|
|
254
|
+
if (!html.includes("<img")) continue;
|
|
255
|
+
template.innerHTML = html;
|
|
256
|
+
for (const img of template.content.querySelectorAll("img[src]")) addresses.add(img.getAttribute("src"));
|
|
257
|
+
}
|
|
258
|
+
const found = new Map();
|
|
259
|
+
if (addresses.size === 0) return found;
|
|
260
|
+
const here = thisReport();
|
|
261
|
+
const list = [...addresses];
|
|
262
|
+
if (list.some((src) => !(SERVED.exec(src)?.[1] === here || (FOLDER_FILE.test(src) && (from ?? here) === here)))) say(t("正在下载…"));
|
|
263
|
+
await Promise.all(list.slice(0, MAX_PICTURES).map(async (src) => found.set(src, await fileOf(src, from, here).catch(() => null))));
|
|
264
|
+
return found;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* What pastedLines makes of a pasted picture (`found`: resolve's answer): the cell's picture, its
|
|
269
|
+
* alt and width kept; else a web one a link to it (`links`), any other its alt words (undefined:
|
|
270
|
+
* an element that is no picture).
|
|
271
|
+
*/
|
|
272
|
+
const maker = (found, links = true) => (img) => {
|
|
273
|
+
if (img.localName !== "img") return undefined;
|
|
274
|
+
const src = img.getAttribute("src") ?? "";
|
|
275
|
+
const alt = img.getAttribute("alt") ?? "";
|
|
276
|
+
const width = img.getAttribute("width") ?? "";
|
|
277
|
+
const file = found.get(src);
|
|
278
|
+
if (file) return cellPicture(document, file.path, { alt, width: WIDTH.test(width) ? Number(width) : null });
|
|
279
|
+
if (links && WEB.test(src)) {
|
|
280
|
+
const link = document.createElement("a");
|
|
281
|
+
link.setAttribute("href", src);
|
|
282
|
+
link.textContent = alt || nameOf(src);
|
|
283
|
+
return link;
|
|
284
|
+
}
|
|
285
|
+
return alt ? document.createTextNode(alt) : null;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
fromFiles, resolve, maker,
|
|
290
|
+
/** The tools' nodes (kept in the top layer with a modal dialog). */
|
|
291
|
+
get nodes() { return [frame, handle].filter((node) => node.isConnected); },
|
|
292
|
+
/** Take them away (the page stops editing). */
|
|
293
|
+
hide() {
|
|
294
|
+
hovered = picked = null;
|
|
295
|
+
if (drag) end(false);
|
|
296
|
+
draw();
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|