@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,3 +1,4 @@
|
|
|
1
|
+
import { IN_LAYER } from "../../../shared/layers.mjs";
|
|
1
2
|
import { tooltip } from "../../page/tooltip.mjs";
|
|
2
3
|
import { element } from "../../page/ui.mjs";
|
|
3
4
|
|
|
@@ -7,7 +8,7 @@ import { element } from "../../page/ui.mjs";
|
|
|
7
8
|
|
|
8
9
|
/** The toolbar's look, and its buttons' (a popover's too: an image's 对齐 pops three of them). */
|
|
9
10
|
export const TOOLBAR_STYLE = `
|
|
10
|
-
.ge-layer > .ge-media-bar { pointer-events: auto; }
|
|
11
|
+
.ge-layer > .ge-media-bar { pointer-events: auto; z-index: ${IN_LAYER.button}; } /* over its block's fill while the block is selected */
|
|
11
12
|
.ge-media-bar { display: flex; padding: 2px; border-radius: 6px; background: var(--ge-bg);
|
|
12
13
|
box-shadow: 0 0 0 1px var(--ge-outline), 0 4px 12px -2px light-dark(rgb(25 25 25 / 8%), rgb(0 0 0 / 30%)); }
|
|
13
14
|
.ge-media-tool { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 4px; color: var(--ge-ico2); cursor: pointer; user-select: none; transition: background 20ms ease-in; }
|
|
@@ -21,11 +22,19 @@ export const mediaBar = (label) => element("div", { class: "ge-media-bar", role:
|
|
|
21
22
|
/**
|
|
22
23
|
* A toolbar button: `label` (what it is), `tip` (the tooltip), `icon` (an svg, or a function giving
|
|
23
24
|
* it, drawn by draw() each time the bar is shown), `run(button)` on a click while `alive()` (the block it stands for is
|
|
24
|
-
* still on the page).
|
|
25
|
+
* still on the page). draw() puts a new icon in only when it changed: a bar placed every frame (an
|
|
26
|
+
* image's, while it is selected) that put its icons in anew each time took the svg a press landed on
|
|
27
|
+
* out of the page before the button was let go, and the browser then fired no click (2026-11-09).
|
|
25
28
|
*/
|
|
26
29
|
export function toolButton({ label, tip, icon, run, alive }) {
|
|
27
30
|
const button = element("div", { class: "ge-media-tool", role: "button", "aria-label": label });
|
|
28
|
-
|
|
31
|
+
let drawn = null;
|
|
32
|
+
button.draw = () => {
|
|
33
|
+
const markup = typeof icon === "function" ? icon() : icon;
|
|
34
|
+
if (markup === drawn) return;
|
|
35
|
+
drawn = markup;
|
|
36
|
+
button.innerHTML = markup;
|
|
37
|
+
};
|
|
29
38
|
if (typeof icon !== "function") button.draw(); // a function icon depends on the block: drawn when the bar is shown
|
|
30
39
|
tooltip(button, tip);
|
|
31
40
|
button.addEventListener("pointerdown", (event) => event.preventDefault());
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { svg } from "../icons.mjs";
|
|
2
2
|
import { PARAGRAPH, classOf } from "../kit.mjs";
|
|
3
|
+
import { ALIGN_TOGGLES } from "../align.mjs";
|
|
3
4
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
4
5
|
|
|
5
6
|
// 文本 and 小字: the plain text blocks. A paragraph is the editor's default block: Enter makes one,
|
|
@@ -8,7 +9,7 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
8
9
|
// default, a paragraph is the engines' own: core.mjs (newHost, splitBlock), insert/
|
|
9
10
|
// (paragraphBefore, newParagraph, clickBlank), convert.mjs (typeOf tells 小字 from text), clipboard/own/
|
|
10
11
|
// (a text block's description is the engine's own, and a block that says no kind is written as a paragraph).
|
|
11
|
-
// Its color (颜色 in the block menu): class switches, colors.mjs.
|
|
12
|
+
// Its color (颜色 in the block menu): class switches, colors.mjs; its alignment and indent (对齐与缩进), align.mjs.
|
|
12
13
|
// Its look: the kit's styles/flow.css (src/render/kit/).
|
|
13
14
|
/** The class of small text's end part (the page editor's Tab), a span at the line's right end (the kit's styles/flow.css). */
|
|
14
15
|
export const TAB = classOf(PARAGRAPH.tail);
|
|
@@ -23,7 +24,7 @@ export default {
|
|
|
23
24
|
name: "paragraph",
|
|
24
25
|
listed: () => t("文本"), hideable: false,
|
|
25
26
|
units: {
|
|
26
|
-
paragraph: { name: t("文本"), match: [{ rank: 180, tag: "p" }], colors: ["text", "background"], emptyIsNothing: true, fromWords: true },
|
|
27
|
+
paragraph: { name: t("文本"), match: [{ rank: 180, tag: "p" }], colors: ["text", "background"], toggles: ALIGN_TOGGLES, emptyIsNothing: true, fromWords: true, replacedByFile: true },
|
|
27
28
|
},
|
|
28
29
|
types: {
|
|
29
30
|
paragraph: {
|
|
@@ -34,7 +35,7 @@ export default {
|
|
|
34
35
|
},
|
|
35
36
|
note: { // 小字: a brief label, unit or scope note
|
|
36
37
|
turn: 2, menu: ["basic", 2],
|
|
37
|
-
tag: "p", className: "doc-note", kind: "paragraph", leaf: "note", text: true, empty: true,
|
|
38
|
+
tag: "p", className: "doc-note", kind: "paragraph", leaf: "note", text: true, empty: true, emptyToText: true,
|
|
38
39
|
corner: "paragraph", // seldom picked: the gray corner of 文本's cell (type-grid.mjs), so 文本 and the headings fill one line
|
|
39
40
|
label: t("小字"), icon: svg('<path d="M6.25 8.5v-.9h7.5v.9M10 7.6v8.15M8.75 15.75h2.5"/>'), words: "小字 注释 说明 备注 标注 xiaozi zhushi shuoming beizhu xz zs sm note small caption",
|
|
40
41
|
glance: { row: 11, name: "小字", write: "`<p class=\"doc-note\">`, `span.doc-tab` at its right", rule: "Kit blocks" },
|
|
@@ -44,7 +44,7 @@ import { t } from "../../shared/i18n/index.mjs";
|
|
|
44
44
|
// without. What is hidden goes from "/", 在下方新建 and the handle's grid and nothing else
|
|
45
45
|
// (page/offered.mjs); its blocks, its Markdown shortcut and "Blocks at a glance" stay.
|
|
46
46
|
// units the unit kinds it defines (blocks the reader selects, moves and deletes):
|
|
47
|
-
// { [kind]: { name, icon?, match, chapter?, fixed?, block?, handle?, below?, boxed?, beside?, inRow?, sharesHeight?, entersAfter?, resizable?, copy?, clipboard?, sizes?, toggles?, colors?, colorDefaults?, removedEmpty?, turnsEmpty?, leadsChapter?, canHaveChildren?, emptyIsNothing?, fromWords?, attributes? } }
|
|
47
|
+
// { [kind]: { name, icon?, match, chapter?, fixed?, block?, handle?, below?, boxed?, beside?, inRow?, sharesHeight?, entersAfter?, resizable?, copy?, clipboard?, sizes?, toggles?, colors?, colorDefaults?, removedEmpty?, turnsEmpty?, leadsChapter?, canHaveChildren?, emptyIsNothing?, fromWords?, replacedByFile?, attributes? } }
|
|
48
48
|
// match: rules an element matches by its tag (one of `tag`), its class and its
|
|
49
49
|
// attribute, and by the element it stands in (`parent`: { tag?, class? }, a flowchart's
|
|
50
50
|
// step being an li whose parent is an ol.doc-flow), whichever are given; every
|
|
@@ -58,7 +58,7 @@ import { t } from "../../shared/i18n/index.mjs";
|
|
|
58
58
|
// (its container's: a tab's, the Sheet area's, on the tab row); below: false, its menu
|
|
59
59
|
// offers no new block below it (a tab, a Sheet area); boxed: a block with a frame of its
|
|
60
60
|
// own, no text flowing beside it: a double click between two of them makes a paragraph,
|
|
61
|
-
// a click to its right
|
|
61
|
+
// a click to its right puts the caret after it (page/after/), Enter on it selected puts a paragraph after it unless
|
|
62
62
|
// it is a text block (beside.mjs), and it is a block of its own to Ctrl+A; beside: the
|
|
63
63
|
// same click and Enter without being boxed (a row of columns); inRow(element): whether
|
|
64
64
|
// this one stands beside another now, as a row of columns always does and a flowchart's
|
|
@@ -91,6 +91,8 @@ import { t } from "../../shared/i18n/index.mjs";
|
|
|
91
91
|
// keys of a block stand after it as they stand in what it is in (a list: under its last item).
|
|
92
92
|
// emptyIsNothing: a block of it left empty counts as nothing written, in a container
|
|
93
93
|
// that says `removedEmpty`. fromWords: a block of it can be made again from its words alone.
|
|
94
|
+
// replacedByFile: a file pasted at the caret in an empty block of it takes the block's place (notion-lite's
|
|
95
|
+
// pasteFiles: an empty text block goes), else it goes after the block (page/clipboard/).
|
|
94
96
|
// boxes the box kinds it defines (containers whose children are units): { [kind]: { role,
|
|
95
97
|
// name?, classes?, find?, owner?, takenBy?, also?, leaveByEnter?, endIsPadding?, lead?, firstLineActs?, firstBlock?, groups?, holdsOne?, emptyIsNothing?, outer?, selectAll? } }, classes being the kit's classes that make an element one
|
|
96
98
|
// (rank order, as above); the others the server knows by their tag (findUnits). find: where
|
|
@@ -165,7 +167,7 @@ import { t } from "../../shared/i18n/index.mjs";
|
|
|
165
167
|
// types the blocks the reader can make on the page or turn a block into (the "/" menu,
|
|
166
168
|
// Markdown shortcuts, the block menu), written by the server in the kit's plain markup:
|
|
167
169
|
// { [type]: { tag, className?, kind, box?, leaf?, wraps?, container?, part?, text?, inside?, tex?,
|
|
168
|
-
// empty?, resettable?, bare?, placeholder?, bar?, label?, icon?, hint?, markdown?, words?, turn?, menu?, fresh?,
|
|
170
|
+
// empty?, resettable?, emptyToText?, bare?, placeholder?, bar?, label?, icon?, hint?, markdown?, words?, turn?, menu?, fresh?,
|
|
169
171
|
// corner?, preload?, onSplit?, create?, takesAsIs?, holder?, nests?, joins?, leadIcon?, fromSelection?, clipboard?, glance? } } and, for the blocks the page
|
|
170
172
|
// and the server write apart from text, title?, content?, keyed?, written?,
|
|
171
173
|
// extras?, textHost?, blanks?, blankSays? (what each says is at its component). Leaves hold
|
|
@@ -174,7 +176,8 @@ import { t } from "../../shared/i18n/index.mjs";
|
|
|
174
176
|
// giving its markup, its words in the language t() says them in: components/placeholder.mjs
|
|
175
177
|
// mediaBar); resettable: an empty block of the type
|
|
176
178
|
// turns into body text on Enter, and the first of its container on Backspace at its start (a
|
|
177
|
-
// quote's paragraph, a list item: notion-lite's `resettable`, typing/leave.mjs)
|
|
179
|
+
// quote's paragraph, a list item: notion-lite's `resettable`, typing/leave.mjs); emptyToText: Backspace in an
|
|
180
|
+
// empty block of the type turns it into an empty paragraph where it stands (a heading, small text: typing/leave.mjs). leaf: the text block a block turned
|
|
178
181
|
// into the type becomes; wraps: such a text block sits in a container of the type, found
|
|
179
182
|
// by `container` (a selector); text: a plain text leaf; empty: a leaf written with no
|
|
180
183
|
// text too (a paragraph, a heading, a list item, a code block: an empty
|
|
@@ -436,6 +439,12 @@ export const CATEGORY_NAMES = Object.fromEntries(Object.entries(CATEGORIES).map(
|
|
|
436
439
|
*/
|
|
437
440
|
export const COPIED_AS_ITSELF = new Set(Object.entries(UNITS).filter(([kind, unit]) => UNIT_CATEGORY[kind] === "card" || unit.copy === "itself").map(([kind]) => kind));
|
|
438
441
|
|
|
442
|
+
/**
|
|
443
|
+
* The unit kinds drawn with no selection fill while selected alone (their own tools show it), each with
|
|
444
|
+
* the selector its element must hold for that (an image with its picture: a placeholder is filled).
|
|
445
|
+
*/
|
|
446
|
+
export const UNFILLED_ALONE = Object.fromEntries(Object.entries(UNITS).filter(([, unit]) => unit.unfilled).map(([kind, unit]) => [kind, unit.unfilled]));
|
|
447
|
+
|
|
439
448
|
/** What the reader calls each kind of unit (block labels while dragging, the block menu). */
|
|
440
449
|
export const KIND_NAMES = Object.fromEntries(Object.entries(UNITS).filter(([, unit]) => unit.name).map(([kind, unit]) => [kind, unit.name]));
|
|
441
450
|
|
|
@@ -744,6 +753,9 @@ export const removedFromAnywhere = (kind) => UNITS[kind]?.removedEmpty === "anyw
|
|
|
744
753
|
const NOTHING_EMPTY = flagged("emptyIsNothing");
|
|
745
754
|
export const TEXT_BLOCKS = [...new Set(Object.values(BLOCK_TYPES).filter((spec) => NOTHING_EMPTY.has(spec.kind)).map((spec) => spec.tag)), ...Object.values(BOXES).filter((box) => box.emptyIsNothing).flatMap((box) => box.classes.map((rule) => `.${rule.class}`))].join(", ");
|
|
746
755
|
|
|
756
|
+
/** Whether an empty unit of this kind gives its place to a file pasted at its caret (`replacedByFile`). */
|
|
757
|
+
export const replacedByFile = (kind) => Boolean(UNITS[kind]?.replacedByFile);
|
|
758
|
+
|
|
747
759
|
/** Whether what a unit of this kind is turned into is an empty block in its place (`turnsEmpty`). */
|
|
748
760
|
export const turnsEmpty = (kind) => Boolean(UNITS[kind]?.turnsEmpty);
|
|
749
761
|
|
|
@@ -777,7 +789,7 @@ export const hasOuter = (boxKind) => BOXES[boxKind]?.outer !== false;
|
|
|
777
789
|
/** The unit kinds with a frame of their own (`boxed`): a double click between two makes a paragraph (insert/). */
|
|
778
790
|
export const BOXED = flagged("boxed");
|
|
779
791
|
|
|
780
|
-
/** The unit kinds a click to the right of one
|
|
792
|
+
/** The unit kinds a click to the right of one puts the caret after (page/after/): the boxed ones and those that say `beside` (beside.mjs). */
|
|
781
793
|
export const BESIDE = new Set([...BOXED, ...flagged("beside")]);
|
|
782
794
|
|
|
783
795
|
/** Whether a unit of this kind stands beside another now, `element` being that unit (`beside`, `inRow`). */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ALIGN_PLACES } from "../align.mjs";
|
|
1
2
|
import { BLOCK_COLORS } from "../colors.mjs";
|
|
2
3
|
import { svg } from "../icons.mjs";
|
|
3
4
|
import { SPAN } from "../../../shared/media.mjs";
|
|
@@ -53,10 +54,11 @@ export default {
|
|
|
53
54
|
{ rank: 90, class: "doc-table-wrap" },
|
|
54
55
|
],
|
|
55
56
|
boxed: true,
|
|
57
|
+
toggles: ALIGN_PLACES, // the table placed on the page, at the left (the kit's own), centred or at the right: 对齐 ›, a text block's classes (align.mjs)
|
|
56
58
|
resizable: ".doc-table-wrap[data-ge-unit]", // its right and bottom edges, once it is in the file
|
|
57
59
|
copy: "itself", // within the report a copy keeps all of its markup (its scope line, its frame's size); another report's gets the table's data (tableData) made anew
|
|
58
|
-
// 识别数字列 off, and the key a chart or a view reads it by (src/shared/table-chart.mjs), which
|
|
59
|
-
//
|
|
60
|
+
// 识别数字列 off, and the key a chart or a view reads it by (src/shared/table-chart.mjs), which a view
|
|
61
|
+
// made from a menu puts on a table that has none (page/table/binding.mjs) in the step that makes the view.
|
|
60
62
|
attributes: { [NUMBERS]: (value) => value === "off", [CHART_SOURCE]: (value) => CHART_KEY.test(value) },
|
|
61
63
|
sizes: {
|
|
62
64
|
[SPAN]: { min: 0.15, max: 3 }, // past 1 the table is wider than the text and scrolls in its frame
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { IN_LAYER } from "../../../shared/layers.mjs";
|
|
2
|
+
import { UNIT, can, isEmpty, isHost, newHost, removalTarget, takeOut, withLeads } from "../core.mjs";
|
|
3
|
+
import { atEdge, caretAt, hosts } from "../caret.mjs";
|
|
4
|
+
import { caretBeside } from "../beside.mjs";
|
|
5
|
+
import { contentOf, extentOf } from "../geometry.mjs";
|
|
6
|
+
import { FOLLOW, task } from "../frames.mjs";
|
|
7
|
+
import { layerOf } from "../overlay-layer.mjs";
|
|
8
|
+
import { onSettle, own } from "../overlays.mjs";
|
|
9
|
+
import { addStyle, element } from "../ui.mjs";
|
|
10
|
+
import { alignClass, shownAlign } from "../align.mjs";
|
|
11
|
+
import { accepts, texOnly } from "../../components/registry.mjs";
|
|
12
|
+
|
|
13
|
+
// The caret after a block (docs/editor.md, "The caret after a block"; the user's, 2026-11-09): a click
|
|
14
|
+
// right of a block that is no text of its own (a picture, a table, a code block, a callout, a divider, a
|
|
15
|
+
// card, an instance of a custom block, a row of columns…: insert/ boxedBeside, beside.mjs) puts a caret
|
|
16
|
+
// at its bottom right, a blinking one, as the browser draws in text. Neither notion-lite nor
|
|
17
|
+
// feishu-lite has one (Gallery's click there selected the block before); this is the plainest rule.
|
|
18
|
+
// The text right below such a block comes back to it (the user's, 2026-11-09: Backspace there went
|
|
19
|
+
// into the block, a table's last cell, a callout's box, or past it, a picture passed over): Backspace
|
|
20
|
+
// at the start of the text block right after it (typing/, after the block's own keys and its
|
|
21
|
+
// alignment), and ← or ↑ there, put this caret after it, an empty block left going as it does
|
|
22
|
+
// (one undo step), one with words staying (no step); a second Backspace is this caret's.
|
|
23
|
+
//
|
|
24
|
+
// Backspace, Delete the block becomes an empty paragraph in its place, whatever follows it (the
|
|
25
|
+
// user's, 2026-11-09; the box it stands in taking one), aligned as the block was
|
|
26
|
+
// shown (a picture centred: centred, align.mjs shownAlign), the caret in it; one
|
|
27
|
+
// more Backspace takes that alignment off (typing/: align.mjs unalign) before
|
|
28
|
+
// anything else
|
|
29
|
+
// Enter an empty paragraph right after it, the caret in it
|
|
30
|
+
// a key typed a paragraph right after it holding what was typed, the caret at its end (an
|
|
31
|
+
// input method composes in the caret, its words going there once it ends)
|
|
32
|
+
// ↑ ←, ↓ → the text before it (its own last text: a table's last cell, a picture's caption),
|
|
33
|
+
// or after it, at its end or start; none there, the caret stays
|
|
34
|
+
// Esc the block selected
|
|
35
|
+
// Ctrl+V a paste goes into a new paragraph after it
|
|
36
|
+
//
|
|
37
|
+
// Each change is one undo step, the words typed going with the paragraph they made (history.mjs
|
|
38
|
+
// typedInto), and undo puts this caret back after the block. The caret is an element of the editor's
|
|
39
|
+
// layer (overlay-layer.mjs: the page's, a dialog's), never the report's: an empty field that takes the
|
|
40
|
+
// focus, in the body text's font, at the block's right edge with its bottom on the block's, kept there
|
|
41
|
+
// as the page moves (a task of frames.mjs); it goes with the focus, its block, or a press elsewhere.
|
|
42
|
+
|
|
43
|
+
const STYLE = `
|
|
44
|
+
.ge-after { z-index: ${IN_LAYER.frame}; display: inline-block; min-width: 2px; margin: 0; padding: 0; border: 0; outline: none; white-space: pre; background: transparent; color: var(--doc-text, currentColor); caret-color: var(--doc-text, currentColor); pointer-events: none; }
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* struct(parents, touched, mutate, options): the editor's undoable structural change (history.mjs).
|
|
49
|
+
* blocks(): the block tools (blocks.mjs). keepText(unit): why a block may not go. removeBlocks(units):
|
|
50
|
+
* the blocks deleted as the selected ones are (where no paragraph can take its place). say: the status
|
|
51
|
+
* line. undo(), redo(): the editor's.
|
|
52
|
+
*/
|
|
53
|
+
export function createAfter({ struct, blocks, keepText, removeBlocks, say, undo, redo }) {
|
|
54
|
+
addStyle(STYLE);
|
|
55
|
+
const node = element("span", { class: "ge-after", contenteditable: "true", spellcheck: "false", "data-doc-dev": "" });
|
|
56
|
+
let at = null; // the block the caret stands after
|
|
57
|
+
|
|
58
|
+
const follow = task("after", {
|
|
59
|
+
run() {
|
|
60
|
+
if (!at) return false;
|
|
61
|
+
if (!at.isConnected || at.getClientRects().length === 0) {
|
|
62
|
+
hide();
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
draw();
|
|
66
|
+
return FOLLOW;
|
|
67
|
+
},
|
|
68
|
+
fail: () => hide(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// At the block's right edge, its bottom on the block's: as tall as a line of the body text of the box it stands in.
|
|
72
|
+
function draw() {
|
|
73
|
+
const rect = extentOf(at);
|
|
74
|
+
if (!rect) return;
|
|
75
|
+
const layer = layerOf(at);
|
|
76
|
+
if (node.parentNode !== layer) layer.append(node);
|
|
77
|
+
const origin = layer.getBoundingClientRect();
|
|
78
|
+
const height = node.offsetHeight || 24;
|
|
79
|
+
node.style.transform = `translate(${Math.round(rect.right + 1 - origin.left)}px, ${Math.round(rect.bottom - height - origin.top)}px)`;
|
|
80
|
+
own(node, "after", { owner: at, alive: () => Boolean(at) && at.isConnected });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The caret after block `unit`, focused. */
|
|
84
|
+
function place(unit) {
|
|
85
|
+
if (!unit?.isConnected) return;
|
|
86
|
+
blocks().clear();
|
|
87
|
+
at = unit;
|
|
88
|
+
// The body text's font of the box it stands in (what a paragraph there is typed in).
|
|
89
|
+
const style = getComputedStyle(unit.parentElement ?? document.body);
|
|
90
|
+
const size = parseFloat(style.fontSize) || 16;
|
|
91
|
+
const line = parseFloat(style.lineHeight) || size * 1.5;
|
|
92
|
+
Object.assign(node.style, { fontFamily: style.fontFamily, fontSize: `${size}px`, lineHeight: `${line}px`, height: `${line}px` });
|
|
93
|
+
node.textContent = "";
|
|
94
|
+
draw();
|
|
95
|
+
node.focus({ preventScroll: true });
|
|
96
|
+
const range = document.createRange();
|
|
97
|
+
range.setStart(node, 0);
|
|
98
|
+
getSelection().removeAllRanges();
|
|
99
|
+
getSelection().addRange(range);
|
|
100
|
+
follow.wake();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function hide() {
|
|
104
|
+
at = null;
|
|
105
|
+
node.textContent = "";
|
|
106
|
+
node.remove();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// What the caret stands after, the caret taken away (the focus going where the change puts it).
|
|
110
|
+
function take() {
|
|
111
|
+
const unit = at;
|
|
112
|
+
at = null;
|
|
113
|
+
node.textContent = "";
|
|
114
|
+
if (document.activeElement === node) node.blur();
|
|
115
|
+
node.remove();
|
|
116
|
+
return unit;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const takesParagraph = (box) => Boolean(box) && accepts(box.getAttribute("data-ge-box-kind") ?? "flow", "paragraph");
|
|
120
|
+
|
|
121
|
+
// A paragraph right after the block (`text` in it: what was typed), the caret at its end; one undo
|
|
122
|
+
// step, the words typed on into it with it (typedInto), undo putting this caret back after the block.
|
|
123
|
+
function paragraphAfter(text = "") {
|
|
124
|
+
const unit = at;
|
|
125
|
+
const box = unit?.parentElement;
|
|
126
|
+
if (!takesParagraph(box)) return null;
|
|
127
|
+
take();
|
|
128
|
+
const paragraph = newHost(document, "paragraph");
|
|
129
|
+
if (text) paragraph.textContent = text;
|
|
130
|
+
struct([box], [], () => {
|
|
131
|
+
unit.after(paragraph);
|
|
132
|
+
return { touched: [paragraph], focus: paragraph, settle: (backwards) => backwards && queueMicrotask(() => place(unit)) };
|
|
133
|
+
}, { typedInto: text ? paragraph : null });
|
|
134
|
+
caretAt(paragraph, "end");
|
|
135
|
+
return paragraph;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Backspace, Delete: the block becomes an empty paragraph in its place, aligned as it was shown, the
|
|
139
|
+
// caret in it, whatever follows it (the user's, 2026-11-09: it was the text after it that took the
|
|
140
|
+
// caret, no paragraph made); where its box takes none, it is deleted as a selected block is. One undo
|
|
141
|
+
// step, which puts this caret back after the block.
|
|
142
|
+
function remove() {
|
|
143
|
+
const unit = at;
|
|
144
|
+
const reason = keepText(unit);
|
|
145
|
+
if (reason) {
|
|
146
|
+
say(reason, "note", 6000);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const box = unit.parentElement;
|
|
150
|
+
if (!takesParagraph(box)) {
|
|
151
|
+
take();
|
|
152
|
+
removeBlocks([unit]);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const parts = withLeads(unit);
|
|
156
|
+
const area = contentOf(box.getBoundingClientRect(), getComputedStyle(box));
|
|
157
|
+
const aligned = alignClass(shownAlign(unit.getBoundingClientRect(), area));
|
|
158
|
+
take();
|
|
159
|
+
const paragraph = newHost(document, "paragraph");
|
|
160
|
+
if (aligned) paragraph.classList.add(aligned);
|
|
161
|
+
struct([box], [], () => {
|
|
162
|
+
parts[0].before(paragraph);
|
|
163
|
+
parts.forEach((part) => part.remove());
|
|
164
|
+
return { touched: [paragraph], focus: paragraph, settle: (backwards) => backwards && queueMicrotask(() => place(unit)) };
|
|
165
|
+
});
|
|
166
|
+
caretAt(paragraph, "start");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// The block of no text right above text block `host` (one a click right of puts this caret after:
|
|
170
|
+
// beside.mjs caretBeside), shown: the block before it in its box, before a list's first item the one
|
|
171
|
+
// before the list; or null. `host` is the first text of its block (a paragraph, a heading, a list's
|
|
172
|
+
// first item), never a table's cell or a code block's text.
|
|
173
|
+
function above(host) {
|
|
174
|
+
if (!isHost(host) || texOnly(host) || !host.matches(UNIT)) return null;
|
|
175
|
+
let node = host;
|
|
176
|
+
while (!withLeads(node)[0].previousElementSibling && /^(UL|OL)$/.test(node.parentElement?.tagName ?? "") && node.parentElement.matches(UNIT)) node = node.parentElement;
|
|
177
|
+
const block = withLeads(node)[0].previousElementSibling;
|
|
178
|
+
return caretBeside(block) && block.parentElement?.hasAttribute("data-ge-box") && block.getClientRects().length > 0 && block.checkVisibility?.() !== false ? block : null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Backspace at the start of text block `host` (typing/, after its own keys): the caret after the
|
|
183
|
+
* block of no text right above it, `host` going when it is empty (one undo step, undo putting the
|
|
184
|
+
* caret back in it, redo this caret after the block), staying when it holds words. Whether it did.
|
|
185
|
+
*/
|
|
186
|
+
function back(host) {
|
|
187
|
+
const unit = above(host);
|
|
188
|
+
if (!unit) return false;
|
|
189
|
+
const node = isEmpty(host) && can(host, "remove") ? removalTarget(host) : null;
|
|
190
|
+
if (node && !keepText(node)) {
|
|
191
|
+
struct([node.parentNode], [host], () => {
|
|
192
|
+
takeOut(node);
|
|
193
|
+
return { touched: [], focus: null, settle: (backwards) => !backwards && queueMicrotask(() => place(unit)) };
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
place(unit);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** ← or ↑ with the caret at the start of text block `host` (editor.mjs): the caret after the block above it, as back(). Whether it did. */
|
|
201
|
+
function up(host, key) {
|
|
202
|
+
if (key !== "ArrowLeft" && key !== "ArrowUp") return false;
|
|
203
|
+
const selection = getSelection();
|
|
204
|
+
if (!selection.rangeCount || !selection.isCollapsed || !atEdge(host, false)) return false;
|
|
205
|
+
const unit = above(host);
|
|
206
|
+
if (!unit) return false;
|
|
207
|
+
place(unit);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ↑ ←: the text before the caret (the block's own last, or the one before it), at its end; ↓ →: the
|
|
212
|
+
// first after it, at its start. Formula blocks are passed over (no text to type in).
|
|
213
|
+
function leave(back) {
|
|
214
|
+
const unit = at;
|
|
215
|
+
const list = hosts().filter((host) => !texOnly(host));
|
|
216
|
+
const before = (host) => unit.contains(host) || Boolean(host.compareDocumentPosition(unit) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
217
|
+
const target = back ? list.filter(before).at(-1) : list.find((host) => !before(host));
|
|
218
|
+
if (!target) return;
|
|
219
|
+
take();
|
|
220
|
+
caretAt(target, back ? "end" : "start");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A key with the caret after a block (editor.mjs onKeydown, first): whether it was this caret's. */
|
|
224
|
+
function key(event) {
|
|
225
|
+
if (!at || event.target !== node) return false;
|
|
226
|
+
if (event.isComposing || event.keyCode === 229) return true; // an input method's: composed here, put in at its end
|
|
227
|
+
const mod = event.ctrlKey || event.metaKey;
|
|
228
|
+
const name = event.key.toLowerCase();
|
|
229
|
+
if (mod && !event.altKey && (name === "z" || name === "y")) {
|
|
230
|
+
event.preventDefault();
|
|
231
|
+
take();
|
|
232
|
+
name === "y" || event.shiftKey ? redo() : undo();
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (mod && !event.altKey && !event.shiftKey && name === "v") {
|
|
236
|
+
paragraphAfter(); // the paste that follows goes into it
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
if (mod && name === "s") return false; // saving, the editor's
|
|
240
|
+
if (event.key === "Backspace" || event.key === "Delete") remove();
|
|
241
|
+
else if (event.key === "Enter") paragraphAfter();
|
|
242
|
+
else if (event.key === "Escape") {
|
|
243
|
+
const unit = take();
|
|
244
|
+
blocks().select(unit);
|
|
245
|
+
} else if (event.key.startsWith("Arrow") && !mod && !event.altKey) leave(event.key === "ArrowUp" || event.key === "ArrowLeft");
|
|
246
|
+
else if (!mod && event.key.length === 1) return true; // a character: typed here, put in a paragraph (input)
|
|
247
|
+
else if (mod || event.key === "Tab") event.preventDefault(); // nothing to format or move: the keys do nothing here
|
|
248
|
+
else return true;
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// What was typed (a key's character, an input method's words once it ends): a paragraph after the
|
|
254
|
+
// block holding it.
|
|
255
|
+
node.addEventListener("input", (event) => {
|
|
256
|
+
if (event.isComposing || !at) return;
|
|
257
|
+
const text = node.textContent;
|
|
258
|
+
node.textContent = ""; // nothing stays in the field, wherever the words go (a box that takes no paragraph: nowhere)
|
|
259
|
+
if (text) paragraphAfter(text);
|
|
260
|
+
});
|
|
261
|
+
node.addEventListener("compositionend", (event) => {
|
|
262
|
+
if (!at) return;
|
|
263
|
+
const text = event.data || node.textContent;
|
|
264
|
+
node.textContent = "";
|
|
265
|
+
if (text) paragraphAfter(text);
|
|
266
|
+
});
|
|
267
|
+
// The focus gone elsewhere on the page (a press, a key that moved it): the caret goes. The window
|
|
268
|
+
// losing it keeps it, as the browser's own caret is kept.
|
|
269
|
+
node.addEventListener("blur", () => {
|
|
270
|
+
if (at && document.hasFocus()) hide();
|
|
271
|
+
});
|
|
272
|
+
// Its block taken off the page (an Agent's write, undo): it goes at once.
|
|
273
|
+
onSettle(() => {
|
|
274
|
+
if (at && !at.isConnected) hide();
|
|
275
|
+
});
|
|
276
|
+
globalThis.__galleryAfter = () => (at && document.activeElement === node ? at : null); // the block the caret stands after (the browser tests)
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
place,
|
|
280
|
+
key,
|
|
281
|
+
back,
|
|
282
|
+
up,
|
|
283
|
+
/** The block the caret stands after, or null. */
|
|
284
|
+
get block() { return at; },
|
|
285
|
+
/** The editor's UI nodes to keep in the top layer when a modal dialog opens. */
|
|
286
|
+
get nodes() { return node.isConnected ? [node] : []; },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ALIGNS, ALIGN_TOGGLES, INDENT_MOST, alignLook, alignOf, aligns, indentLook, indentOf, indents } from "../components/align.mjs";
|
|
2
|
+
import { TOGGLES } from "../components/registry.mjs";
|
|
3
|
+
import { svg } from "../components/icons.mjs";
|
|
4
|
+
import { TABLE_ICONS } from "./table/icons.mjs";
|
|
5
|
+
import { t } from "../../shared/i18n/index.mjs";
|
|
6
|
+
|
|
7
|
+
// 对齐与缩进 (docs/editor.md, "Alignment and indent"; Feishu's 缩进和对齐 ›, its block menu's), shared by
|
|
8
|
+
// the tools that use it (block-menu/, typing/, after/): a
|
|
9
|
+
// paragraph's or a heading's alignment and indent, class switches its component takes (components/
|
|
10
|
+
// align.mjs) that the file keeps and the kit draws; a table's frame takes the alignment alone (it is
|
|
11
|
+
// placed on the page left, centred or right, as a text block's words are), by the same row, named 对齐.
|
|
12
|
+
// The block menu's row (block-menu/), for one block or
|
|
13
|
+
// for several selected that all take them, one undo step; Enter carrying them to the block it makes
|
|
14
|
+
// (core.mjs newBlock: components/align.mjs carry), and Backspace at a block's start taking them off before anything else
|
|
15
|
+
// (typing/: unalign), one at a time: a centred or right-aligned block to the left, then its indent a
|
|
16
|
+
// level less (Feishu's text block: 退对齐, then 反缩进, then 合并到上一块), each one undo step, the caret
|
|
17
|
+
// staying at the start. The same for a paragraph left in a block's place by Backspace after it
|
|
18
|
+
// (after/): it takes the alignment the block showed.
|
|
19
|
+
|
|
20
|
+
// Its icons, in the menus' lines (20 units, the table's alignments beside them): text lines the middle
|
|
21
|
+
// two moved in (the row), and the same with an arrow in (增加缩进) or out (减少缩进) at their left.
|
|
22
|
+
const LINES = '<path d="M3.75 4.75h12.5M9.25 8.5h7M9.25 11.75h7M3.75 15.25h12.5"/>';
|
|
23
|
+
export const ALIGN_ICONS = {
|
|
24
|
+
row: svg('<path d="M3.75 4.75h12.5M7.25 8.5h9M7.25 11.75h9M3.75 15.25h12.5"/>'),
|
|
25
|
+
left: TABLE_ICONS["doc-align-left"],
|
|
26
|
+
center: TABLE_ICONS["doc-align-center"],
|
|
27
|
+
right: TABLE_ICONS["doc-align-right"],
|
|
28
|
+
indent: svg(`${LINES}<path d="m3.75 7.9 2.25 2.25-2.25 2.25"/>`),
|
|
29
|
+
outdent: svg(`${LINES}<path d="M6 7.9 3.75 10.15 6 12.4"/>`),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const LABELS = { left: t("左对齐"), center: t("居中对齐"), right: t("右对齐") };
|
|
33
|
+
|
|
34
|
+
/** Whether a block takes an alignment: its kind's class switches hold it (`kind`: its unit kind). */
|
|
35
|
+
export const alignable = (kind) => aligns(TOGGLES[kind]);
|
|
36
|
+
|
|
37
|
+
/** Whether a block takes an indent too, as a text block does (`kind`: its unit kind). */
|
|
38
|
+
export const indentable = (kind) => indents(TOGGLES[kind]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The block menu's 对齐与缩进 › for `units` (one block, or several selected), when they all take one
|
|
42
|
+
* (kindOf(unit): its unit kind), or none: the three alignments (the one they share checked), then 增加缩进
|
|
43
|
+
* and 减少缩进 (each a level for every one of them, disabled where none can go further), one undo step
|
|
44
|
+
* each (look: the editor's change of classes). Where they do not all take an indent (a table's frame
|
|
45
|
+
* has none) the row is 对齐 › with the three alignments alone.
|
|
46
|
+
*/
|
|
47
|
+
export function alignRows(units, { kindOf, look }) {
|
|
48
|
+
if (units.length === 0 || !units.every((unit) => alignable(kindOf(unit)))) return [];
|
|
49
|
+
const shared = units.every((unit) => alignOf(unit) === alignOf(units[0])) ? alignOf(units[0]) : null;
|
|
50
|
+
const withIndent = units.every((unit) => indentable(kindOf(unit)));
|
|
51
|
+
const levels = units.map(indentOf);
|
|
52
|
+
const indent = (step) => look(units, units.map((unit) => indentLook(Math.max(0, Math.min(INDENT_MOST, indentOf(unit) + step)))), units);
|
|
53
|
+
return [{
|
|
54
|
+
label: withIndent ? t("对齐与缩进") : t("对齐"), icon: ALIGN_ICONS.row,
|
|
55
|
+
submenu: () => [
|
|
56
|
+
...["left", "center", "right"].map((name) => ({ label: LABELS[name], icon: ALIGN_ICONS[name], current: shared === name, run: () => look(units, alignLook(name), units) })),
|
|
57
|
+
...(withIndent ? [
|
|
58
|
+
{ separator: true },
|
|
59
|
+
{ label: t("增加缩进"), icon: ALIGN_ICONS.indent, disabled: levels.every((level) => level >= INDENT_MOST), run: () => indent(1) },
|
|
60
|
+
{ label: t("减少缩进"), icon: ALIGN_ICONS.outdent, disabled: levels.every((level) => level === 0), run: () => indent(-1) },
|
|
61
|
+
] : []),
|
|
62
|
+
],
|
|
63
|
+
}];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Backspace at the start of text block `host` (typing/): its alignment taken off (to the left), else its
|
|
68
|
+
* indent a level less, one undo step (look), the caret where it is. Returns whether it did either.
|
|
69
|
+
*/
|
|
70
|
+
export function unalign(host, look) {
|
|
71
|
+
if (!indentable(host.getAttribute?.("data-ge-unit-kind")) || !ALIGN_TOGGLES.some((name) => host.classList.contains(name))) return false;
|
|
72
|
+
if (alignOf(host) !== "left") return look(host, alignLook("left"));
|
|
73
|
+
return look(host, indentLook(indentOf(host) - 1));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The alignment a block shows in the text area of the box it stands in (`area`: that area's rect,
|
|
78
|
+
* window coordinates): "center" or "right" when it is narrower and stands there, else "left" (a
|
|
79
|
+
* block as wide as the area has none). How a picture, a card or a table set narrower is placed,
|
|
80
|
+
* whatever says so (its class, its margins): what the reader sees.
|
|
81
|
+
*/
|
|
82
|
+
export function shownAlign(rect, area) {
|
|
83
|
+
const left = rect.left - area.left;
|
|
84
|
+
const right = area.right - rect.right;
|
|
85
|
+
if (left <= 2 && right <= 2) return "left";
|
|
86
|
+
if (Math.abs(left - right) <= 2) return "center";
|
|
87
|
+
return right <= 2 ? "right" : "left";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The class that gives a new paragraph the alignment `name`, or null for the left. */
|
|
91
|
+
export const alignClass = (name) => ALIGNS[name] ?? null;
|
|
@@ -6,16 +6,15 @@ import { isHost } from "./core.mjs";
|
|
|
6
6
|
// (selection/).
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Whether a click to the right of a block
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* section).
|
|
9
|
+
* Whether a click to the right of a block (insert/ boxedBeside) puts the caret after it (after/): a
|
|
10
|
+
* boxed block (a code block, a highlight block, a disclosure, a card, a table, a divider…) or a row
|
|
11
|
+
* of columns, as opposed to text and the boxes text flows in (a list, a quote, a section).
|
|
13
12
|
*/
|
|
14
|
-
export const
|
|
13
|
+
export const caretBeside = (unit) => BESIDE.has(unit?.getAttribute?.("data-ge-unit-kind"));
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Whether Enter on a block selected (the first of those selected) puts a new paragraph after them
|
|
18
17
|
* (blocks.mjs, insert.paragraphAfter): a block of no text of its own, those a click to their right
|
|
19
|
-
*
|
|
18
|
+
* puts the caret after and a 选项卡区块; not a text block among them (a formula block, whose editor it opens).
|
|
20
19
|
*/
|
|
21
20
|
export const entersAfter = (unit) => Boolean(unit) && !isHost(unit) && ENTERS_AFTER.has(unit.getAttribute("data-ge-unit-kind"));
|
|
@@ -10,6 +10,7 @@ import { columnOps, menuRow, pageOfUnit, rowBeside, sectionOps } from "../page-o
|
|
|
10
10
|
import { MENU_COLUMNS, MENU_WIDTH, typeGrid } from "../type-grid.mjs";
|
|
11
11
|
import { rememberType, typeItems } from "../slash-menu/index.mjs";
|
|
12
12
|
import { COLOR_ICON, COLOR_WIDTH, colorItems } from "./colors.mjs";
|
|
13
|
+
import { alignRows } from "../align.mjs";
|
|
13
14
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
14
15
|
|
|
15
16
|
// The block menu (docs/editor.md, "Deleting blocks" and "New blocks and conversions"; the card is
|
|
@@ -30,7 +31,8 @@ import { t } from "../../../shared/i18n/index.mjs";
|
|
|
30
31
|
// "分区与分栏 ›" (组成新分区 for neighbours in the page or a Sheet panel, 解散所在分区 for blocks all in
|
|
31
32
|
// one section, 分成 n 栏 for neighbours side by side, a column each) and deleting them all. A block that
|
|
32
33
|
// takes a color (a paragraph, a heading: components/colors.mjs) has "颜色 ›" right below its types,
|
|
33
|
-
// as Notion's (colors.mjs); so do several selected ones that all take one.
|
|
34
|
+
// as Notion's (colors.mjs); so do several selected ones that all take one. A paragraph and a heading
|
|
35
|
+
// have "对齐与缩进 ›" right above it (align.mjs, Feishu's 缩进和对齐 ›), several selected that all take one too. It keeps clear of the block, or stands right of a section's bar.
|
|
34
36
|
// A component whose block has a menu of its own gives it (`menu`): a Sheet tab's is Notion's tab
|
|
35
37
|
// menu, below the tab (components/sheets/page.mjs): 重命名, 编辑图标 and 删除. A block that turns into
|
|
36
38
|
// nothing has its name at the head (`nameOf`), with its mark where its component has one (`iconOf`:
|
|
@@ -147,7 +149,7 @@ export function createBlockMenu({ info, menu, selection, remove, takenWith = (un
|
|
|
147
149
|
}, { columns: COLUMNS });
|
|
148
150
|
items.push({ node: grid, cells: [...grid.querySelectorAll(".ge-type")], columns: COLUMNS }, { separator: true });
|
|
149
151
|
}
|
|
150
|
-
const colors = colorRow(group);
|
|
152
|
+
const colors = [...alignRows(group, { kindOf, look }), ...colorRow(group)];
|
|
151
153
|
items.push(...colors, ...(colors.length > 0 ? [{ separator: true }] : []));
|
|
152
154
|
// Blocks next to each other in the page or a Sheet panel become a section; a selection with gaps does not.
|
|
153
155
|
// A folded heading among them goes in with what it hides (fold/).
|
|
@@ -171,7 +173,8 @@ export function createBlockMenu({ info, menu, selection, remove, takenWith = (un
|
|
|
171
173
|
}
|
|
172
174
|
// A highlight block's icon (添加图标 / 更换图标: components/icon/page.mjs rowBeside) beside its color.
|
|
173
175
|
const icon = rowBeside(target);
|
|
174
|
-
|
|
176
|
+
const aligned = alignRows([target], { kindOf, look });
|
|
177
|
+
if (aligned.length > 0 || colorable(target) || icon) items.push(...aligned, ...(colorable(target) ? colorRow([target]) : []), ...(icon ? [icon] : []), { separator: true });
|
|
175
178
|
// A component's own rows (an image's: components/image/page.mjs; a table's, with its size's
|
|
176
179
|
// 铺满正文宽度…; a card's, a code block's, a Sheet area's), one group, from its page module.
|
|
177
180
|
const own = pageOfUnit(target)?.menuItems?.(target) ?? [];
|
|
@@ -67,7 +67,8 @@ export function createBlocks({ slug = "", units, boxes, active, struct, look, sa
|
|
|
67
67
|
const blockMenu = createBlockMenu({ info, menu, selection, remove: (targets) => remove(targets), takenWith: folds.takenWith, turnInto, convert, turnIntoAll, convertAll, below, belowTypes, add, look, copyLink, linkAhead });
|
|
68
68
|
const handle = createHandle({ info, active, selection, drag, menu, openMenu: blockMenu.open, drawing: () => marquee.drawing, schedule, keeps: (node) => keepsHandle(node) || folds.holds(node), aside: folds.holds, add: (target, where) => { selection.clear(); addNear(target, where); }, notes });
|
|
69
69
|
const remove = createDelete({ info, selection, struct, say, closeMenu, forget: () => handle.forget(), folds });
|
|
70
|
-
// What a component makes with these tools (`blockTools`): a Sheet's tabs as Notion's pills, a click, a drag, their menu, + (components/sheets/pills.mjs)
|
|
70
|
+
// What a component makes with these tools (`blockTools`): a Sheet's tabs as Notion's pills, a click, a drag, their menu, + (components/sheets/pills.mjs);
|
|
71
|
+
// an image's grips and bar while it is the one block selected (components/image/page.mjs), placed in this task's frames (`follow`).
|
|
71
72
|
const parts = pageObjects().flatMap((page) => page.blockTools?.({ active, drag, menu, openMenu: blockMenu.open, struct, say }) ?? []);
|
|
72
73
|
// A press on empty space: a box selection, or a click there (marquee.mjs clears the selection).
|
|
73
74
|
const marquee = createMarquee({
|
|
@@ -91,6 +92,7 @@ export function createBlocks({ slug = "", units, boxes, active, struct, look, sa
|
|
|
91
92
|
step("selection", () => selection.paint(drag.active), () => selection.unpaint());
|
|
92
93
|
step("handle", () => handle.place(), () => handle.hide());
|
|
93
94
|
step("fold", () => folds.place(handle.pointed), () => folds.hide());
|
|
95
|
+
step("parts", () => parts.forEach((part) => part.follow?.(drag.active ? [] : selection.picked)), () => parts.forEach((part) => part.hide()));
|
|
94
96
|
if (drag.active) return true;
|
|
95
97
|
return handle.showing || folds.showing || selection.picked.length > 0 ? FOLLOW : false;
|
|
96
98
|
}
|