@heycys/gallery 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/browser/editor/components/align.mjs +58 -0
- package/browser/editor/components/code/index.mjs +1 -1
- package/browser/editor/components/embed/page.mjs +1 -1
- package/browser/editor/components/file/page.mjs +4 -4
- package/browser/editor/components/heading/index.mjs +7 -6
- package/browser/editor/components/icons.mjs +3 -0
- package/browser/editor/components/image/index.mjs +2 -0
- package/browser/editor/components/image/page.mjs +172 -90
- package/browser/editor/components/installed/index.mjs +6 -2
- package/browser/editor/components/installed/page.mjs +80 -13
- package/browser/editor/components/media/icons.mjs +13 -8
- package/browser/editor/components/media/page.mjs +5 -3
- package/browser/editor/components/media/toolbar.mjs +12 -3
- package/browser/editor/components/paragraph/index.mjs +4 -3
- package/browser/editor/components/registry.mjs +17 -5
- package/browser/editor/components/table/index.mjs +4 -2
- package/browser/editor/page/after/index.mjs +288 -0
- package/browser/editor/page/align.mjs +91 -0
- package/browser/editor/page/beside.mjs +5 -6
- package/browser/editor/page/block-menu/index.mjs +6 -3
- package/browser/editor/page/blocks.mjs +3 -1
- package/browser/editor/page/board/hint.mjs +64 -27
- package/browser/editor/page/board/index.mjs +160 -71
- package/browser/editor/page/board/motion.mjs +177 -0
- package/browser/editor/page/board/settings.mjs +4 -3
- package/browser/editor/page/clipboard/index.mjs +10 -3
- package/browser/editor/page/clipboard/own/blocks.mjs +3 -4
- package/browser/editor/page/clipboard/own/describe.mjs +6 -2
- package/browser/editor/page/commit.mjs +2 -2
- package/browser/editor/page/core.mjs +3 -1
- package/browser/editor/page/custom-page.mjs +15 -11
- package/browser/editor/page/editor.mjs +31 -14
- package/browser/editor/page/history.mjs +19 -4
- package/browser/editor/page/insert/index.mjs +20 -22
- package/browser/editor/page/menu.mjs +15 -7
- package/browser/editor/page/page-objects.mjs +3 -7
- package/browser/editor/page/present-links.mjs +5 -4
- package/browser/editor/page/present-shell.mjs +63 -0
- package/browser/editor/page/selection/index.mjs +10 -2
- package/browser/editor/page/table/binding.mjs +87 -0
- package/browser/editor/page/table/icons.mjs +0 -2
- package/browser/editor/page/table/index.mjs +10 -14
- package/browser/editor/page/table/views.mjs +5 -52
- package/browser/editor/page/table/widths.mjs +82 -10
- package/browser/editor/page/typing/index.mjs +44 -12
- package/browser/editor/page/typing/leave.mjs +19 -5
- package/browser/shared/block-packs.mjs +23 -8
- package/browser/shared/component-catalog.mjs +15 -8
- package/browser/shared/component-source.mjs +3 -1
- package/browser/shared/components/heading.mjs +2 -1
- package/browser/shared/components/installed.mjs +1 -1
- package/browser/shared/components/page.mjs +2 -0
- package/browser/shared/components/paragraph.mjs +8 -1
- package/browser/shared/components/table.mjs +1 -0
- package/browser/shared/emoji/line-icons.mjs +37 -0
- package/browser/shared/http.mjs +9 -0
- package/browser/shared/i18n/en.mjs +40 -17
- package/browser/shared/outline.mjs +30 -2
- package/browser/shared/pack-manifest.mjs +12 -1
- package/browser/shared/pages.mjs +169 -57
- package/browser/shared/pinyin.mjs +92 -0
- package/browser/shared/present-step.mjs +17 -0
- package/browser/shared/sidebar-head.mjs +70 -0
- package/browser/shared/web-links.mjs +5 -0
- package/dist/chunk-2OHANJ6Q.mjs +2 -0
- package/dist/{chunk-QOUEFUCH.mjs → chunk-3MBRL2PM.mjs} +1 -1
- package/dist/{chunk-NWR2LT5B.mjs → chunk-4OKWZY5E.mjs} +1 -1
- package/dist/{chunk-RMECZA2K.mjs → chunk-5K44HLTZ.mjs} +1 -1
- package/dist/{chunk-NFMCP2HI.mjs → chunk-5XA2ZBAE.mjs} +2 -2
- package/dist/{chunk-OUUP3WKT.mjs → chunk-67N2QJYT.mjs} +1 -1
- package/dist/chunk-6KBYRA2C.mjs +2 -0
- package/dist/{chunk-SX4LJYT6.mjs → chunk-6N3PX4XX.mjs} +1 -1
- package/dist/{chunk-RFDZXFJF.mjs → chunk-7JDX4E65.mjs} +1 -1
- package/dist/chunk-7M2BQQGZ.mjs +2 -0
- package/dist/{chunk-ZUNVAXLH.mjs → chunk-7OG26PGF.mjs} +1 -1
- package/dist/{chunk-B4P2CM2J.mjs → chunk-AHN3NFKJ.mjs} +1 -1
- package/dist/chunk-ALZ6ZENZ.mjs +3 -0
- package/dist/{chunk-UGWYKHBL.mjs → chunk-AR5D57ZD.mjs} +2 -2
- package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
- package/dist/{chunk-HLYV7RIH.mjs → chunk-CMA72MAG.mjs} +1 -1
- package/dist/{chunk-D4CKRD3V.mjs → chunk-DAZL73VK.mjs} +2 -2
- package/dist/{chunk-6VTU6KEO.mjs → chunk-DJISHPUL.mjs} +1 -1
- package/dist/{chunk-BPL64HGA.mjs → chunk-EAIFITAV.mjs} +1 -1
- package/dist/chunk-EP25HDAJ.mjs +2 -0
- package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
- package/dist/{chunk-3KJHLHUO.mjs → chunk-F6HVDZR4.mjs} +1 -1
- package/dist/{chunk-3NJACR4W.mjs → chunk-FDMAXMQA.mjs} +1 -1
- package/dist/chunk-FETSREUN.mjs +5 -0
- package/dist/{chunk-RKPWLGZ3.mjs → chunk-GHOYKMQ3.mjs} +1 -1
- package/dist/chunk-HDWTZ763.mjs +12 -0
- package/dist/{chunk-VTKNEYT3.mjs → chunk-HPOI4ZX7.mjs} +1 -1
- package/dist/{chunk-OW5NWPOJ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
- package/dist/{chunk-V6RIYQZZ.mjs → chunk-ITOH5EGO.mjs} +1 -1
- package/dist/{chunk-ZWPMCK7P.mjs → chunk-IV64FFI4.mjs} +1 -1
- package/dist/chunk-J43Z6LSS.mjs +2 -0
- package/dist/chunk-J5PUIV3Y.mjs +4 -0
- package/dist/{chunk-KTKHRC6H.mjs → chunk-JA7G53PC.mjs} +1 -1
- package/dist/{chunk-NWFBE2YA.mjs → chunk-JLTZF4LA.mjs} +1 -1
- package/dist/{chunk-GY3FAUVM.mjs → chunk-JZT7I2OJ.mjs} +1 -1
- package/dist/{chunk-QKXMPUXC.mjs → chunk-K5XATEOO.mjs} +1 -1
- package/dist/chunk-L2SUIS5S.mjs +2 -0
- package/dist/{chunk-TEZAWDSQ.mjs → chunk-LM4XFQHZ.mjs} +1 -1
- package/dist/{chunk-5ZJA7CXT.mjs → chunk-MN7BZL4R.mjs} +1 -1
- package/dist/chunk-MTYYHM2G.mjs +15 -0
- package/dist/{chunk-HMYTM3GT.mjs → chunk-MXUBKDRO.mjs} +1 -1
- package/dist/{chunk-MHW7ZKOU.mjs → chunk-OEXWRJME.mjs} +1 -1
- package/dist/{chunk-CRJEG7MT.mjs → chunk-OFHXGWOX.mjs} +1 -1
- package/dist/chunk-ORID3AE7.mjs +166 -0
- package/dist/{chunk-32ZUEFZ7.mjs → chunk-PGDE24N5.mjs} +1 -1
- package/dist/{chunk-UNOYVIYR.mjs → chunk-POTA2FV4.mjs} +1 -1
- package/dist/{chunk-N7AZZLGT.mjs → chunk-QE5RG76S.mjs} +18 -17
- package/dist/{chunk-N76HWBA3.mjs → chunk-R2ZDLJYT.mjs} +1 -1
- package/dist/chunk-SWUTTZCN.mjs +2 -0
- package/dist/{chunk-POQSE4C2.mjs → chunk-TR7TWGHB.mjs} +1 -1
- package/dist/chunk-U5XL7D6A.mjs +3 -0
- package/dist/{chunk-PVER7LSC.mjs → chunk-U7NZI6N4.mjs} +2 -2
- package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
- package/dist/chunk-UJQJKNGK.mjs +2 -0
- package/dist/chunk-UYVCQK3H.mjs +22 -0
- package/dist/{chunk-FJYN7RTH.mjs → chunk-VFX2KNHP.mjs} +1 -1
- package/dist/{chunk-UBBRTJ2R.mjs → chunk-VS7JIGJW.mjs} +1 -1
- package/dist/{chunk-NVRNLK25.mjs → chunk-W7665ZEC.mjs} +2 -2
- package/dist/{chunk-NG375ILQ.mjs → chunk-WQDQWCX5.mjs} +1 -1
- package/dist/chunk-YLPSMDIS.mjs +5 -0
- package/dist/{chunk-VXACTLO3.mjs → chunk-YNAWBAYN.mjs} +1 -1
- package/dist/{chunk-ZB4H7ZN4.mjs → chunk-YROX2O22.mjs} +1 -1
- package/dist/{chunk-YQGT4AB2.mjs → chunk-ZDLPUOAG.mjs} +1 -1
- package/dist/chunk-ZQS6H473.mjs +16 -0
- package/dist/{chunk-OJKMINIU.mjs → chunk-ZWWC4KLF.mjs} +15 -15
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +5 -5
- package/kit/code.js +15 -15
- package/kit/report.css +120 -41
- package/kit/runtime.js +45 -7
- package/kit/tables.js +2 -0
- package/kit/ui.js +2 -0
- package/package.json +1 -1
- package/packs/gallery/chart-view/kit.js +3 -3
- package/packs/gallery/chart-view/writing.md +1 -1
- package/packs/gallery/countdown/component.json +1 -1
- package/packs/gallery/countdown/kit.js +89 -29
- package/packs/gallery/countdown/preview.svg +1 -1
- package/packs/gallery/countdown/style.css +88 -100
- package/packs/gallery/countdown/writing.md +2 -2
- package/packs/gallery/habit/component.json +3 -3
- package/packs/gallery/habit/kit.js +114 -75
- package/packs/gallery/habit/preview.svg +1 -1
- package/packs/gallery/habit/style.css +133 -205
- package/packs/gallery/habit/writing.md +2 -2
- package/packs/gallery/heatmap/component.json +2 -2
- package/packs/gallery/heatmap/kit.js +335 -173
- package/packs/gallery/heatmap/preview.svg +1 -1
- package/packs/gallery/heatmap/style.css +158 -170
- package/packs/gallery/heatmap/writing.md +2 -2
- package/packs/gallery/kanban/kit.js +3 -3
- package/packs/gallery/kanban/writing.md +1 -1
- package/packs/gallery/metric/kit.js +3 -3
- package/packs/gallery/metric/writing.md +1 -1
- package/packs/gallery/note/component.json +3 -3
- package/packs/gallery/note/kit.js +40 -13
- package/packs/gallery/note/preview.svg +1 -1
- package/packs/gallery/note/style.css +51 -30
- package/packs/gallery/note/writing.md +2 -2
- package/packs/gallery/recent/component.json +2 -2
- package/packs/gallery/recent/kit.js +141 -86
- package/packs/gallery/recent/preview.svg +1 -1
- package/packs/gallery/recent/style.css +53 -126
- package/packs/gallery/recent/writing.md +2 -2
- package/packs/gallery/static/component.json +2 -2
- package/packs/gallery/static/preview.svg +1 -1
- package/packs/gallery/static/style.css +99 -66
- package/packs/gallery/static/writing.md +2 -2
- package/packs/gallery/stats/component.json +3 -3
- package/packs/gallery/stats/kit.js +253 -72
- package/packs/gallery/stats/preview.svg +1 -1
- package/packs/gallery/stats/style.css +64 -142
- package/packs/gallery/stats/writing.md +2 -2
- package/packs/gallery/today/component.json +2 -2
- package/packs/gallery/today/kit.js +62 -27
- package/packs/gallery/today/preview.svg +1 -1
- package/packs/gallery/today/style.css +101 -72
- package/packs/gallery/today/writing.md +2 -2
- package/packs/gallery/todos/component.json +1 -1
- package/packs/gallery/todos/kit.js +218 -71
- package/packs/gallery/todos/preview.svg +1 -1
- package/packs/gallery/todos/style.css +170 -223
- package/packs/gallery/todos/writing.md +1 -1
- package/packs/gallery/weather/component.json +2 -2
- package/packs/gallery/weather/kit.js +165 -90
- package/packs/gallery/weather/preview.svg +1 -1
- package/packs/gallery/weather/style.css +100 -218
- package/packs/gallery/weather/writing.md +1 -1
- package/packs/gallery-soft/countdown/component.json +18 -0
- package/packs/gallery-soft/countdown/example.html +3 -0
- package/packs/gallery-soft/countdown/kit.js +206 -0
- package/packs/gallery-soft/countdown/preview.svg +1 -0
- package/packs/gallery-soft/countdown/style.css +211 -0
- package/packs/gallery-soft/countdown/template.html +3 -0
- package/packs/gallery-soft/countdown/writing.md +24 -0
- package/packs/gallery-soft/habit/component.json +14 -0
- package/packs/gallery-soft/habit/example.html +3 -0
- package/packs/gallery-soft/habit/kit.js +366 -0
- package/packs/gallery-soft/habit/preview.svg +1 -0
- package/packs/gallery-soft/habit/style.css +251 -0
- package/packs/gallery-soft/habit/template.html +3 -0
- package/packs/gallery-soft/habit/writing.md +23 -0
- package/packs/gallery-soft/heatmap/component.json +18 -0
- package/packs/gallery-soft/heatmap/example.html +3 -0
- package/packs/gallery-soft/heatmap/kit.js +498 -0
- package/packs/gallery-soft/heatmap/preview.svg +1 -0
- package/packs/gallery-soft/heatmap/style.css +181 -0
- package/packs/gallery-soft/heatmap/template.html +3 -0
- package/packs/gallery-soft/heatmap/writing.md +23 -0
- package/packs/gallery-soft/note/component.json +14 -0
- package/packs/gallery-soft/note/example.html +3 -0
- package/packs/gallery-soft/note/kit.js +162 -0
- package/packs/gallery-soft/note/preview.svg +1 -0
- package/packs/gallery-soft/note/style.css +148 -0
- package/packs/gallery-soft/note/template.html +3 -0
- package/packs/gallery-soft/note/writing.md +20 -0
- package/packs/gallery-soft/recent/component.json +13 -0
- package/packs/gallery-soft/recent/example.html +1 -0
- package/packs/gallery-soft/recent/kit.js +335 -0
- package/packs/gallery-soft/recent/preview.svg +1 -0
- package/packs/gallery-soft/recent/style.css +160 -0
- package/packs/gallery-soft/recent/template.html +1 -0
- package/packs/gallery-soft/recent/writing.md +17 -0
- package/packs/gallery-soft/static/component.json +13 -0
- package/packs/gallery-soft/static/example.html +12 -0
- package/packs/gallery-soft/static/preview.svg +1 -0
- package/packs/gallery-soft/static/style.css +186 -0
- package/packs/gallery-soft/static/template.html +1 -0
- package/packs/gallery-soft/static/writing.md +20 -0
- package/packs/gallery-soft/stats/component.json +13 -0
- package/packs/gallery-soft/stats/example.html +1 -0
- package/packs/gallery-soft/stats/kit.js +341 -0
- package/packs/gallery-soft/stats/preview.svg +1 -0
- package/packs/gallery-soft/stats/style.css +119 -0
- package/packs/gallery-soft/stats/template.html +1 -0
- package/packs/gallery-soft/stats/writing.md +13 -0
- package/packs/gallery-soft/today/component.json +12 -0
- package/packs/gallery-soft/today/example.html +1 -0
- package/packs/gallery-soft/today/kit.js +222 -0
- package/packs/gallery-soft/today/preview.svg +1 -0
- package/packs/gallery-soft/today/style.css +178 -0
- package/packs/gallery-soft/today/template.html +1 -0
- package/packs/gallery-soft/today/writing.md +16 -0
- package/packs/gallery-soft/todos/component.json +19 -0
- package/packs/gallery-soft/todos/example.html +3 -0
- package/packs/gallery-soft/todos/kit.js +527 -0
- package/packs/gallery-soft/todos/preview.svg +1 -0
- package/packs/gallery-soft/todos/style.css +212 -0
- package/packs/gallery-soft/todos/template.html +3 -0
- package/packs/gallery-soft/todos/writing.md +32 -0
- package/packs/gallery-soft/weather/component.json +19 -0
- package/packs/gallery-soft/weather/example.html +3 -0
- package/packs/gallery-soft/weather/kit.js +411 -0
- package/packs/gallery-soft/weather/preview.svg +1 -0
- package/packs/gallery-soft/weather/style.css +185 -0
- package/packs/gallery-soft/weather/template.html +3 -0
- package/packs/gallery-soft/weather/writing.md +28 -0
- package/release.json +9 -9
- package/skill/SKILL.md +12 -12
- package/skill/references/building-blocks.md +4 -4
- package/skill/references/pages-and-data.md +13 -9
- package/skill/references/writing-html.md +2 -0
- package/web/assets/index-BM_K-Auu.css +1 -0
- package/web/assets/index-DRslOBiv.js +15 -0
- package/web/index.html +2 -2
- package/dist/chunk-2NRBY6AJ.mjs +0 -2
- package/dist/chunk-35NQ4PLQ.mjs +0 -2
- package/dist/chunk-4AV456RQ.mjs +0 -2
- package/dist/chunk-5XYHDBJ5.mjs +0 -2
- package/dist/chunk-6A53MZYZ.mjs +0 -2
- package/dist/chunk-6EXH3ORZ.mjs +0 -166
- package/dist/chunk-BVEFGE4B.mjs +0 -2
- package/dist/chunk-D4EURAX4.mjs +0 -4
- package/dist/chunk-DQCE7TJY.mjs +0 -5
- package/dist/chunk-FWNRPMCJ.mjs +0 -12
- package/dist/chunk-FWW4CSAZ.mjs +0 -2
- package/dist/chunk-JVHHHGEA.mjs +0 -3
- package/dist/chunk-PZDLJJTJ.mjs +0 -3
- package/dist/chunk-SXHBYZST.mjs +0 -16
- package/dist/chunk-UFNYFFKO.mjs +0 -20
- package/dist/chunk-UKSHDZZ6.mjs +0 -2
- package/dist/chunk-VJPFVNNW.mjs +0 -15
- package/dist/chunk-VS3ZMVGX.mjs +0 -5
- package/web/assets/index-BK2e_07w.js +0 -13
- package/web/assets/index-CWFI83mF.css +0 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { GAP, GRID, cellAt, fitHeight, gridFor, moved, pushDown, sizeFor, spanOf } from "../../../shared/board-places.mjs";
|
|
2
2
|
import { cellsOf } from "../../../shared/card-size.mjs";
|
|
3
3
|
import { language, t } from "../../../shared/i18n/index.mjs";
|
|
4
|
-
import { BLOCK_ICON, svg } from "../../components/icons.mjs";
|
|
4
|
+
import { BLOCK_ICON, SETTINGS_ICON, svg } from "../../components/icons.mjs";
|
|
5
5
|
import { instanceAround, instanceOf, packageOf } from "../../components/instances.mjs";
|
|
6
|
-
import { edgeScroll } from "../edge-scroll.mjs";
|
|
7
6
|
import { gestureLock } from "../gesture.mjs";
|
|
8
7
|
import { ICONS, createMenu } from "../menu.mjs";
|
|
9
8
|
import { say } from "../notices.mjs";
|
|
@@ -12,7 +11,8 @@ import { tooltip } from "../tooltip.mjs";
|
|
|
12
11
|
import { addStyle, element, mount } from "../ui.mjs";
|
|
13
12
|
import { createClient } from "./client.mjs";
|
|
14
13
|
import { createHistory } from "./history.mjs";
|
|
15
|
-
import {
|
|
14
|
+
import { createEdgeScroll, createSettle, glide, isGliding, stopGlide } from "./motion.mjs";
|
|
15
|
+
import { showBlank, showHint, showNews } from "./hint.mjs";
|
|
16
16
|
import { closeSettings, formOpen, openSettings } from "./settings.mjs";
|
|
17
17
|
|
|
18
18
|
// A custom page's board edited (docs/render.md "A custom page"; the plan's §5.3, the spec §6 and §8):
|
|
@@ -31,20 +31,33 @@ import { closeSettings, formOpen, openSettings } from "./settings.mjs";
|
|
|
31
31
|
// one that moves less, or a key (Enter, Space) on the grip, opens the card's menu: what is dragged
|
|
32
32
|
// and selected in a card is its block's (DocKit.drag never moves a card). A bare card (its block's
|
|
33
33
|
// card.bare) shows where it stands by a dashed line of the guides' gray.
|
|
34
|
-
// -
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
34
|
+
// - The board while a card is moved or resized (the user's word of 2026-11-09): no landing shadow
|
|
35
|
+
// under it; the board's lattice drawn as faint grey dots, one where the gaps between four cells
|
|
36
|
+
// cross (the board's outer edge too), from the gesture's start to its end (.ge-board-dots, light
|
|
37
|
+
// and dark), and the card snapping to the cells it would land at as it comes within SNAP px of them,
|
|
38
|
+
// each axis on its own: where it snaps is where a drop puts it.
|
|
39
|
+
// - Its motion (./motion.mjs; the user's word of 2026-11-09, an iPhone's home-screen widgets): the card
|
|
40
|
+
// held follows the pointer at once and settles onto the cells it snaps to (or off them) on a spring in
|
|
41
|
+
// SETTLE ms, never a jump; the cards making way, the card let go (to its cells, or back on Escape), a
|
|
42
|
+
// resize's and 大小 ›'s, an undo's and a redo's glide from where they were seen to their new place on
|
|
43
|
+
// that spring (FLIP, GLIDE ms), each starting again from where it is seen when the target changes under
|
|
44
|
+
// it; near the window's top or bottom edge the page scrolls, timed by the frames, faster nearer the edge
|
|
45
|
+
// and never past FASTEST px a second. prefers-reduced-motion: no glide or settling.
|
|
46
|
+
// - Moving. The card follows the pointer, and where it would go is the whole cells nearest to it
|
|
47
|
+
// (shared/board-places.mjs cellAt), which the card itself is drawn at (DocBoard.draft), moved off
|
|
48
|
+
// them by the pointer's offset but within SNAP px of them on an axis, where it stands on them; the
|
|
49
|
+
// cards it would meet are drawn pushed down under it (pushDown, the service's own rule), never up.
|
|
50
|
+
// Escape, or the pointer taken away, puts all back; a drop sends `place`, and the board stands as
|
|
51
|
+
// the service answers. The rows above a board's first card are not drawn (DocBoard.skipped): the
|
|
52
|
+
// pointer's row is the drawn one plus the rows skipped as the drag began, so that a card let go
|
|
53
|
+
// where it was writes nothing and one held above the first row goes above it (drawn at the top, the
|
|
54
|
+
// others under it).
|
|
42
55
|
// - Resizing. A card whose block says card.resize has a grip at its bottom-right: it follows the
|
|
43
|
-
// pointer,
|
|
44
|
-
// between card.min and card.max (sizeFor), never past the row's end;
|
|
45
|
-
// gets wider (its height is its content's); one whose manifest frees it
|
|
46
|
-
// whole size. The menu's 大小 › gives the listed sizes (the common ones), in a
|
|
47
|
-
// words alone, only as wide as they are.
|
|
56
|
+
// pointer, snapping to the size a drop gives (within SNAP px of its width or height), the nearest of
|
|
57
|
+
// card.sizes, else whole cells between card.min and card.max (sizeFor), never past the row's end;
|
|
58
|
+
// one of fit content only gets wider (its height is its content's); one whose manifest frees it
|
|
59
|
+
// (`any`) reaches every whole size. The menu's 大小 › gives the listed sizes (the common ones), in a
|
|
60
|
+
// card of rows of words alone, only as wide as they are.
|
|
48
61
|
// - The card's menu (the grip's click): 设置… (a block with settings: ./settings.mjs, written by the
|
|
49
62
|
// service's `settings`), 大小 ›, 删除 (`remove`).
|
|
50
63
|
// - 添加自定义块: in a workbench (`hosted`) the workbench's top bar has its button, which asks by the
|
|
@@ -53,7 +66,8 @@ import { closeSettings, formOpen, openSettings } from "./settings.mjs";
|
|
|
53
66
|
// centred, and adds to the page's FIRST board; a page open alone in a tab has the button of its own
|
|
54
67
|
// at the top right of each board, above it. The library (the service's GET library: the blocks
|
|
55
68
|
// whose places hold page and are not turned off, with their previews, Gallery's own not yet in the
|
|
56
|
-
// workspace too, which the service puts in)
|
|
69
|
+
// workspace too, which the service puts in), in a section of each pack under its name (no pack's
|
|
70
|
+
// name in grey after a block's): the card put at the first free spot by its block's
|
|
57
71
|
// default size (`add`); the page is then shown again (the host's hidden-frame swap), which brings
|
|
58
72
|
// the block's script and style.
|
|
59
73
|
// - A block's own write (context.edit, the kit's cancellable doc-component-edit): taken here, its
|
|
@@ -88,12 +102,14 @@ import { closeSettings, formOpen, openSettings } from "./settings.mjs";
|
|
|
88
102
|
const BOARD = ".gallery-board";
|
|
89
103
|
const CARD = ".gallery-card";
|
|
90
104
|
const THRESHOLD = 4; // px a press moves before it is a drag
|
|
105
|
+
const SNAP = 14; // px from the place (or the size) a drop gives within which a moved (or resized) card stands on it
|
|
106
|
+
const DOT = 3; // px of room around each of the board's dots while a card is moved or resized (their tile's corner)
|
|
91
107
|
const GRIP = { size: 28, inset: 6 }; // the grip's box, and its room from the card's top and right edges (inside the card)
|
|
92
108
|
const FIELDS = "input, textarea, select, [contenteditable]:not([contenteditable=false])";
|
|
93
109
|
|
|
94
110
|
const DOTS = '<svg viewBox="0 0 10 16" aria-hidden="true"><circle cx="2.5" cy="2" r="1.25"/><circle cx="7.5" cy="2" r="1.25"/><circle cx="2.5" cy="8" r="1.25"/><circle cx="7.5" cy="8" r="1.25"/><circle cx="2.5" cy="14" r="1.25"/><circle cx="7.5" cy="14" r="1.25"/></svg>';
|
|
95
111
|
const CORNER = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M13 6.5 6.5 13M13 10.5 10.5 13"/></svg>';
|
|
96
|
-
const GEAR =
|
|
112
|
+
const GEAR = SETTINGS_ICON;
|
|
97
113
|
const SIZE = svg('<rect x="3.25" y="3.25" width="13.5" height="13.5" rx="2.5"/><path d="M10 3.25v13.5M3.25 10H10"/>');
|
|
98
114
|
const BLOCK = BLOCK_ICON;
|
|
99
115
|
|
|
@@ -118,10 +134,21 @@ const STYLE = `
|
|
|
118
134
|
.ge-board-add:hover { background: var(--ge-hover); color: var(--ge-tx); }
|
|
119
135
|
.ge-board-add:active, .ge-board-add[data-pressed] { background: var(--ge-press); color: var(--ge-tx); }
|
|
120
136
|
.ge-board-add svg { display: block; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.gallery-board > .
|
|
124
|
-
|
|
137
|
+
/* The board's lattice while a card is moved or resized: a faint grey dot where the gaps between four cells cross, under the cards (first in the board, which stands as their containing block meanwhile), placed by dotsOn. */
|
|
138
|
+
.gallery-board[data-ge-dotted] { position: relative; }
|
|
139
|
+
.gallery-board > .ge-board-dots { position: absolute; pointer-events: none; background-image: radial-gradient(circle at ${DOT}px ${DOT}px, light-dark(rgb(55 53 47 / 24%), rgb(255 255 255 / 20%)) 0 1.1px, transparent 1.6px); background-repeat: repeat; animation: ge-board-dots-in .14s ease-out; }
|
|
140
|
+
@keyframes ge-board-dots-in { from { opacity: 0; } }
|
|
141
|
+
@media (prefers-reduced-motion: reduce) { .gallery-board > .ge-board-dots { animation: none; } }
|
|
142
|
+
/* The card held lifts: its own shadow kept as it is, layer for layer, and a soft lift added after it, so
|
|
143
|
+
that only the lift eases in and out (a menu's shadow in its place made each of the card's layers turn
|
|
144
|
+
into another's, the shadow swelling and shrinking on the way: the user, 2026-11-09); a frameless card
|
|
145
|
+
takes the lift alone. One let go stays over the others while it glides to its cells. */
|
|
146
|
+
.gallery-board { --ge-board-lift: 0 10px 28px -10px light-dark(rgb(15 15 15 / 16%), rgb(0 0 0 / 55%)), 0 3px 10px -4px light-dark(rgb(15 15 15 / 8%), rgb(0 0 0 / 30%)); }
|
|
147
|
+
.gallery-board > .gallery-card[data-ge-lifted], .gallery-board > .gallery-card[data-ge-landing] { transition: box-shadow .2s ease-out; }
|
|
148
|
+
.gallery-board > .gallery-card[data-ge-lifted] { z-index: 3; box-shadow: var(--gallery-card-shadow), var(--ge-board-lift); cursor: grabbing; }
|
|
149
|
+
.gallery-board > .gallery-card[data-ge-bare][data-ge-lifted] { box-shadow: var(--ge-board-lift); }
|
|
150
|
+
.gallery-board > .gallery-card[data-ge-sizing], .gallery-board > .gallery-card[data-ge-landing] { z-index: 3; }
|
|
151
|
+
@media (prefers-reduced-motion: reduce) { .gallery-board > .gallery-card[data-ge-lifted], .gallery-board > .gallery-card[data-ge-landing] { transition: none; } }
|
|
125
152
|
.gallery-board > .gallery-card[data-ge-bare][data-ge-hover] { outline: 1px dashed var(--ge-guide); outline-offset: 2px; }
|
|
126
153
|
/* The card the grip stands in: a head's quiet note in its corner fades (the kit's --gallery-grip-shown, board.css). */
|
|
127
154
|
.gallery-board > .gallery-card[data-ge-hover] { --gallery-grip-shown: 1; }
|
|
@@ -218,7 +245,7 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
218
245
|
};
|
|
219
246
|
const drawn = (card) => globalThis.DocBoard?.drawn(card) ?? cellsOf(card);
|
|
220
247
|
// How many rows above its first card the board does not draw (the kit's DocBoard.skipped): its drawn row
|
|
221
|
-
// r is the file's row r + skipped, which
|
|
248
|
+
// r is the file's row r + skipped, which a dragged card's place and a drop are reckoned by.
|
|
222
249
|
const skipped = (board) => globalThis.DocBoard?.skipped?.(board) ?? 0;
|
|
223
250
|
const draft = (board, cells) => globalThis.DocBoard?.draft(board, cells);
|
|
224
251
|
|
|
@@ -226,16 +253,20 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
226
253
|
// has not, or one the page has that it does not name, is someone else's doing: the page is shown again.
|
|
227
254
|
function took(answer, { own = [] } = {}) {
|
|
228
255
|
const named = new Set();
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
256
|
+
// Where the page drew a card elsewhere than the service put it, it glides there (a held card follows the pointer).
|
|
257
|
+
glide(allCards(), () => {
|
|
258
|
+
for (const card of answer.cards ?? []) {
|
|
259
|
+
named.add(card.id);
|
|
260
|
+
const node = cardById(card.id);
|
|
261
|
+
if (!node) {
|
|
262
|
+
if (!own.includes(card.id) && !leaving.has(card.id)) wantSwap(); // one the page took out meanwhile, its remove asked, is its own
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
setCells(node, card);
|
|
266
|
+
node.dataset.geBase = card.base;
|
|
235
267
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
268
|
+
if (!gesture) for (const board of boards()) globalThis.DocBoard?.layout(board);
|
|
269
|
+
}, { still: gesture?.card ?? null, settled: placeAgain });
|
|
239
270
|
if (allCards().some((card) => !named.has(card.dataset.cardId))) wantSwap();
|
|
240
271
|
history.know(basesNow());
|
|
241
272
|
}
|
|
@@ -403,7 +434,8 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
403
434
|
if (resizable(card) && sizes.length > 1) {
|
|
404
435
|
items.push({
|
|
405
436
|
label: t("大小"), icon: SIZE, icons: false, compact: true,
|
|
406
|
-
|
|
437
|
+
// By width, then height (the user's word of 2026-11-09; the manifest lists its default first).
|
|
438
|
+
submenu: () => [...sizes].sort(([aw, ah], [bw, bh]) => aw - bw || ah - bh).map(([w, h]) => ({ label: `${w} × ${h}`, current: w === cells.w && h === cells.h, run: () => layOut(board, () => placeCard(card, { ...cells, w, h }), { lift: card }) })),
|
|
407
439
|
});
|
|
408
440
|
}
|
|
409
441
|
if (items.length) items.push({ separator: true });
|
|
@@ -435,37 +467,58 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
435
467
|
}, true);
|
|
436
468
|
|
|
437
469
|
// ---------- moving and resizing ----------
|
|
438
|
-
const
|
|
439
|
-
//
|
|
440
|
-
//
|
|
470
|
+
const dots = element("div", { class: "ge-board-dots", "data-doc-dev": "", "aria-hidden": "true" });
|
|
471
|
+
// The board's dots over all it draws now (its rows grow as a draft puts a card lower), each at a point of
|
|
472
|
+
// its lattice: where the gaps between four cells cross, and as far out on its edges, a DOT px room
|
|
473
|
+
// around each (the tile's corner the gradient draws it in).
|
|
474
|
+
const dotsOn = (board) => {
|
|
475
|
+
const { rect, step } = geometry(board);
|
|
476
|
+
const box = board.getBoundingClientRect();
|
|
477
|
+
const columns = Math.round((rect.right - rect.left + GAP) / step);
|
|
478
|
+
const rows = Math.max(1, Math.round((board.clientHeight + GAP) / step));
|
|
479
|
+
Object.assign(dots.style, {
|
|
480
|
+
left: `${rect.left - box.left - board.clientLeft - GAP / 2 - DOT}px`, top: `${-GAP / 2 - DOT}px`,
|
|
481
|
+
width: `${columns * step + 2 * DOT}px`, height: `${rows * step + 2 * DOT}px`, backgroundSize: `${step}px ${step}px`,
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
// Each frame of a gesture: near the window's top or bottom edge the page scrolls (./motion.mjs
|
|
485
|
+
// createEdgeScroll: timed by the frames, faster nearer the edge, never past FASTEST px a second) and the
|
|
486
|
+
// gesture follows what came into view; a card settling onto (or off) what it snaps to is drawn again.
|
|
441
487
|
let pointer = null;
|
|
442
488
|
let scrolling = 0;
|
|
443
|
-
const
|
|
489
|
+
const edge = createEdgeScroll();
|
|
490
|
+
const frame = () => {
|
|
444
491
|
scrolling = 0;
|
|
445
492
|
if (!gesture || !pointer) return;
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
// The spot at cells of the file, drawn where its board draws them.
|
|
450
|
-
const placeSpot = (board, cells) => {
|
|
451
|
-
for (const axis of ["x", "y", "w", "h"]) spot.style.setProperty(`--gallery-${axis}`, String(axis === "y" ? cells.y - skipped(board) : cells[axis]));
|
|
493
|
+
if (edge.frame(pointer.clientY, performance.now())) follow(pointer);
|
|
494
|
+
else if (gesture.settle.gliding()) draw();
|
|
495
|
+
scrolling = requestAnimationFrame(frame);
|
|
452
496
|
};
|
|
497
|
+
// The cards of `board` laid out by `change` (cells set), those it moves gliding there (./motion.mjs), the grip
|
|
498
|
+
// put again on its card once they stand.
|
|
499
|
+
const layOut = (board, change, options = {}) => glide(cardsIn(board), () => {
|
|
500
|
+
const result = change();
|
|
501
|
+
globalThis.DocBoard?.layout(board);
|
|
502
|
+
return result;
|
|
503
|
+
}, { settled: placeAgain, ...options });
|
|
453
504
|
|
|
454
505
|
function begin(kind, card, event) {
|
|
506
|
+
stopGlide(card); // still gliding from a drop: taken up where its cells are
|
|
455
507
|
const board = card.parentElement;
|
|
456
508
|
const file = cellsOf(card);
|
|
457
509
|
const rect = card.getBoundingClientRect();
|
|
458
510
|
// skip: the rows the board skipped as the gesture began, which the kit keeps while its draft is shown
|
|
459
511
|
// (a card held above the first row skips fewer): the pointer's row is reckoned by it throughout.
|
|
460
|
-
gesture = { kind, card, board, file, skip: skipped(board), target: { ...file }, grab: { x: event.clientX - rect.left, y: event.clientY - rect.top }, start: { x: event.clientX, y: event.clientY, width: rect.width, height: rect.height, scroll: scrollY } };
|
|
512
|
+
gesture = { kind, card, board, file, skip: skipped(board), target: { ...file }, grab: { x: event.clientX - rect.left, y: event.clientY - rect.top }, start: { x: event.clientX, y: event.clientY, width: rect.width, height: rect.height, scroll: scrollY }, settle: createSettle(SNAP), size: null };
|
|
461
513
|
document.documentElement.setAttribute(kind === "move" ? "data-ge-dragging" : "data-ge-sizing", "");
|
|
462
514
|
card.setAttribute(kind === "move" ? "data-ge-lifted" : "data-ge-sizing", "");
|
|
463
|
-
|
|
464
|
-
board.
|
|
515
|
+
board.setAttribute("data-ge-dotted", "");
|
|
516
|
+
board.prepend(dots);
|
|
517
|
+
dotsOn(board);
|
|
465
518
|
grip.removeAttribute("data-shown");
|
|
466
519
|
corner.removeAttribute("data-shown");
|
|
467
520
|
follow(event);
|
|
468
|
-
scrolling = requestAnimationFrame(
|
|
521
|
+
scrolling = requestAnimationFrame(frame);
|
|
469
522
|
}
|
|
470
523
|
function follow(event) {
|
|
471
524
|
const { kind, card, board, file, skip, grab, start } = gesture;
|
|
@@ -479,7 +532,7 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
479
532
|
target = { ...file, x: clamp(cellAt(left, cell), 0, 12 - file.w), y: Math.max(0, cellAt(top, cell) + skip) };
|
|
480
533
|
} else {
|
|
481
534
|
// The card follows the pointer between the least and the most it can snap to (its listed sizes', or
|
|
482
|
-
// its min and max), never past its row's end
|
|
535
|
+
// its min and max), never past its row's end, standing at the size a drop gives where it is near it.
|
|
483
536
|
const block = infoOf(card)?.card ?? {};
|
|
484
537
|
const listed = block.any ? null : block.sizes?.filter(([w]) => file.x + w <= 12); // a card its block frees (any) takes every whole size
|
|
485
538
|
const least = listed?.length ? { w: Math.min(...listed.map(([w]) => w)), h: Math.min(...listed.map(([, h]) => h)) } : { w: block.min?.w ?? 1, h: block.min?.h ?? 1 };
|
|
@@ -490,36 +543,59 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
490
543
|
const asked = { w: Math.max(1, Math.round((width + GAP) / step)), h: Math.max(1, Math.round((height + GAP) / step)) };
|
|
491
544
|
const size = listed?.length ? sizeFor({ sizes: listed }, asked) : sizeFor(block, { w: Math.min(asked.w, 12 - file.x), h: asked.h }, 12 - file.x);
|
|
492
545
|
target = { ...file, w: size.w, h: fits ? file.h : size.h };
|
|
493
|
-
|
|
494
|
-
if (!fits) card.style.height = `${Math.round(height)}px`;
|
|
546
|
+
gesture.size = { width, height, w: spanOf(size.w, cell), h: spanOf(size.h, cell), fits };
|
|
495
547
|
}
|
|
496
548
|
if (!cellsSame(target, gesture.target) || !gesture.drafted) {
|
|
497
549
|
gesture.target = target;
|
|
498
550
|
gesture.drafted = true;
|
|
499
|
-
|
|
500
|
-
|
|
551
|
+
// The cards it meets make way, gliding (the held card follows the pointer: draw).
|
|
552
|
+
glide(cardsIn(board), () => draft(board, new Map([[card, target]])), { still: card });
|
|
501
553
|
}
|
|
554
|
+
draw();
|
|
555
|
+
dotsOn(board);
|
|
556
|
+
}
|
|
557
|
+
// The held card drawn where the pointer has it: moved, at the cells it would land at (the draft's), off them
|
|
558
|
+
// by the pointer's offset but where that is within SNAP px; resized, at the pointer's size but where that is
|
|
559
|
+
// within SNAP px of the size a drop gives. Each axis settles onto (or off) what it snaps to (createSettle).
|
|
560
|
+
function draw() {
|
|
561
|
+
const { kind, card, board, grab, settle, size } = gesture;
|
|
562
|
+
const time = performance.now();
|
|
502
563
|
if (kind === "move") {
|
|
564
|
+
const { rect, step } = geometry(board);
|
|
503
565
|
const now = drawn(card);
|
|
504
|
-
|
|
566
|
+
const cells = { x: now.x * step, y: (now.y - skipped(board)) * step };
|
|
567
|
+
const x = settle.at("x", pointer.clientX - grab.x - rect.left, cells.x, time);
|
|
568
|
+
const y = settle.at("y", pointer.clientY - grab.y - rect.top, cells.y, time);
|
|
569
|
+
card.style.translate = `${Math.round(x - cells.x)}px ${Math.round(y - cells.y)}px`;
|
|
570
|
+
} else {
|
|
571
|
+
card.style.width = `${Math.round(settle.at("w", size.width, size.w, time))}px`;
|
|
572
|
+
if (!size.fits) card.style.height = `${Math.round(settle.at("h", size.height, size.h, time))}px`;
|
|
505
573
|
}
|
|
506
574
|
}
|
|
507
575
|
function finish(drop) {
|
|
508
576
|
const done = gesture;
|
|
509
577
|
gesture = null;
|
|
510
578
|
cancelAnimationFrame(scrolling);
|
|
579
|
+
edge.stop();
|
|
511
580
|
pointer = null;
|
|
512
581
|
const { card, board, file, target, kind } = done;
|
|
513
582
|
document.documentElement.removeAttribute("data-ge-dragging");
|
|
514
583
|
document.documentElement.removeAttribute("data-ge-sizing");
|
|
584
|
+
card.setAttribute("data-ge-landing", ""); // its lifted shadow eases out (and it stays over the others while it glides)
|
|
515
585
|
card.removeAttribute("data-ge-lifted");
|
|
516
586
|
card.removeAttribute("data-ge-sizing");
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
587
|
+
// Let go: it glides from where it is seen to its cells (dropped) or back to its own (Escape), over the
|
|
588
|
+
// others meanwhile, and they to theirs.
|
|
589
|
+
glide(cardsIn(board), () => {
|
|
590
|
+
card.style.translate = "";
|
|
591
|
+
card.style.width = "";
|
|
592
|
+
card.style.height = "";
|
|
593
|
+
dots.remove();
|
|
594
|
+
board.removeAttribute("data-ge-dotted");
|
|
595
|
+
if (drop && !cellsSame(target, file)) placeCard(card, target, { kind });
|
|
596
|
+
draft(board, null);
|
|
597
|
+
}, { lift: card, settled: placeAgain });
|
|
598
|
+
if (!isGliding(card)) setTimeout(() => { if (!isGliding(card)) card.removeAttribute("data-ge-landing"); }, 220);
|
|
523
599
|
trySwap();
|
|
524
600
|
hideSoon(); // the grip comes back where the pointer is
|
|
525
601
|
}
|
|
@@ -722,14 +798,26 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
722
798
|
say(t("连不上本地服务"), "error");
|
|
723
799
|
return false;
|
|
724
800
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
801
|
+
// A section of each pack (the user's word of 2026-11-09), its name over its blocks, in the service's order
|
|
802
|
+
// (the packs as 设置 › 组件列表 has them, each's blocks by name); what is typed keeps the sections it finds blocks in.
|
|
803
|
+
const rows = (text = "") => {
|
|
804
|
+
const items = [];
|
|
805
|
+
let pack;
|
|
806
|
+
for (const block of listed) {
|
|
807
|
+
const words = `${said(block.name)} ${said(block.description)} ${block.id}`.toLowerCase();
|
|
808
|
+
if (text.trim() && !words.includes(text.trim().toLowerCase())) continue;
|
|
809
|
+
if (block.pack?.id !== pack) {
|
|
810
|
+
pack = block.pack?.id;
|
|
811
|
+
if (pack) items.push({ title: said(block.pack.name) || pack });
|
|
812
|
+
}
|
|
813
|
+
items.push({
|
|
814
|
+
label: said(block.name) || block.id, icon: BLOCK, hint: block.installed ? `${block.size.w} × ${block.size.h}` : t("自带"),
|
|
815
|
+
...(block.preview || block.description ? { preview: block.preview ? { src: block.preview, says: said(block.description) } : { says: said(block.description) } } : {}),
|
|
816
|
+
run: () => addBlock(board, block),
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
return items;
|
|
820
|
+
};
|
|
733
821
|
button?.setAttribute("data-pressed", "");
|
|
734
822
|
const closed = () => menu.close();
|
|
735
823
|
if (!button) addEventListener("blur", closed);
|
|
@@ -773,7 +861,7 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
773
861
|
});
|
|
774
862
|
|
|
775
863
|
// ---------- 恢复默认卡片 ----------
|
|
776
|
-
// The
|
|
864
|
+
// The default cards a page with them misses put back (the service's restore-defaults), a task as every
|
|
777
865
|
// op is: one step of undo for all of them (a group of adds), the page shown again with their blocks'
|
|
778
866
|
// scripts and styles. Gives the service's answer ({ ok, added: [{ card, block, name }], skipped }),
|
|
779
867
|
// which the workbench says, or its refusal.
|
|
@@ -822,7 +910,7 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
822
910
|
const cards = step.cards.filter(({ id }) => cardById(id));
|
|
823
911
|
if (cards.length === 0) return false;
|
|
824
912
|
const to = way === "undo" ? "before" : "after";
|
|
825
|
-
for (const one of cards) setCells(cardById(one.id), one[to]);
|
|
913
|
+
layOut(cardById(cards[0].id).parentElement, () => { for (const one of cards) setCells(cardById(one.id), one[to]); });
|
|
826
914
|
const answer = await send({ op: "arrange", cards: cards.map((one) => ({ card: one.id, ...one[to], base: cardById(one.id).dataset.geBase })) });
|
|
827
915
|
if (!answer?.ok) return failed(answer) ?? false;
|
|
828
916
|
took(answer);
|
|
@@ -960,10 +1048,11 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
960
1048
|
watch.observe(document.documentElement, { attributes: true, attributeFilter: ["data-doc-drawn"] });
|
|
961
1049
|
}
|
|
962
1050
|
}
|
|
963
|
-
// The
|
|
964
|
-
// 恢复默认卡片 puts them: restoreDefaults).
|
|
1051
|
+
// The page's first-open note, while its map says so, its note of new default cards (put in as
|
|
1052
|
+
// 恢复默认卡片 puts them: restoreDefaults), and the middle of a blank page (an Agent writes it).
|
|
965
1053
|
if (map.hint) showHint(map, { hosted });
|
|
966
1054
|
if (map.news) showNews(map, { restore: restoreDefaults });
|
|
1055
|
+
if (map.blank) showBlank(map);
|
|
967
1056
|
// Its blocks may write from now on (the kit's context.canEdit, editable).
|
|
968
1057
|
dispatchEvent(new Event("doc-editable"));
|
|
969
1058
|
return { swap: wantSwap, undo, redo, idle: () => Promise.all([client.idle(), todos.idle()]), restoreDefaults, undoRestore, addCard };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// How a board's cards move while they are edited (./index.mjs; docs/render.md "Its cards edited"), the user's
|
|
2
|
+
// word of 2026-11-09: as smooth as the widgets of an iPhone's home screen, nothing that jumps or stutters.
|
|
3
|
+
//
|
|
4
|
+
// - glide(): the cards a change of the layout moves (a draft while a card is dragged or resized, a drop, an
|
|
5
|
+
// undo, 大小 ›) glide from where they were seen to where the layout now puts them (FLIP: their box before
|
|
6
|
+
// and after, a transform from the one to the other run to none) on a spring's ease (spring: a little
|
|
7
|
+
// less than critically damped, its overshoot under 2%), GLIDE ms; a card whose size changed glides its
|
|
8
|
+
// width and height too. A card already gliding to where the layout still puts it is left to go on; one
|
|
9
|
+
// the change sends elsewhere starts again from where it is seen then, so a target that changes under a
|
|
10
|
+
// drag never makes a card jump (interruptible).
|
|
11
|
+
// - createSettle(): a held card's place (or a resized card's size), axis by axis: the pointer's, or within
|
|
12
|
+
// `snap` px of the cells (or the size) a drop gives, those; where that changes (the card comes within the
|
|
13
|
+
// snap or leaves it, or the cells it stands on move) it settles from where it was carried to the new
|
|
14
|
+
// place in SETTLE ms on the same spring, the pointer's own motion never delayed.
|
|
15
|
+
// - createEdgeScroll(): near the window's top or bottom edge a gesture scrolls the page, timed by the frames'
|
|
16
|
+
// clock (not by the pointer's events): within BAND px of the edge, faster as the pointer is nearer it (the
|
|
17
|
+
// square of how far into the band, past the edge the most), FASTEST px a second at the most (12 a frame
|
|
18
|
+
// at 60 frames a second), and growing from nothing over RAMP ms once the pointer comes into the band, so
|
|
19
|
+
// that a card picked up near the edge does not set the page running.
|
|
20
|
+
// - prefers-reduced-motion: no glide and no settling (the layout and the snap at once); the scroll as it is.
|
|
21
|
+
|
|
22
|
+
export const GLIDE = 280; // ms a card glides to where a layout puts it
|
|
23
|
+
export const SETTLE = 180; // ms a held (or resized) card settles on what it snaps to, or comes off it
|
|
24
|
+
export const BAND = 64; // px from the window's top or bottom edge within which a gesture scrolls the page
|
|
25
|
+
export const FASTEST = 720; // px a second the page scrolls at the most
|
|
26
|
+
export const RAMP = 320; // ms over which that speed grows from nothing once the pointer is in the band
|
|
27
|
+
|
|
28
|
+
export const reduced = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
29
|
+
|
|
30
|
+
// The step response of a spring (damping ZETA, settled within 0.2% at t = 1): 0 at 0, 1 at 1, still at both
|
|
31
|
+
// ends, a crest of under 2% past 1 at t ≈ 0.65.
|
|
32
|
+
const ZETA = 0.8;
|
|
33
|
+
const OMEGA = 6.4 / ZETA;
|
|
34
|
+
const DAMPED = OMEGA * Math.sqrt(1 - ZETA * ZETA);
|
|
35
|
+
export function spring(t) {
|
|
36
|
+
if (t <= 0) return 0;
|
|
37
|
+
if (t >= 1) return 1;
|
|
38
|
+
return 1 - Math.exp(-ZETA * OMEGA * t) * (Math.cos(DAMPED * t) + ((ZETA * OMEGA) / DAMPED) * Math.sin(DAMPED * t));
|
|
39
|
+
}
|
|
40
|
+
// The same as an easing a Web Animation takes (CSS linear() of 40 points), else a close cubic Bézier.
|
|
41
|
+
const EASE = (() => {
|
|
42
|
+
const points = Array.from({ length: 41 }, (_, index) => Math.round(spring(index / 40) * 10000) / 10000).join(", ");
|
|
43
|
+
const curve = `linear(${points})`;
|
|
44
|
+
return typeof CSS !== "undefined" && CSS.supports?.("transition-timing-function", curve) ? curve : "cubic-bezier(.2, .9, .25, 1)";
|
|
45
|
+
})();
|
|
46
|
+
|
|
47
|
+
const running = new WeakMap(); // a card -> its glide under way
|
|
48
|
+
// Where the kit's board script draws a card now: its cells as the CSS takes them (the board's row skip in).
|
|
49
|
+
const keyOf = (card) => ["x", "y", "w", "h"].map((name) => card.style.getPropertyValue(`--gallery-${name}`)).join();
|
|
50
|
+
|
|
51
|
+
/** Whether a card is gliding now. */
|
|
52
|
+
export const isGliding = (card) => running.has(card);
|
|
53
|
+
|
|
54
|
+
/** A card's glide stopped where it is (a gesture takes it up): it stands where the layout puts it. */
|
|
55
|
+
export function stopGlide(card) {
|
|
56
|
+
running.get(card)?.cancel();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `change()` run (it lays the board out at once: DocBoard.draft or .layout), and each of `cards` it moves
|
|
61
|
+
* glided from where it was seen to its new place. `still`: a card left out (the one a gesture holds, which
|
|
62
|
+
* follows the pointer); `lift`: a card kept over the others while it glides (data-ge-landing: one let go);
|
|
63
|
+
* `settled()`: run once every glide this started has ended (at once where none started). Gives what change() gave.
|
|
64
|
+
*/
|
|
65
|
+
export function glide(cards, change, { still = null, lift = null, duration = GLIDE, settled = null } = {}) {
|
|
66
|
+
if (reduced()) {
|
|
67
|
+
const result = change();
|
|
68
|
+
settled?.();
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
const started = [];
|
|
72
|
+
const before = cards.filter((card) => card !== still).map((card) => ({ card, key: keyOf(card), box: card.getBoundingClientRect() }));
|
|
73
|
+
const result = change();
|
|
74
|
+
for (const { card, key, box } of before) {
|
|
75
|
+
if (!card.isConnected) continue;
|
|
76
|
+
const going = running.get(card);
|
|
77
|
+
if (going && keyOf(card) === key) continue; // on its way to where it still goes
|
|
78
|
+
going?.cancel();
|
|
79
|
+
const now = card.getBoundingClientRect();
|
|
80
|
+
const dx = box.left - now.left;
|
|
81
|
+
const dy = box.top - now.top;
|
|
82
|
+
const sized = Math.abs(box.width - now.width) > 0.5 || Math.abs(box.height - now.height) > 0.5;
|
|
83
|
+
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5 && !sized) continue;
|
|
84
|
+
const from = { transform: `translate(${dx}px, ${dy}px)` };
|
|
85
|
+
const to = { transform: "translate(0px, 0px)" };
|
|
86
|
+
if (sized) {
|
|
87
|
+
Object.assign(from, { width: `${box.width}px`, height: `${box.height}px` });
|
|
88
|
+
Object.assign(to, { width: `${now.width}px`, height: `${now.height}px` });
|
|
89
|
+
}
|
|
90
|
+
const animation = card.animate([from, to], { duration, easing: EASE });
|
|
91
|
+
running.set(card, animation);
|
|
92
|
+
if (card === lift) card.setAttribute("data-ge-landing", "");
|
|
93
|
+
const end = () => {
|
|
94
|
+
if (running.get(card) !== animation) return;
|
|
95
|
+
running.delete(card);
|
|
96
|
+
card.removeAttribute("data-ge-landing");
|
|
97
|
+
};
|
|
98
|
+
animation.addEventListener("finish", end);
|
|
99
|
+
animation.addEventListener("cancel", end);
|
|
100
|
+
started.push(animation.finished.catch(() => {}));
|
|
101
|
+
}
|
|
102
|
+
if (settled) {
|
|
103
|
+
if (started.length) Promise.all(started).then(settled);
|
|
104
|
+
else settled();
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A held card's place, or a resized card's size, axis by axis (`name`: "x", "y", "w", "h"): at(name, raw,
|
|
111
|
+
* target, time) gives what is drawn for the pointer's `raw` and the `target` a drop gives (the cells' place
|
|
112
|
+
* or size, px): the target within `snap` px of it, else raw; a change of which (or of the target while on
|
|
113
|
+
* it) settles from where the card was carried, in `duration` ms. gliding(): whether any axis still settles.
|
|
114
|
+
*/
|
|
115
|
+
export function createSettle(snap, duration = SETTLE) {
|
|
116
|
+
const axes = new Map();
|
|
117
|
+
return {
|
|
118
|
+
at(name, raw, target, time) {
|
|
119
|
+
const on = Math.abs(raw - target) <= snap;
|
|
120
|
+
const want = on ? target : raw;
|
|
121
|
+
const axis = axes.get(name);
|
|
122
|
+
if (!axis) {
|
|
123
|
+
axes.set(name, { raw, target, on, shown: want, glide: null });
|
|
124
|
+
return want;
|
|
125
|
+
}
|
|
126
|
+
// Where it would be had it gone with the pointer since the last time: what a settling starts from.
|
|
127
|
+
const carried = axis.shown + (raw - axis.raw);
|
|
128
|
+
if (on !== axis.on || (on && target !== axis.target)) axis.glide = reduced() ? null : { offset: carried - want, start: time };
|
|
129
|
+
Object.assign(axis, { raw, target, on });
|
|
130
|
+
let shown = want;
|
|
131
|
+
if (axis.glide) {
|
|
132
|
+
const progress = (time - axis.glide.start) / duration;
|
|
133
|
+
if (progress >= 1) axis.glide = null;
|
|
134
|
+
else shown = want + axis.glide.offset * (1 - spring(progress));
|
|
135
|
+
}
|
|
136
|
+
axis.shown = shown;
|
|
137
|
+
return shown;
|
|
138
|
+
},
|
|
139
|
+
gliding: () => [...axes.values()].some((axis) => axis.glide !== null),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The page scrolled near the window's edge while a gesture lasts: frame(y, time) once a frame (the pointer's
|
|
145
|
+
* y in the window, the frame's time), whether the page moved; stop() as the gesture ends.
|
|
146
|
+
*/
|
|
147
|
+
export function createEdgeScroll({ band = BAND, fastest = FASTEST, ramp = RAMP } = {}) {
|
|
148
|
+
let since = null; // when the pointer came into the band
|
|
149
|
+
let last = null; // the last frame's time
|
|
150
|
+
let carry = 0; // px asked and not yet scrolled (a frame's share is seldom whole)
|
|
151
|
+
return {
|
|
152
|
+
frame(y, time) {
|
|
153
|
+
const step = last === null ? 0 : Math.min(34, Math.max(0, time - last)); // a stalled frame scrolls no more than two
|
|
154
|
+
last = time;
|
|
155
|
+
const into = y < band ? -(band - y) / band : y > innerHeight - band ? (y - (innerHeight - band)) / band : 0;
|
|
156
|
+
if (into === 0) {
|
|
157
|
+
since = null;
|
|
158
|
+
carry = 0;
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
since ??= time;
|
|
162
|
+
const depth = Math.min(1, Math.abs(into));
|
|
163
|
+
carry += Math.sign(into) * fastest * depth * depth * Math.min(1, (time - since) / ramp) * (step / 1000);
|
|
164
|
+
const whole = Math.trunc(carry);
|
|
165
|
+
if (!whole) return false;
|
|
166
|
+
carry -= whole;
|
|
167
|
+
const was = scrollY;
|
|
168
|
+
scrollBy(0, whole);
|
|
169
|
+
return scrollY !== was;
|
|
170
|
+
},
|
|
171
|
+
stop() {
|
|
172
|
+
since = null;
|
|
173
|
+
last = null;
|
|
174
|
+
carry = 0;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -14,7 +14,8 @@ import { addStyle, button, element, mount } from "../ui.mjs";
|
|
|
14
14
|
// workspace's documents by title (the workbench's outline: GET /__gallery/workspace/outline, its web
|
|
15
15
|
// links no documents), the slug what is kept. A choice from a list (select, document) is a field that
|
|
16
16
|
// opens the editor's own menu card under it (menu.mjs: rows, the chosen one checked, ↑/↓ Enter Escape from
|
|
17
|
-
// the field),
|
|
17
|
+
// the field), a choice of the caller's (type "choice", `choices`: [[value, words]], what a document block adds
|
|
18
|
+
// to its fields: the table a view reads) too, not a native <select>: the page cannot style that one's popup, which a browser draws in
|
|
18
19
|
// the system's colours (a white list with pale words in a dark page, the user's picture of 2026-11-09).
|
|
19
20
|
// While the form stands, the page is not shown again under it (the board's idle).
|
|
20
21
|
|
|
@@ -92,9 +93,9 @@ export async function openSettings({ fields, values, name, anchor, save }) {
|
|
|
92
93
|
continue;
|
|
93
94
|
}
|
|
94
95
|
let control;
|
|
95
|
-
const choice = field.type === "select" || field.type === "document"; // chosen from a list
|
|
96
|
+
const choice = field.type === "select" || field.type === "document" || field.type === "choice"; // chosen from a list
|
|
96
97
|
if (choice) {
|
|
97
|
-
const options = field.type === "select" ? field.options.map((option) => [String(option), String(option)]) : [["", t("(未选择)")], ...listed.map(({ slug, title }) => [slug, title])];
|
|
98
|
+
const options = field.type === "select" ? field.options.map((option) => [String(option), String(option)]) : field.type === "choice" ? field.choices : [["", t("(未选择)")], ...listed.map(({ slug, title }) => [slug, title])];
|
|
98
99
|
if (field.type === "document" && value && !listed.some(({ slug }) => slug === value)) options.push([String(value), String(value)]);
|
|
99
100
|
const text = element("span", { class: "ge-board-pick-text" });
|
|
100
101
|
control = element("button", { type: "button", class: "ge-board-pick", "data-key": field.key, "aria-haspopup": "menu", "aria-expanded": "false" }, text, element("span", { html: CHEVRON }));
|