@heycys/gallery 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +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/icon/page.mjs +6 -4
- package/browser/editor/components/icons.mjs +5 -2
- 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/import/read.mjs +20 -2
- 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/document-copy.mjs +43 -0
- package/browser/shared/drafts.mjs +10 -3
- package/browser/shared/emoji/icon-value.mjs +2 -2
- package/browser/shared/emoji/line-icons.mjs +37 -0
- package/browser/shared/extensions.mjs +83 -7
- package/browser/shared/http.mjs +9 -0
- package/browser/shared/i18n/en.mjs +50 -17
- package/browser/shared/outline-shape.mjs +6 -0
- package/browser/shared/outline.mjs +165 -12
- 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/browser/shared/workspace-columns.mjs +11 -7
- package/dist/{chunk-N7AZZLGT.mjs → chunk-2GKGEVMR.mjs} +29 -24
- package/dist/chunk-2OHANJ6Q.mjs +2 -0
- package/dist/{chunk-HYMEEXKN.mjs → chunk-2RKIHJI2.mjs} +1 -1
- package/dist/{chunk-742D2WWJ.mjs → chunk-36FIRJIF.mjs} +2 -2
- package/dist/{chunk-BPL64HGA.mjs → chunk-3A5I2GJK.mjs} +1 -1
- package/dist/{chunk-RFDZXFJF.mjs → chunk-3OFXMN6N.mjs} +1 -1
- package/dist/chunk-46ACXOU5.mjs +2 -0
- package/dist/{chunk-PUOQ65NC.mjs → chunk-4AMTKWDO.mjs} +1 -1
- package/dist/{chunk-VTKNEYT3.mjs → chunk-4DNHHJSL.mjs} +1 -1
- package/dist/chunk-5DPABPBI.mjs +2 -0
- package/dist/chunk-5IIH335D.mjs +3 -0
- package/dist/chunk-5L26JUYA.mjs +2 -0
- package/dist/{chunk-3NJACR4W.mjs → chunk-5MEXZ2UF.mjs} +1 -1
- package/dist/{chunk-ZHEHPMBK.mjs → chunk-6MPKCH3P.mjs} +1 -1
- package/dist/{chunk-C564NU2H.mjs → chunk-74RPP2M2.mjs} +1 -1
- package/dist/chunk-7M2BQQGZ.mjs +2 -0
- package/dist/{chunk-VXACTLO3.mjs → chunk-7M7UPWU6.mjs} +1 -1
- package/dist/{chunk-ZB4H7ZN4.mjs → chunk-7N2LPG33.mjs} +1 -1
- package/dist/{chunk-UBBRTJ2R.mjs → chunk-7XDYK3UC.mjs} +1 -1
- package/dist/chunk-AEMVZYZQ.mjs +166 -0
- package/dist/{chunk-POQSE4C2.mjs → chunk-AFGUR7SY.mjs} +1 -1
- package/dist/{chunk-QKXMPUXC.mjs → chunk-AMDGVDNV.mjs} +1 -1
- package/dist/{chunk-KTKHRC6H.mjs → chunk-ASAXY2DJ.mjs} +1 -1
- package/dist/{chunk-NVRNLK25.mjs → chunk-BYSF5UMM.mjs} +2 -2
- package/dist/{chunk-U3SSPL4K.mjs → chunk-CCSGFCLZ.mjs} +1 -1
- package/dist/{chunk-NWFBE2YA.mjs → chunk-CG3UHDKH.mjs} +1 -1
- package/dist/chunk-CPS4SRPF.mjs +5 -0
- package/dist/{chunk-B4P2CM2J.mjs → chunk-CZ5KJSGK.mjs} +1 -1
- package/dist/chunk-DAMVDXR3.mjs +2 -0
- package/dist/{chunk-NG375ILQ.mjs → chunk-DH6GMDNN.mjs} +1 -1
- package/dist/chunk-DHB7UZ7F.mjs +3 -0
- package/dist/{chunk-MHW7ZKOU.mjs → chunk-DKR7LFIY.mjs} +1 -1
- package/dist/chunk-DLFZJJEE.mjs +2 -0
- package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
- package/dist/{chunk-6VTU6KEO.mjs → chunk-FCK6NXON.mjs} +1 -1
- package/dist/{chunk-ZWPMCK7P.mjs → chunk-FX42XU6J.mjs} +1 -1
- package/dist/chunk-FXQBADMN.mjs +5 -0
- package/dist/{chunk-W3BGBJQW.mjs → chunk-G4YP7GVC.mjs} +1 -1
- package/dist/{chunk-V6XZ4VIR.mjs → chunk-GWOYMENX.mjs} +1 -1
- package/dist/chunk-HATA2W7X.mjs +2 -0
- package/dist/{chunk-YI43RG4P.mjs → chunk-HBAUKKQH.mjs} +1 -1
- package/dist/{chunk-MAFIGVXY.mjs → chunk-HM4E43VM.mjs} +1 -1
- package/dist/{chunk-CRJEG7MT.mjs → chunk-JI6T3A4Y.mjs} +1 -1
- package/dist/{chunk-NFMCP2HI.mjs → chunk-JJNRIFND.mjs} +2 -2
- package/dist/chunk-JL6R4FG4.mjs +4 -0
- package/dist/{chunk-UMHUSOPJ.mjs → chunk-JXLSHVJ2.mjs} +1 -1
- package/dist/{chunk-FJYN7RTH.mjs → chunk-KFZBYDIS.mjs} +1 -1
- package/dist/{chunk-ZTHICSSF.mjs → chunk-KH24LWAT.mjs} +1 -1
- package/dist/{chunk-RKPWLGZ3.mjs → chunk-KUILYI7H.mjs} +1 -1
- package/dist/{chunk-RMECZA2K.mjs → chunk-L7W624FD.mjs} +1 -1
- package/dist/chunk-LE44MD3O.mjs +4 -0
- package/dist/{chunk-4K5KC6V2.mjs → chunk-LV24RY2E.mjs} +1 -1
- package/dist/{chunk-43B75J5T.mjs → chunk-MKTX5PGV.mjs} +1 -1
- package/dist/{chunk-LKX55H76.mjs → chunk-NAGMRVW4.mjs} +1 -1
- package/dist/{chunk-D4CKRD3V.mjs → chunk-NFABH3QW.mjs} +2 -2
- package/dist/{chunk-PVER7LSC.mjs → chunk-NIW7F7LU.mjs} +2 -2
- package/dist/{chunk-PAREWQGB.mjs → chunk-NWRZXELO.mjs} +1 -1
- package/dist/{chunk-OJKMINIU.mjs → chunk-OU3S466R.mjs} +15 -15
- package/dist/{chunk-5ZJA7CXT.mjs → chunk-PAFSFVYB.mjs} +1 -1
- package/dist/chunk-QCEATQC4.mjs +2 -0
- package/dist/{chunk-Y34NR5JL.mjs → chunk-QJ3TFJLQ.mjs} +1 -1
- package/dist/{chunk-IA4PSIMD.mjs → chunk-QK3VISCA.mjs} +1 -1
- package/dist/{chunk-ZUNVAXLH.mjs → chunk-QXVNUASA.mjs} +1 -1
- package/dist/{chunk-GRAXKKG4.mjs → chunk-QZ35UAB4.mjs} +1 -1
- package/dist/{chunk-UGWYKHBL.mjs → chunk-S2Y7VI4H.mjs} +2 -2
- package/dist/{chunk-OW5NWPOJ.mjs → chunk-SAHS56QM.mjs} +1 -1
- package/dist/{chunk-QOUEFUCH.mjs → chunk-SOD23RGL.mjs} +1 -1
- package/dist/chunk-TDWJKOLN.mjs +2 -0
- package/dist/{chunk-V6RIYQZZ.mjs → chunk-TOE6DPLH.mjs} +1 -1
- package/dist/chunk-UGO5N3EI.mjs +15 -0
- package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
- package/dist/{chunk-NWR2LT5B.mjs → chunk-V5JRSGZF.mjs} +1 -1
- package/dist/{chunk-BZ5RAF7L.mjs → chunk-VA3KAQZD.mjs} +1 -1
- package/dist/chunk-VG5JEIUQ.mjs +12 -0
- package/dist/{chunk-UNOYVIYR.mjs → chunk-VGTMLQWZ.mjs} +1 -1
- package/dist/chunk-VKSRM7EY.mjs +22 -0
- package/dist/{chunk-YQGT4AB2.mjs → chunk-VNUWREXU.mjs} +1 -1
- package/dist/{chunk-AGDILNWB.mjs → chunk-VPVOAJ7X.mjs} +1 -1
- package/dist/{chunk-HLYV7RIH.mjs → chunk-WCWYNFDF.mjs} +1 -1
- package/dist/chunk-WTEGUPFG.mjs +16 -0
- package/dist/{chunk-N76HWBA3.mjs → chunk-XKOHV4H6.mjs} +1 -1
- package/dist/{chunk-HMYTM3GT.mjs → chunk-XMKHCUI6.mjs} +1 -1
- package/dist/{chunk-OUUP3WKT.mjs → chunk-XQRUG4G3.mjs} +1 -1
- package/dist/{chunk-32ZUEFZ7.mjs → chunk-YBB5LWZK.mjs} +1 -1
- package/dist/{chunk-SX4LJYT6.mjs → chunk-YFJU3O7G.mjs} +1 -1
- package/dist/chunk-YPPJVDQI.mjs +2 -0
- package/dist/chunk-ZANMKAWS.mjs +2 -0
- package/dist/{chunk-6QBHU4DF.mjs → chunk-ZCP7BTOZ.mjs} +1 -1
- package/dist/{chunk-ERLAIBOA.mjs → chunk-ZEY2RLBT.mjs} +2 -2
- package/dist/{chunk-SQ6Y2ZMA.mjs → chunk-ZO7SFB2E.mjs} +2 -2
- 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/math.js +1 -1
- package/kit/report.css +123 -44
- 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 +10 -10
- package/skill/SKILL.md +15 -14
- package/skill/references/building-blocks.md +4 -4
- package/skill/references/extensions.md +11 -7
- package/skill/references/pages-and-data.md +13 -9
- package/skill/references/writing-html.md +2 -0
- package/web/assets/index-BHC5ZOMh.js +15 -0
- package/web/assets/index-xP0uw3a0.css +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-2NRBY6AJ.mjs +0 -2
- package/dist/chunk-35NQ4PLQ.mjs +0 -2
- package/dist/chunk-3KJHLHUO.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-GY3FAUVM.mjs +0 -2
- package/dist/chunk-JVHHHGEA.mjs +0 -3
- package/dist/chunk-L7UP52JO.mjs +0 -2
- package/dist/chunk-NAMN3RAF.mjs +0 -2
- package/dist/chunk-PZDLJJTJ.mjs +0 -3
- package/dist/chunk-SXHBYZST.mjs +0 -16
- package/dist/chunk-TEZAWDSQ.mjs +0 -2
- 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/dist/{chunk-YEY3RSFQ.mjs → chunk-5MMN3N5J.mjs} +0 -0
- /package/dist/{chunk-UH4SIXTR.mjs → chunk-74UW7QSL.mjs} +0 -0
- /package/dist/{chunk-W7XHE4CT.mjs → chunk-EKGYYOSP.mjs} +0 -0
- /package/dist/{chunk-QAIHRH3I.mjs → chunk-KNZJT4ET.mjs} +0 -0
- /package/dist/{chunk-A55HN5OK.mjs → chunk-N3DSBH5S.mjs} +0 -0
- /package/dist/{chunk-QR6A54KJ.mjs → chunk-ZW2A6GBW.mjs} +0 -0
|
@@ -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
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ARRIVING, ATTRIBUTE, DATA_MOST, DATA_SHOWN, FALLBACK, FIELDS, INSTANCE, NO_TEXT, PACKAGE_MARK, SCHEMA, SLOT, SLOT_PART, componentAt, fitsDataScript, heldBy, instanceMatch, isDataScript, noDataPart, refusedInData, schemaAhead, slotsIn } from "../../../shared/components/installed.mjs";
|
|
2
2
|
import { referencedIds } from "../../../shared/copy-ids.mjs";
|
|
3
|
+
import { jsonIn } from "../../../shared/json-layout.mjs";
|
|
4
|
+
import { CHART_SOURCE, VIEW_FROM } from "../../../shared/table-chart.mjs";
|
|
3
5
|
import { language, t } from "../../../shared/i18n/index.mjs";
|
|
6
|
+
import { searchWords } from "../../../shared/pinyin.mjs";
|
|
4
7
|
import { caretAt, editable } from "../../page/caret.mjs";
|
|
5
8
|
import { htmlToBlocks, parseClipboardHTML } from "../../page/clipboard/import/html.mjs";
|
|
6
9
|
import { toBlocks } from "../../page/clipboard/import/to-blocks.mjs";
|
|
@@ -8,8 +11,11 @@ import { build as buildBlock } from "../../page/clipboard/own/blocks.mjs";
|
|
|
8
11
|
import { copyOf } from "../../page/clipboard/own/describe.mjs";
|
|
9
12
|
import { ownIds } from "../../page/clipboard/own/ids.mjs";
|
|
10
13
|
import { firstHost, newHost, unitElement } from "../../page/core.mjs";
|
|
14
|
+
import { openSettings } from "../../page/board/settings.mjs";
|
|
15
|
+
import { bindAbove, keyOf, tableChoices } from "../../page/table/binding.mjs";
|
|
11
16
|
import { addStyle } from "../../page/ui.mjs";
|
|
12
17
|
import { accepts, capsFor } from "../registry.mjs";
|
|
18
|
+
import { SETTINGS_ICON } from "../icons.mjs";
|
|
13
19
|
import { ICON } from "./index.mjs";
|
|
14
20
|
import { followCatalog, followSettings, isBehind } from "./served.mjs";
|
|
15
21
|
|
|
@@ -301,9 +307,6 @@ function keepSlots(node, from, next) {
|
|
|
301
307
|
console.error(`[DocKit] ${instance.getAttribute(ATTRIBUTE)}: a slot left its instance: it is put back, its blocks the document's`);
|
|
302
308
|
}
|
|
303
309
|
|
|
304
|
-
// A package's picture where a menu shows a mark.
|
|
305
|
-
const pictureOf = ({ preview }) => (preview ? `<img src="${preview}" alt="">` : null);
|
|
306
|
-
|
|
307
310
|
// A slot the file has empty (an Agent's): what a disclosure's content says (details/page.mjs), a click
|
|
308
311
|
// on it making a paragraph there (insert/ clickBlank).
|
|
309
312
|
const SLOT_EMPTY = `
|
|
@@ -312,7 +315,7 @@ const SLOT_EMPTY = `
|
|
|
312
315
|
`;
|
|
313
316
|
|
|
314
317
|
/** An instance on the page: what its events are, what Enter does in its words, and what the menus make of its package. */
|
|
315
|
-
export function create({ map, newParagraph, active, sourceStep, struct }) {
|
|
318
|
+
export function create({ map, newParagraph, active, sourceStep, struct, look }) {
|
|
316
319
|
held = map;
|
|
317
320
|
addStyle(SLOT_EMPTY);
|
|
318
321
|
// Whether a key in an instance, on no field of it, is undo or redo: a button or what a component draws
|
|
@@ -360,7 +363,73 @@ export function create({ map, newParagraph, active, sourceStep, struct }) {
|
|
|
360
363
|
sourceStep(changed.map(([part, was]) => [part, was]));
|
|
361
364
|
return true;
|
|
362
365
|
}
|
|
366
|
+
// ---------- 设置 ----------
|
|
367
|
+
// An instance's 设置 (the handle's menu; the board's form of a custom page, page/board/settings.mjs, whose
|
|
368
|
+
// menu cards a choice is made in): the fields its manifest's `settings` lists, kept in its part
|
|
369
|
+
// `settings` (a script of JSON, what `context.editJSON` writes: one undo step, a change laid out as the
|
|
370
|
+
// part was), and for a view of a table (needs "tables") which table it reads, kept in its VIEW_FROM and on
|
|
371
|
+
// the table's frame (binding.mjs), one undo step too. Null where it has none: no field, or no part to keep
|
|
372
|
+
// them in, and no table to choose; an instance that does not run or that is past its manifest has no form.
|
|
373
|
+
const TABLE_FIELD = "__table"; // the form's field for the table: no setting of the manifest's
|
|
374
|
+
const settingsIn = (part) => {
|
|
375
|
+
try {
|
|
376
|
+
const value = JSON.parse(part?.textContent?.trim() || "{}");
|
|
377
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
378
|
+
} catch {
|
|
379
|
+
return {};
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
function formOf(instance) {
|
|
383
|
+
if (!active() || instance.hasAttribute(FALLBACK) || ahead(instance)) return null;
|
|
384
|
+
const entry = entryOf(instance.getAttribute(ATTRIBUTE));
|
|
385
|
+
if (!entry) return null;
|
|
386
|
+
const part = dataParts(instance).find(([name]) => name === "settings")?.[1] ?? null;
|
|
387
|
+
const fields = part ? entry.settings ?? [] : [];
|
|
388
|
+
const reads = entry.needs?.includes("tables") ?? false;
|
|
389
|
+
return fields.length > 0 || reads ? { entry, part, fields, reads } : null;
|
|
390
|
+
}
|
|
391
|
+
// The values of the manifest's fields put in the part, one `source` step, as editJSON writes them.
|
|
392
|
+
function writeSettings(instance, part, values) {
|
|
393
|
+
write({
|
|
394
|
+
parts: [{ name: "settings", part }],
|
|
395
|
+
run({ settings: draft }) {
|
|
396
|
+
const text = draft.textContent ?? "";
|
|
397
|
+
const current = settingsIn(draft);
|
|
398
|
+
draft.textContent = text.trim() ? jsonIn(text, { ...current, ...values }) : JSON.stringify({ ...current, ...values });
|
|
399
|
+
return true;
|
|
400
|
+
},
|
|
401
|
+
}, instance);
|
|
402
|
+
}
|
|
403
|
+
// The table a view reads put on it, as one undo step: its key on both, a new one on a table with none.
|
|
404
|
+
function readTable(instance, frame) {
|
|
405
|
+
const key = frame ? keyOf(frame) : null;
|
|
406
|
+
const keyed = frame && frame.getAttribute(CHART_SOURCE) !== key;
|
|
407
|
+
look([instance, ...(keyed ? [frame] : [])], [{ attributes: { [VIEW_FROM]: key } }, ...(keyed ? [{ attributes: { [CHART_SOURCE]: key } }] : [])]);
|
|
408
|
+
}
|
|
409
|
+
function openFormOf(instance) {
|
|
410
|
+
const form = formOf(instance);
|
|
411
|
+
if (!form) return;
|
|
412
|
+
const choices = form.reads ? tableChoices() : [];
|
|
413
|
+
const bound = form.reads ? choices.findIndex(({ frame }) => frame.getAttribute(CHART_SOURCE) === instance.getAttribute(VIEW_FROM) && instance.hasAttribute(VIEW_FROM)) : -1;
|
|
414
|
+
const fields = [
|
|
415
|
+
...(form.reads ? [{ key: TABLE_FIELD, label: t("表格"), type: "choice", choices: [["", t("(未选择)")], ...choices.map(({ label }, at) => [String(at), label])] }] : []),
|
|
416
|
+
...form.fields,
|
|
417
|
+
];
|
|
418
|
+
const box = instance.getBoundingClientRect();
|
|
419
|
+
const top = Math.min(Math.max(box.top, 8), innerHeight - 40);
|
|
420
|
+
openSettings({
|
|
421
|
+
fields, name: form.entry.name,
|
|
422
|
+
values: { ...(form.part ? settingsIn(form.part) : {}), ...(form.reads ? { [TABLE_FIELD]: bound >= 0 ? String(bound) : "" } : {}) },
|
|
423
|
+
anchor: { left: box.left, right: box.right, top, bottom: top + 8, width: box.width, height: 8 },
|
|
424
|
+
save({ [TABLE_FIELD]: table, ...values }) {
|
|
425
|
+
if (table !== undefined) readTable(instance, table === "" ? null : choices[Number(table)]?.frame ?? null);
|
|
426
|
+
if (Object.keys(values).length > 0 && form.part?.isConnected) writeSettings(instance, form.part, values);
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
}
|
|
363
430
|
return {
|
|
431
|
+
/** An instance's own rows in its block menu: 设置…, where it has a form (formOf). */
|
|
432
|
+
menuItems: (unit) => (formOf(unit) ? [{ label: t("设置…"), icon: SETTINGS_ICON, run: () => openFormOf(unit) }] : []),
|
|
364
433
|
keepsKeys: (node, event) => componentAt(node) && !editable(node) && !pageUndo(node, event),
|
|
365
434
|
undoHere: (node) => componentAt(node) && !editable(node),
|
|
366
435
|
keys: {
|
|
@@ -399,8 +468,8 @@ export function create({ map, newParagraph, active, sourceStep, struct }) {
|
|
|
399
468
|
// for a divider). Its slots are boxes made on the page at once, what the template has in each made
|
|
400
469
|
// blocks of the page's (an empty paragraph where it has none), so that the reader writes in one
|
|
401
470
|
// before the instance is saved, and Enter in a text part goes into the slot after it. One whose needs
|
|
402
|
-
// hold "tables" is a view a table
|
|
403
|
-
//
|
|
471
|
+
// hold "tables" is a view of a table (`readsTables`), which binds itself to the nearest table above it as
|
|
472
|
+
// it is made (VIEW_FROM: page/table/binding.mjs). Where two offered have one name (the same block
|
|
404
473
|
// of two packs: 我的待办 of Gallery 风格 and of 柔光风格), each row says its pack's name after it in grey
|
|
405
474
|
// (`of`: the menu's ge-pop-of).
|
|
406
475
|
menuVariants: () => {
|
|
@@ -410,8 +479,8 @@ export function create({ map, newParagraph, active, sourceStep, struct }) {
|
|
|
410
479
|
return Object.fromEntries(offered.map((entry) => [entry.id, {
|
|
411
480
|
label: entry.name[language],
|
|
412
481
|
...(counts.get(entry.name[language]) > 1 ? { of: entry.pack?.name?.[language] ?? entry.id.split("/")[0] } : {}),
|
|
413
|
-
icon:
|
|
414
|
-
words:
|
|
482
|
+
icon: ICON,
|
|
483
|
+
words: searchWords(entry),
|
|
415
484
|
sample: true,
|
|
416
485
|
readsTables: entry.needs?.includes("tables") ?? false,
|
|
417
486
|
preview: { src: entry.preview, says: entry.description[language] },
|
|
@@ -421,6 +490,7 @@ export function create({ map, newParagraph, active, sourceStep, struct }) {
|
|
|
421
490
|
slotsMade(doc, instance, (slot) => blocksOfTemplate(doc, slot));
|
|
422
491
|
ownIds(doc, [instance]);
|
|
423
492
|
place(instance);
|
|
493
|
+
if (entry.needs?.includes("tables")) bindAbove(instance); // a view reads the nearest table above it; its 设置 changes that
|
|
424
494
|
return editableParts(instance)[0] ?? firstHost(instance) ?? place(paragraph());
|
|
425
495
|
},
|
|
426
496
|
}]));
|
|
@@ -458,11 +528,8 @@ export function create({ map, newParagraph, active, sourceStep, struct }) {
|
|
|
458
528
|
if (next) caretAt(next, "start");
|
|
459
529
|
else if (next === null) selection.removeAllRanges();
|
|
460
530
|
},
|
|
461
|
-
/**
|
|
462
|
-
iconOf: (
|
|
463
|
-
const entry = entryOf(unit.getAttribute(ATTRIBUTE));
|
|
464
|
-
return (entry && pictureOf(entry)) ?? ICON;
|
|
465
|
-
},
|
|
531
|
+
/** The mark of a custom block: the one every custom block has in a menu, its picture shown beside its row instead. */
|
|
532
|
+
iconOf: () => ICON,
|
|
466
533
|
/**
|
|
467
534
|
* What of an instance its component writes, part by part, as the file is to hold it (null: nothing
|
|
468
535
|
* here does): its parts as the kit's dehydrate leaves them, what it and its component leave on them
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { svg } from "../icons.mjs";
|
|
2
|
+
import { TABLE_ICONS } from "../../page/table/icons.mjs";
|
|
2
3
|
|
|
3
4
|
// The media blocks' toolbar icons (an image's, an embed's, the block menus' rows): the editor's own line
|
|
4
5
|
// drawings (components/icons.mjs svg) on the same 20 units as the menus' others, the picture in the
|
|
5
6
|
// middle 16 of them. They carry no size or color: the toolbar's buttons and the menus set them.
|
|
7
|
+
// Plain, a few strokes each (the user, 2026-11-09: Notion's looked too close, then the bars against a
|
|
8
|
+
// guide line that took their place too busy): a side is the editor's own 对齐 lines, the ones a text
|
|
9
|
+
// block's and a table's 对齐 › show (table/icons.mjs), so one look says it everywhere; a caption a picture
|
|
10
|
+
// (one hill in it) over a line of words; viewing large one line corner to corner with a head at each end; three dots.
|
|
6
11
|
|
|
7
12
|
export const TOOL_ICONS = {
|
|
8
|
-
// The three sides:
|
|
9
|
-
alignLeft:
|
|
10
|
-
alignCenter:
|
|
11
|
-
alignRight:
|
|
12
|
-
// A picture
|
|
13
|
-
caption: svg('<rect x="
|
|
14
|
-
//
|
|
15
|
-
expand: svg('<
|
|
13
|
+
// The three sides: the editor's alignment lines (a text block's 对齐 ›, a table's).
|
|
14
|
+
alignLeft: TABLE_ICONS["doc-align-left"],
|
|
15
|
+
alignCenter: TABLE_ICONS["doc-align-center"],
|
|
16
|
+
alignRight: TABLE_ICONS["doc-align-right"],
|
|
17
|
+
// A picture (its frame, one hill in it) over a line of words.
|
|
18
|
+
caption: svg('<rect x="3.75" y="3.25" width="12.5" height="9.5" rx="1.5"/><path d="m4 11.75 4-3.5 4.5 4M3.75 16.25h12.5"/>'),
|
|
19
|
+
// One line from corner to corner, a head at each end.
|
|
20
|
+
expand: svg('<path d="M11.5 4.25h4.25V8.5M8.5 15.75H4.25V11.5M15.25 4.75l-10.5 10.5"/>'),
|
|
16
21
|
// An arrow down onto a line.
|
|
17
22
|
download: svg('<path d="M10 4v9M6.5 9.75l3.5 3.5 3.5-3.5M4.5 16h11"/>'),
|
|
18
23
|
more: svg('<circle cx="5" cy="10" r="1.3" fill="currentColor" stroke="none"/><circle cx="10" cy="10" r="1.3" fill="currentColor" stroke="none"/><circle cx="15" cy="10" r="1.3" fill="currentColor" stroke="none"/>'),
|
|
@@ -175,14 +175,16 @@ export function create({ struct, finishStep, look, say, active, blocks, newParag
|
|
|
175
175
|
},
|
|
176
176
|
/**
|
|
177
177
|
* `file` pasted: a new block made by `make(doc)`, put in its place by `put` (the paste's, clipboard/:
|
|
178
|
-
* where a block pasted there goes, one undo step; false when it could not),
|
|
179
|
-
* (its placeholder filled
|
|
178
|
+
* where a block pasted there goes, one undo step; false when it could not), the block selected, then
|
|
179
|
+
* `fill(figure, file, { pasted: true })` (its placeholder filled, in the same undo step: one Ctrl+Z
|
|
180
|
+
* takes the paste back to what stood there). Returns whether it took the file.
|
|
180
181
|
*/
|
|
181
182
|
paste(put, file, make, fill) {
|
|
182
183
|
if (!file) return false;
|
|
183
184
|
const figure = make(document);
|
|
184
185
|
if (!put(figure)) return false;
|
|
185
|
-
|
|
186
|
+
blocks()?.select(figure, { quiet: true });
|
|
187
|
+
fill(figure, file, { pasted: true });
|
|
186
188
|
return true;
|
|
187
189
|
},
|
|
188
190
|
};
|