@heycys/gallery 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/browser/editor/components/columns/page.mjs +4 -4
- package/browser/editor/components/embed/index.mjs +2 -2
- package/browser/editor/components/flowchart/add.mjs +7 -6
- package/browser/editor/components/flowchart/page.mjs +1 -1
- package/browser/editor/components/icons.mjs +8 -0
- package/browser/editor/components/image/crop.mjs +6 -9
- package/browser/editor/components/installed/index.mjs +11 -4
- package/browser/editor/components/installed/page.mjs +27 -16
- package/browser/editor/components/installed/served.mjs +6 -5
- package/browser/editor/components/instances.mjs +18 -0
- package/browser/editor/components/list/page.mjs +3 -6
- package/browser/editor/components/registry.mjs +13 -4
- package/browser/editor/components/table/index.mjs +59 -2
- package/browser/editor/components/table/page.mjs +1 -1
- package/browser/editor/page/annotate/card.mjs +3 -6
- package/browser/editor/page/autosave.mjs +310 -37
- package/browser/editor/page/board/client.mjs +57 -0
- package/browser/editor/page/board/hint.mjs +145 -0
- package/browser/editor/page/board/history.mjs +119 -0
- package/browser/editor/page/board/index.mjs +970 -0
- package/browser/editor/page/board/settings.mjs +209 -0
- package/browser/editor/page/caret.mjs +1 -1
- package/browser/editor/page/changes/bar.mjs +6 -14
- package/browser/editor/page/changes/index.mjs +2 -2
- package/browser/editor/page/clipboard/import/html.mjs +29 -6
- package/browser/editor/page/clipboard/import/inline.mjs +32 -0
- package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
- package/browser/editor/page/clipboard/import/read.mjs +6 -1
- package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
- package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
- package/browser/editor/page/clipboard/index.mjs +124 -13
- package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
- package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
- package/browser/editor/page/clipboard/own/selection.mjs +1 -1
- package/browser/editor/page/columns/index.mjs +48 -46
- package/browser/editor/page/commit.mjs +80 -22
- package/browser/editor/page/conflicts.mjs +7 -7
- package/browser/editor/page/core.mjs +31 -15
- package/browser/editor/page/custom-page.mjs +106 -0
- package/browser/editor/page/editor.mjs +51 -14
- package/browser/editor/page/emoji-picker/styles.mjs +7 -12
- package/browser/editor/page/emoji-picker/upload.mjs +2 -2
- package/browser/editor/page/handle/index.mjs +5 -8
- package/browser/editor/page/history.mjs +24 -4
- package/browser/editor/page/links/index.mjs +5 -3
- package/browser/editor/page/links/pages.mjs +27 -9
- package/browser/editor/page/look.mjs +54 -3
- package/browser/editor/page/margin.mjs +18 -3
- package/browser/editor/page/media-menu/index.mjs +6 -7
- package/browser/editor/page/menu.mjs +18 -7
- package/browser/editor/page/notices.mjs +2 -2
- package/browser/editor/page/page-objects.mjs +13 -2
- package/browser/editor/page/panel.mjs +4 -9
- package/browser/editor/page/present-links.mjs +11 -2
- package/browser/editor/page/resize/index.mjs +42 -29
- package/browser/editor/page/served.mjs +14 -0
- package/browser/editor/page/session.mjs +48 -4
- package/browser/editor/page/slash-menu/index.mjs +3 -3
- package/browser/editor/page/table/cells.mjs +102 -46
- package/browser/editor/page/table/icons.mjs +2 -0
- package/browser/editor/page/table/index.mjs +29 -10
- package/browser/editor/page/table/pictures.mjs +299 -0
- package/browser/editor/page/table/views.mjs +150 -0
- package/browser/editor/page/text-select/index.mjs +26 -12
- package/browser/editor/page/todo-writes.mjs +75 -0
- package/browser/editor/page/typing/index.mjs +53 -3
- package/browser/editor/page/ui.mjs +13 -0
- package/browser/shared/block-packs.mjs +404 -0
- package/browser/shared/board-places.mjs +224 -0
- package/browser/shared/card-size.mjs +59 -0
- package/browser/shared/colors.mjs +3 -2
- package/browser/shared/component-catalog.mjs +323 -46
- package/browser/shared/component-source.mjs +37 -0
- package/browser/shared/components/index.mjs +12 -3
- package/browser/shared/components/installed.mjs +35 -3
- package/browser/shared/components/page.mjs +18 -8
- package/browser/shared/components/state.mjs +5 -1
- package/browser/shared/document-look.mjs +10 -5
- package/browser/shared/drafts.mjs +432 -0
- package/browser/shared/extension-sidebar.mjs +126 -0
- package/browser/shared/extensions.mjs +79 -0
- package/browser/shared/file-table.mjs +101 -0
- package/browser/shared/i18n/en.mjs +307 -27
- package/browser/shared/json-layout.mjs +47 -0
- package/browser/shared/net-cache.mjs +133 -0
- package/browser/shared/net-keys.mjs +32 -0
- package/browser/shared/net-policy.mjs +177 -0
- package/browser/shared/outline-shape.mjs +128 -0
- package/browser/shared/outline.mjs +331 -67
- package/browser/shared/pack-manifest.mjs +77 -0
- package/browser/shared/page-address.mjs +6 -3
- package/browser/shared/page-cards.mjs +308 -0
- package/browser/shared/page-links.mjs +69 -6
- package/browser/shared/pages.mjs +388 -0
- package/browser/shared/paths.mjs +6 -0
- package/browser/shared/preview-server.mjs +3 -7
- package/browser/shared/session.mjs +8 -1
- package/browser/shared/shipped-blocks.mjs +299 -0
- package/browser/shared/table-chart.mjs +33 -1
- package/browser/shared/todos.mjs +83 -0
- package/browser/shared/user-directory.mjs +24 -0
- package/browser/shared/versions.mjs +14 -0
- package/browser/shared/web-links.mjs +134 -0
- package/browser/shared/workspace-columns.mjs +120 -0
- package/browser/shared/workspace-name.mjs +154 -0
- package/browser/shared/workspace-query.mjs +125 -0
- package/browser/shared/workspace-registry.mjs +272 -0
- package/browser/shared/workspace.mjs +171 -20
- package/dist/chunk-2NRBY6AJ.mjs +2 -0
- package/dist/chunk-32ZUEFZ7.mjs +2 -0
- package/dist/chunk-35NQ4PLQ.mjs +2 -0
- package/dist/chunk-3KJHLHUO.mjs +2 -0
- package/dist/chunk-3NJACR4W.mjs +2 -0
- package/dist/chunk-4AV456RQ.mjs +2 -0
- package/dist/chunk-4OZWXZE5.mjs +5 -0
- package/dist/chunk-5XYHDBJ5.mjs +2 -0
- package/dist/chunk-5ZJA7CXT.mjs +2 -0
- package/dist/chunk-6A53MZYZ.mjs +2 -0
- package/dist/chunk-6EXH3ORZ.mjs +166 -0
- package/dist/chunk-6EZ7FNIK.mjs +3 -0
- package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
- package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
- package/dist/chunk-B4P2CM2J.mjs +3 -0
- package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
- package/dist/chunk-BVEFGE4B.mjs +2 -0
- package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
- package/dist/chunk-CRJEG7MT.mjs +2 -0
- package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
- package/dist/chunk-D4EURAX4.mjs +4 -0
- package/dist/chunk-DQCE7TJY.mjs +5 -0
- package/dist/chunk-DR73Y2UJ.mjs +3 -0
- package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
- package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
- package/dist/chunk-EURLTOTZ.mjs +4 -0
- package/dist/chunk-FJYN7RTH.mjs +4 -0
- package/dist/chunk-FWNRPMCJ.mjs +12 -0
- package/dist/chunk-FWW4CSAZ.mjs +2 -0
- package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
- package/dist/chunk-GY3FAUVM.mjs +2 -0
- package/dist/chunk-HLYV7RIH.mjs +2 -0
- package/dist/chunk-HMYTM3GT.mjs +15 -0
- package/dist/chunk-IA4PSIMD.mjs +2 -0
- package/dist/chunk-JEUK3ZO7.mjs +2 -0
- package/dist/chunk-KTKHRC6H.mjs +2 -0
- package/dist/chunk-L7UP52JO.mjs +2 -0
- package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
- package/dist/chunk-N76HWBA3.mjs +10 -0
- package/dist/chunk-N7AZZLGT.mjs +548 -0
- package/dist/chunk-NAMN3RAF.mjs +2 -0
- package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
- package/dist/chunk-NG375ILQ.mjs +2 -0
- package/dist/chunk-NVRNLK25.mjs +6 -0
- package/dist/chunk-NWFBE2YA.mjs +2 -0
- package/dist/chunk-NWR2LT5B.mjs +2 -0
- package/dist/chunk-OJKMINIU.mjs +20 -0
- package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
- package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
- package/dist/chunk-PAREWQGB.mjs +4 -0
- package/dist/chunk-POQSE4C2.mjs +2 -0
- package/dist/chunk-PUOQ65NC.mjs +2 -0
- package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
- package/dist/chunk-PZDLJJTJ.mjs +3 -0
- package/dist/chunk-Q47PLKIJ.mjs +15 -0
- package/dist/chunk-Q4UEPUWF.mjs +2 -0
- package/dist/chunk-QKXMPUXC.mjs +2 -0
- package/dist/chunk-QOUEFUCH.mjs +16 -0
- package/dist/chunk-RFDZXFJF.mjs +13 -0
- package/dist/chunk-RKPWLGZ3.mjs +4 -0
- package/dist/chunk-RMECZA2K.mjs +2 -0
- package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
- package/dist/chunk-SX4LJYT6.mjs +8 -0
- package/dist/chunk-SXHBYZST.mjs +16 -0
- package/dist/chunk-TANZRINB.mjs +2 -0
- package/dist/chunk-TEZAWDSQ.mjs +2 -0
- package/dist/chunk-UBBRTJ2R.mjs +24 -0
- package/dist/chunk-UFNYFFKO.mjs +20 -0
- package/dist/chunk-UGWYKHBL.mjs +170 -0
- package/dist/chunk-UKSHDZZ6.mjs +2 -0
- package/dist/chunk-UNOYVIYR.mjs +2 -0
- package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
- package/dist/chunk-VJPFVNNW.mjs +15 -0
- package/dist/chunk-VS3ZMVGX.mjs +5 -0
- package/dist/chunk-VTKNEYT3.mjs +6 -0
- package/dist/chunk-VXACTLO3.mjs +3 -0
- package/dist/chunk-YI43RG4P.mjs +2 -0
- package/dist/chunk-YQGT4AB2.mjs +6 -0
- package/dist/chunk-ZB4H7ZN4.mjs +2 -0
- package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
- package/dist/chunk-ZTHICSSF.mjs +2 -0
- package/dist/chunk-ZUNVAXLH.mjs +2 -0
- package/dist/chunk-ZWPMCK7P.mjs +2 -0
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +95 -0
- package/kit/board.js +250 -0
- package/kit/charts.js +5 -71
- package/kit/code.js +14 -14
- package/kit/report.css +255 -17
- package/kit/runtime.js +1600 -125
- package/kit/state.js +10 -1
- package/kit/tables.js +128 -43
- package/kit/ui.js +77 -35
- package/package.json +2 -1
- package/packs/gallery/card/component.json +14 -0
- package/packs/gallery/card/example.html +10 -0
- package/packs/gallery/card/preview.svg +1 -0
- package/packs/gallery/card/style.css +6 -0
- package/packs/gallery/card/template.html +6 -0
- package/packs/gallery/card/writing.md +14 -0
- package/packs/gallery/chart-card/component.json +15 -0
- package/packs/gallery/chart-card/example.html +14 -0
- package/packs/gallery/chart-card/kit.js +130 -0
- package/packs/gallery/chart-card/preview.svg +1 -0
- package/packs/gallery/chart-card/style.css +42 -0
- package/packs/gallery/chart-card/template.html +13 -0
- package/packs/gallery/chart-card/writing.md +20 -0
- package/packs/gallery/chart-view/component.json +20 -0
- package/packs/gallery/chart-view/example.html +13 -0
- package/packs/gallery/chart-view/kit.js +355 -0
- package/packs/gallery/chart-view/preview.svg +1 -0
- package/packs/gallery/chart-view/style.css +94 -0
- package/packs/gallery/chart-view/template.html +3 -0
- package/packs/gallery/chart-view/writing.md +22 -0
- package/packs/gallery/countdown/component.json +18 -0
- package/packs/gallery/countdown/example.html +3 -0
- package/packs/gallery/countdown/kit.js +156 -0
- package/packs/gallery/countdown/preview.svg +1 -0
- package/packs/gallery/countdown/style.css +130 -0
- package/packs/gallery/countdown/template.html +3 -0
- package/packs/gallery/countdown/writing.md +22 -0
- package/packs/gallery/habit/component.json +14 -0
- package/packs/gallery/habit/example.html +3 -0
- package/packs/gallery/habit/kit.js +295 -0
- package/packs/gallery/habit/preview.svg +1 -0
- package/packs/gallery/habit/style.css +246 -0
- package/packs/gallery/habit/template.html +3 -0
- package/packs/gallery/habit/writing.md +21 -0
- package/packs/gallery/heatmap/component.json +18 -0
- package/packs/gallery/heatmap/example.html +3 -0
- package/packs/gallery/heatmap/kit.js +351 -0
- package/packs/gallery/heatmap/preview.svg +1 -0
- package/packs/gallery/heatmap/style.css +201 -0
- package/packs/gallery/heatmap/template.html +3 -0
- package/packs/gallery/heatmap/writing.md +21 -0
- package/packs/gallery/kanban/component.json +18 -0
- package/packs/gallery/kanban/example.html +12 -0
- package/packs/gallery/kanban/kit.js +540 -0
- package/packs/gallery/kanban/preview.svg +1 -0
- package/packs/gallery/kanban/style.css +212 -0
- package/packs/gallery/kanban/template.html +3 -0
- package/packs/gallery/kanban/writing.md +20 -0
- package/packs/gallery/metric/component.json +22 -0
- package/packs/gallery/metric/example.html +15 -0
- package/packs/gallery/metric/kit.js +248 -0
- package/packs/gallery/metric/preview.svg +1 -0
- package/packs/gallery/metric/style.css +92 -0
- package/packs/gallery/metric/template.html +3 -0
- package/packs/gallery/metric/writing.md +23 -0
- package/packs/gallery/note/component.json +14 -0
- package/packs/gallery/note/example.html +3 -0
- package/packs/gallery/note/kit.js +102 -0
- package/packs/gallery/note/preview.svg +1 -0
- package/packs/gallery/note/style.css +76 -0
- package/packs/gallery/note/template.html +3 -0
- package/packs/gallery/note/writing.md +18 -0
- package/packs/gallery/pack.json +8 -0
- package/packs/gallery/recent/component.json +13 -0
- package/packs/gallery/recent/example.html +1 -0
- package/packs/gallery/recent/kit.js +238 -0
- package/packs/gallery/recent/preview.svg +1 -0
- package/packs/gallery/recent/style.css +158 -0
- package/packs/gallery/recent/template.html +1 -0
- package/packs/gallery/recent/writing.md +15 -0
- package/packs/gallery/static/component.json +13 -0
- package/packs/gallery/static/example.html +11 -0
- package/packs/gallery/static/preview.svg +1 -0
- package/packs/gallery/static/style.css +138 -0
- package/packs/gallery/static/template.html +1 -0
- package/packs/gallery/static/writing.md +18 -0
- package/packs/gallery/stats/component.json +13 -0
- package/packs/gallery/stats/example.html +1 -0
- package/packs/gallery/stats/kit.js +166 -0
- package/packs/gallery/stats/preview.svg +1 -0
- package/packs/gallery/stats/style.css +151 -0
- package/packs/gallery/stats/template.html +1 -0
- package/packs/gallery/stats/writing.md +11 -0
- package/packs/gallery/today/component.json +12 -0
- package/packs/gallery/today/example.html +1 -0
- package/packs/gallery/today/kit.js +185 -0
- package/packs/gallery/today/preview.svg +1 -0
- package/packs/gallery/today/style.css +120 -0
- package/packs/gallery/today/template.html +1 -0
- package/packs/gallery/today/writing.md +14 -0
- package/packs/gallery/todos/component.json +19 -0
- package/packs/gallery/todos/example.html +3 -0
- package/packs/gallery/todos/kit.js +342 -0
- package/packs/gallery/todos/preview.svg +1 -0
- package/packs/gallery/todos/style.css +267 -0
- package/packs/gallery/todos/template.html +3 -0
- package/packs/gallery/todos/writing.md +30 -0
- package/packs/gallery/weather/component.json +19 -0
- package/packs/gallery/weather/example.html +3 -0
- package/packs/gallery/weather/kit.js +312 -0
- package/packs/gallery/weather/preview.svg +1 -0
- package/packs/gallery/weather/style.css +258 -0
- package/packs/gallery/weather/template.html +3 -0
- package/packs/gallery/weather/writing.md +26 -0
- package/packs/gallery-soft/pack.json +8 -0
- package/release.json +12 -10
- package/sdk/doc-kit.d.ts +396 -5
- package/sdk/doc-state.d.ts +2 -0
- package/skill/SKILL.md +56 -23
- package/skill/references/block-script.md +140 -0
- package/skill/references/building-blocks.md +257 -0
- package/skill/references/extensions.md +59 -0
- package/skill/references/pages-and-data.md +221 -0
- package/skill/references/writing-html.md +6 -4
- package/skill/sdk/doc-kit.d.ts +396 -5
- package/skill/sdk/doc-state.d.ts +2 -0
- package/web/assets/index-BK2e_07w.js +13 -0
- package/web/assets/index-CWFI83mF.css +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-3HIRPVLU.mjs +0 -2
- package/dist/chunk-5MHMDLZD.mjs +0 -4
- package/dist/chunk-64K7IPLO.mjs +0 -255
- package/dist/chunk-6QXGZM36.mjs +0 -8
- package/dist/chunk-6RGJOMJP.mjs +0 -16
- package/dist/chunk-AJ73XJF3.mjs +0 -2
- package/dist/chunk-AUZGSZJH.mjs +0 -13
- package/dist/chunk-BFE45AKL.mjs +0 -2
- package/dist/chunk-BXO2ZY5P.mjs +0 -2
- package/dist/chunk-CEH4MRCC.mjs +0 -2
- package/dist/chunk-CY2FNNAB.mjs +0 -16
- package/dist/chunk-DFUDV4LX.mjs +0 -2
- package/dist/chunk-DKXG4XDG.mjs +0 -2
- package/dist/chunk-EZQ34M3F.mjs +0 -124
- package/dist/chunk-EZY3WC6Z.mjs +0 -15
- package/dist/chunk-FUKQNOQS.mjs +0 -9
- package/dist/chunk-JPOSLGOC.mjs +0 -6
- package/dist/chunk-L3QKH4OK.mjs +0 -26
- package/dist/chunk-LAE5NQ4B.mjs +0 -2
- package/dist/chunk-LEF6DRCK.mjs +0 -59
- package/dist/chunk-NO5NJCHO.mjs +0 -2
- package/dist/chunk-NQPVAU63.mjs +0 -2
- package/dist/chunk-NSKUTQ5J.mjs +0 -5
- package/dist/chunk-O35FIXRB.mjs +0 -2
- package/dist/chunk-P2CAZPDP.mjs +0 -3
- package/dist/chunk-PL6GHLGD.mjs +0 -15
- package/dist/chunk-QJIR2BLP.mjs +0 -6
- package/dist/chunk-QUPLVPB2.mjs +0 -7
- package/dist/chunk-R7BNENLY.mjs +0 -4
- package/dist/chunk-S6O4UHLC.mjs +0 -2
- package/dist/chunk-SGJWQKEI.mjs +0 -2
- package/dist/chunk-TX6JH7TR.mjs +0 -2
- package/dist/chunk-TYK6VNAB.mjs +0 -10
- package/dist/chunk-UALRNVSE.mjs +0 -4
- package/dist/chunk-V3HXVQEM.mjs +0 -2
- package/dist/chunk-V5VXUJKG.mjs +0 -2
- package/dist/chunk-VBXMADMP.mjs +0 -2
- package/dist/chunk-VNV55RUA.mjs +0 -2
- package/dist/chunk-VPD2PS35.mjs +0 -2
- package/dist/chunk-W4VKN3GF.mjs +0 -7
- package/dist/chunk-W5BXXPDJ.mjs +0 -3
- package/dist/chunk-XUNHRCOG.mjs +0 -6
- package/skill/references/building-components.md +0 -367
- package/web/assets/index-Bh74DfsV.css +0 -1
- package/web/assets/index-UW0RyFFt.js +0 -13
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/* Every selector starts at the instance and goes down a step at a time (>), so that nothing here
|
|
2
|
+
reaches the rest of the page. The board: a muted line saying what it groups by, the settings (⚙)
|
|
3
|
+
at its right while the page edits, then the groups side by side, each a column of cards on a soft
|
|
4
|
+
fill, its name, its count and 新建 at its foot; past the width of the page the groups scroll sideways
|
|
5
|
+
in the board, never the page. A card is the kit's surface, its title, the other cells' words in small
|
|
6
|
+
muted print, its links as chips; its ✎ and 🗑 show as the pointer is on it, where the table takes a
|
|
7
|
+
change. Notion-flat, in the kit's colors, light and dark. */
|
|
8
|
+
[data-doc-component="gallery/kanban"] {
|
|
9
|
+
margin: var(--doc-component-gap) 0;
|
|
10
|
+
> .gallery-kanban-face {
|
|
11
|
+
position: relative;
|
|
12
|
+
color: var(--doc-text);
|
|
13
|
+
font-size: 14px;
|
|
14
|
+
line-height: 1.45;
|
|
15
|
+
}
|
|
16
|
+
> .gallery-kanban-face > .gallery-kanban-head {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 8px;
|
|
20
|
+
min-height: 28px;
|
|
21
|
+
margin: 0 0 6px;
|
|
22
|
+
> .gallery-kanban-caption { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--doc-muted); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
23
|
+
> .gallery-kanban-gear {
|
|
24
|
+
display: none;
|
|
25
|
+
flex: none;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
width: 26px;
|
|
29
|
+
height: 26px;
|
|
30
|
+
padding: 0;
|
|
31
|
+
border: 0;
|
|
32
|
+
border-radius: 6px;
|
|
33
|
+
background: transparent;
|
|
34
|
+
color: var(--doc-muted);
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
opacity: 0;
|
|
37
|
+
transition: opacity .12s;
|
|
38
|
+
}
|
|
39
|
+
> .gallery-kanban-gear > svg { width: 16px; height: 16px; }
|
|
40
|
+
> .gallery-kanban-gear:hover { background: var(--doc-hover); color: var(--doc-text); }
|
|
41
|
+
> .gallery-kanban-gear[aria-expanded="true"] { background: var(--doc-press); color: var(--doc-text); opacity: 1; }
|
|
42
|
+
}
|
|
43
|
+
> .gallery-kanban-face > .gallery-kanban-head[hidden] { display: none; }
|
|
44
|
+
> .gallery-kanban-face[data-writes] > .gallery-kanban-head > .gallery-kanban-gear { display: inline-flex; }
|
|
45
|
+
> .gallery-kanban-face[data-writes]:hover > .gallery-kanban-head > .gallery-kanban-gear,
|
|
46
|
+
> .gallery-kanban-face[data-writes]:focus-within > .gallery-kanban-head > .gallery-kanban-gear { opacity: 1; }
|
|
47
|
+
> .gallery-kanban-face > .gallery-kanban-strip {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
gap: 6px 16px;
|
|
51
|
+
margin: 0 0 8px;
|
|
52
|
+
padding: 8px 10px;
|
|
53
|
+
border-radius: 8px;
|
|
54
|
+
background: var(--doc-hover);
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
> .gallery-kanban-setting { display: inline-flex; align-items: center; gap: 6px; color: var(--doc-muted); }
|
|
57
|
+
/* A select is a button that opens the kit's menu (DocKit's choices): no line, as every button. */
|
|
58
|
+
> .gallery-kanban-setting > select {
|
|
59
|
+
max-width: 180px;
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
height: 24px;
|
|
62
|
+
padding: 2px 6px;
|
|
63
|
+
border: 0;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
background: var(--doc-surface);
|
|
67
|
+
color: var(--doc-text);
|
|
68
|
+
font: inherit;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
> .gallery-kanban-face > .gallery-kanban-strip[hidden] { display: none; }
|
|
72
|
+
> .gallery-kanban-face > .gallery-kanban-line { margin: 0 0 8px; font-size: 13px; color: var(--doc-muted); }
|
|
73
|
+
> .gallery-kanban-face > .gallery-kanban-line[hidden] { display: none; }
|
|
74
|
+
> .gallery-kanban-face > .gallery-kanban-groups {
|
|
75
|
+
display: grid;
|
|
76
|
+
grid-auto-flow: column;
|
|
77
|
+
grid-auto-columns: minmax(200px, 1fr);
|
|
78
|
+
align-items: start;
|
|
79
|
+
gap: 10px;
|
|
80
|
+
overflow-x: auto;
|
|
81
|
+
padding-bottom: 4px;
|
|
82
|
+
scrollbar-width: thin;
|
|
83
|
+
}
|
|
84
|
+
> .gallery-kanban-face > .gallery-kanban-groups > .gallery-kanban-group {
|
|
85
|
+
min-width: 0;
|
|
86
|
+
padding: 8px;
|
|
87
|
+
border-radius: 10px;
|
|
88
|
+
background: var(--doc-hover);
|
|
89
|
+
> .gallery-kanban-group-head {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 8px;
|
|
93
|
+
margin: 0 2px 8px;
|
|
94
|
+
font-size: 13px;
|
|
95
|
+
> .gallery-kanban-name {
|
|
96
|
+
min-width: 0;
|
|
97
|
+
padding: 1px 8px;
|
|
98
|
+
border-radius: 999px;
|
|
99
|
+
background: var(--doc-surface);
|
|
100
|
+
border: 1px solid var(--doc-border);
|
|
101
|
+
font-weight: 500;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
white-space: nowrap;
|
|
104
|
+
text-overflow: ellipsis;
|
|
105
|
+
}
|
|
106
|
+
> .gallery-kanban-count { color: var(--doc-muted); font-variant-numeric: tabular-nums; }
|
|
107
|
+
}
|
|
108
|
+
> .gallery-kanban-cards {
|
|
109
|
+
display: grid;
|
|
110
|
+
gap: 6px;
|
|
111
|
+
min-height: 8px;
|
|
112
|
+
margin: 0;
|
|
113
|
+
padding: 0;
|
|
114
|
+
list-style: none;
|
|
115
|
+
}
|
|
116
|
+
> .gallery-kanban-add {
|
|
117
|
+
display: none;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 4px;
|
|
120
|
+
width: 100%;
|
|
121
|
+
margin: 6px 0 0;
|
|
122
|
+
padding: 4px 6px;
|
|
123
|
+
border: 0;
|
|
124
|
+
border-radius: 6px;
|
|
125
|
+
background: transparent;
|
|
126
|
+
color: var(--doc-muted);
|
|
127
|
+
font: inherit;
|
|
128
|
+
font-size: 13px;
|
|
129
|
+
text-align: start;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
}
|
|
132
|
+
> .gallery-kanban-add > svg { flex: none; width: 14px; height: 14px; }
|
|
133
|
+
> .gallery-kanban-add:hover { background: var(--doc-press); color: var(--doc-text); }
|
|
134
|
+
}
|
|
135
|
+
> .gallery-kanban-face > .gallery-kanban-groups[data-writes] > .gallery-kanban-group > .gallery-kanban-add { display: flex; }
|
|
136
|
+
> .gallery-kanban-face > .gallery-kanban-groups > .gallery-kanban-group > .gallery-kanban-cards > .gallery-kanban-card {
|
|
137
|
+
position: relative;
|
|
138
|
+
/* A card off the screen is not laid out (its size kept as it was last drawn): a board of 500 cards
|
|
139
|
+
costs a keystroke in its table what the cards in sight cost. */
|
|
140
|
+
content-visibility: auto;
|
|
141
|
+
contain-intrinsic-size: auto 64px;
|
|
142
|
+
border: 1px solid var(--doc-border);
|
|
143
|
+
border-radius: 8px;
|
|
144
|
+
background: var(--doc-surface);
|
|
145
|
+
box-shadow: 0 1px 2px rgb(15 15 15 / .04);
|
|
146
|
+
overflow-wrap: anywhere;
|
|
147
|
+
> .gallery-kanban-hold { padding: 8px 10px; }
|
|
148
|
+
> .gallery-kanban-hold > .gallery-kanban-title { display: block; font-weight: 500; }
|
|
149
|
+
> .gallery-kanban-hold > .gallery-kanban-title.gallery-kanban-untitled { color: var(--doc-muted); font-weight: 400; }
|
|
150
|
+
> .gallery-kanban-hold > .gallery-kanban-props { display: flex; flex-wrap: wrap; gap: 2px 10px; margin-top: 4px; font-size: 12px; color: var(--doc-muted); }
|
|
151
|
+
> .gallery-kanban-links { display: flex; flex-wrap: wrap; gap: 4px; padding: 0 10px 8px; margin-top: -2px; }
|
|
152
|
+
> .gallery-kanban-links > .gallery-kanban-link {
|
|
153
|
+
display: inline-flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
gap: 4px;
|
|
156
|
+
max-width: 100%;
|
|
157
|
+
padding: 1px 6px;
|
|
158
|
+
border-radius: 4px;
|
|
159
|
+
background: var(--doc-hover);
|
|
160
|
+
color: var(--doc-link);
|
|
161
|
+
font-size: 12px;
|
|
162
|
+
text-decoration: none;
|
|
163
|
+
}
|
|
164
|
+
> .gallery-kanban-links > .gallery-kanban-link > svg { flex: none; width: 13px; height: 13px; }
|
|
165
|
+
> .gallery-kanban-links > .gallery-kanban-link > .gallery-kanban-words { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
166
|
+
> .gallery-kanban-links > .gallery-kanban-link:hover { background: var(--doc-press); }
|
|
167
|
+
> .gallery-kanban-field {
|
|
168
|
+
display: block;
|
|
169
|
+
box-sizing: border-box;
|
|
170
|
+
width: calc(100% - 12px);
|
|
171
|
+
margin: 6px 6px 0;
|
|
172
|
+
padding: 3px 6px;
|
|
173
|
+
border: 1px solid var(--doc-accent);
|
|
174
|
+
border-radius: 6px;
|
|
175
|
+
background: var(--doc-surface);
|
|
176
|
+
color: var(--doc-text);
|
|
177
|
+
font: inherit;
|
|
178
|
+
font-weight: 500;
|
|
179
|
+
}
|
|
180
|
+
> .gallery-kanban-tools {
|
|
181
|
+
position: absolute;
|
|
182
|
+
top: 4px;
|
|
183
|
+
right: 4px;
|
|
184
|
+
display: none;
|
|
185
|
+
gap: 2px;
|
|
186
|
+
padding: 1px;
|
|
187
|
+
border: 1px solid var(--doc-border);
|
|
188
|
+
border-radius: 6px;
|
|
189
|
+
background: var(--doc-surface);
|
|
190
|
+
}
|
|
191
|
+
> .gallery-kanban-tools > button {
|
|
192
|
+
display: inline-flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
justify-content: center;
|
|
195
|
+
width: 22px;
|
|
196
|
+
height: 22px;
|
|
197
|
+
padding: 0;
|
|
198
|
+
border: 0;
|
|
199
|
+
border-radius: 4px;
|
|
200
|
+
background: transparent;
|
|
201
|
+
color: var(--doc-muted);
|
|
202
|
+
cursor: pointer;
|
|
203
|
+
}
|
|
204
|
+
> .gallery-kanban-tools > button > svg { width: 14px; height: 14px; }
|
|
205
|
+
> .gallery-kanban-tools > button:hover { background: var(--doc-hover); color: var(--doc-text); }
|
|
206
|
+
}
|
|
207
|
+
> .gallery-kanban-face > .gallery-kanban-groups[data-writes] > .gallery-kanban-group > .gallery-kanban-cards > .gallery-kanban-card:hover:not([data-editing]) > .gallery-kanban-tools { display: inline-flex; }
|
|
208
|
+
> .gallery-kanban-face > .gallery-kanban-groups[data-writes] > .gallery-kanban-group > .gallery-kanban-cards > .gallery-kanban-card > .gallery-kanban-hold { cursor: grab; touch-action: none; user-select: none; }
|
|
209
|
+
> .gallery-kanban-face > .gallery-kanban-groups > .gallery-kanban-group > .gallery-kanban-cards > .gallery-kanban-card[data-editing] > .gallery-kanban-hold > .gallery-kanban-title { display: none; }
|
|
210
|
+
> .gallery-kanban-face > .gallery-kanban-groups > .gallery-kanban-group > .gallery-kanban-cards > .gallery-kanban-card.doc-dragging { opacity: .45; }
|
|
211
|
+
> .gallery-kanban-face > .gallery-kanban-groups > .gallery-kanban-group > .gallery-kanban-cards > .gallery-kanban-drop { height: 3px; border-radius: 2px; background: var(--doc-accent); }
|
|
212
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 看板 (gallery/kanban)
|
|
2
|
+
|
|
3
|
+
A board of a table of the document: each row a card, grouped by a column (a task's status, a deal's stage). The table holds the data and stays an ordinary table the reader can edit; the board is a view of it. Use it for tasks, a pipeline, anything whose rows move through a few states. A task that needs more than a line: link its first cell to a document of its own, and the card opens it.
|
|
4
|
+
|
|
5
|
+
## How to write it
|
|
6
|
+
|
|
7
|
+
Write a table, then the board after it, as example.html does (`gallery block show gallery/kanban` prints it):
|
|
8
|
+
|
|
9
|
+
- the table: an ordinary table of the document whose `<table>` carries `data-doc-chart-source="<key>"`, a key of the document's own (a letter, then letters, digits, `_` or `-`; 32 at most): its header row names the columns, its first column is each card's title, the other columns its words under the title, a link in any cell a chip on the card. No merged cells, no `<tfoot>`, every row as long as the header: the board reads such a table and cannot change it.
|
|
10
|
+
- the board: `<div data-doc-component="gallery/kanban" data-doc-from="<key>">`, the same key, holding its settings, one `<script type="application/json">` on one line:
|
|
11
|
+
- `group`: the name of the column it groups by. Left out, it finds one: a column named like a status (状态, 进度, 阶段, status, stage), else the one whose values repeat most.
|
|
12
|
+
- `order`: the groups first, in this order, comma-separated (`"待办,进行中,已完成"`), each shown even with no card; any other value the rows hold comes after them.
|
|
13
|
+
|
|
14
|
+
Write `{}` for no settings. One table may have several views (another board, a 指标, a 图表), each with the same key.
|
|
15
|
+
|
|
16
|
+
The reader adds a board in Gallery from a table's menu, 视图 › 看板, which writes the key on both; it is not in "/", as a board needs its table.
|
|
17
|
+
|
|
18
|
+
## How it looks
|
|
19
|
+
|
|
20
|
+
A muted line saying what it groups by, then the groups side by side (scrolling sideways past the width of the page), each with its name, its count and its cards. Where the page edits: a card dragged to another group changes its cell of that column and puts its row there in the table, dragged within its group puts its row before the card it is dropped on; 新建 at a group's foot adds a row to the table; a card's ✎ renames it (its first cell), its 🗑 takes its row out; the ⚙ at the board's right picks the column it groups by. Each is one undo step of the document's. In an exported file, 演示视图, and for a table it cannot change, it only shows. Where the block is not, a reader has the table.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "gallery/metric",
|
|
3
|
+
"apiVersion": 1,
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"name": { "zh": "指标", "en": "Metric" },
|
|
6
|
+
"description": { "zh": "表格的视图:一列的计数、合计或平均,表格一改就跟着变", "en": "A view of a table: a count, sum or average of a column, following each edit of the table" },
|
|
7
|
+
"words": "zhibiao shuzi metric number kpi count sum average total 指标 数字 合计 平均 计数 统计 视图 view",
|
|
8
|
+
"parts": {
|
|
9
|
+
"settings": { "at": ":scope > script", "edit": "data" }
|
|
10
|
+
},
|
|
11
|
+
"runtime": { "generated": [":scope > .gallery-metric-face"] },
|
|
12
|
+
"places": ["document"],
|
|
13
|
+
"settings": [
|
|
14
|
+
{ "key": "op", "label": { "zh": "计算", "en": "Calculate" }, "type": "select", "options": ["count", "sum", "average"], "default": "count" },
|
|
15
|
+
{ "key": "column", "label": { "zh": "列", "en": "Column" }, "type": "text", "default": "" },
|
|
16
|
+
{ "key": "where", "label": { "zh": "只算这一列", "en": "Only where column" }, "type": "text", "default": "" },
|
|
17
|
+
{ "key": "is", "label": { "zh": "等于", "en": "is" }, "type": "text", "default": "" },
|
|
18
|
+
{ "key": "title", "label": { "zh": "名称", "en": "Name" }, "type": "text", "default": "" },
|
|
19
|
+
{ "key": "unit", "label": { "zh": "单位", "en": "Unit" }, "type": "text", "default": "" }
|
|
20
|
+
],
|
|
21
|
+
"needs": ["tables"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<table data-doc-chart-source="sales">
|
|
2
|
+
<thead><tr><th>客户</th><th>区域</th><th>金额</th><th>状态</th></tr></thead>
|
|
3
|
+
<tbody>
|
|
4
|
+
<tr><td>华东物流</td><td>华东</td><td>12,800</td><td>已成交</td></tr>
|
|
5
|
+
<tr><td>北方食品</td><td>华北</td><td>8,600</td><td>洽谈中</td></tr>
|
|
6
|
+
<tr><td>南方家居</td><td>华南</td><td>15,200</td><td>已成交</td></tr>
|
|
7
|
+
<tr><td>西部能源</td><td>西部</td><td>6,400</td><td>洽谈中</td></tr>
|
|
8
|
+
</tbody>
|
|
9
|
+
</table>
|
|
10
|
+
<div data-doc-component="gallery/metric" data-doc-from="sales">
|
|
11
|
+
<script type="application/json">{"op":"sum","column":"金额","unit":"元"}</script>
|
|
12
|
+
</div>
|
|
13
|
+
<div data-doc-component="gallery/metric" data-doc-from="sales">
|
|
14
|
+
<script type="application/json">{"where":"状态","is":"已成交","title":"已成交的单数"}</script>
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// gallery/metric (指标): a view of a table of the document (its manifest's needs hold "tables"; the table's
|
|
2
|
+
// frame carries the key its data-doc-from names, which the table's 视图 › puts on both): one number of
|
|
3
|
+
// the table, large, under its name. Its settings (its data part `settings`, JSON) say which: `op`
|
|
4
|
+
// "count" (the rows: the default), "sum" or "average" of the numbers of a `column` (the first column
|
|
5
|
+
// holding numbers where it names none; a cell holding no number is left out, as a chart leaves it out),
|
|
6
|
+
// over every row or those whose cell of `where` is `is`; `title` its name (one made of the rest where it
|
|
7
|
+
// gives none), `unit` the words after the number. It reads the table each time its rows change (the
|
|
8
|
+
// `data` signal: a cell typed in, a row added or taken out), and draws only what changed, so that it
|
|
9
|
+
// follows the reader's typing at once. Its ⚙, at its head while the page edits, opens its settings,
|
|
10
|
+
// each change one undo step of the document's (context.editJSON); it writes nothing else. The words
|
|
11
|
+
// are the page's language.
|
|
12
|
+
|
|
13
|
+
const WORDS = {
|
|
14
|
+
zh: {
|
|
15
|
+
settings: "设置",
|
|
16
|
+
op: "计算",
|
|
17
|
+
ops: { count: "计数", sum: "合计", average: "平均" },
|
|
18
|
+
column: "列",
|
|
19
|
+
where: "只算",
|
|
20
|
+
every: "所有行",
|
|
21
|
+
is: "等于",
|
|
22
|
+
title: "名称",
|
|
23
|
+
unit: "单位",
|
|
24
|
+
rows: "行数",
|
|
25
|
+
named: (/** @type {string} */ where, /** @type {string} */ is) => `${where}为“${is}”`,
|
|
26
|
+
sum: (/** @type {string} */ column) => `${column}合计`,
|
|
27
|
+
average: (/** @type {string} */ column) => `${column}平均`,
|
|
28
|
+
of: (/** @type {string} */ label, /** @type {string} */ where) => `${label}(${where})`,
|
|
29
|
+
counted: (/** @type {number} */ count) => `按 ${count} 行算`,
|
|
30
|
+
outOf: (/** @type {number} */ count) => `共 ${count} 行`,
|
|
31
|
+
unbound: "这个指标还没有接到表格上:在表格的菜单里选“视图 › 指标”。",
|
|
32
|
+
unread: "读不到这个指标的表格:它不在页面上了,或者有合并的单元格。",
|
|
33
|
+
missing: (/** @type {string} */ name) => `找不到列“${name}”:表格的列名改过了吗?`,
|
|
34
|
+
noNumbers: "表格里没有写数的列:合计和平均要一列数。",
|
|
35
|
+
},
|
|
36
|
+
en: {
|
|
37
|
+
settings: "Settings",
|
|
38
|
+
op: "Calculate",
|
|
39
|
+
ops: { count: "Count", sum: "Sum", average: "Average" },
|
|
40
|
+
column: "Column",
|
|
41
|
+
where: "Only where",
|
|
42
|
+
every: "Every row",
|
|
43
|
+
is: "is",
|
|
44
|
+
title: "Name",
|
|
45
|
+
unit: "Unit",
|
|
46
|
+
rows: "Rows",
|
|
47
|
+
named: (/** @type {string} */ where, /** @type {string} */ is) => `${where}: ${is}`,
|
|
48
|
+
sum: (/** @type {string} */ column) => `Total ${column}`,
|
|
49
|
+
average: (/** @type {string} */ column) => `Average ${column}`,
|
|
50
|
+
of: (/** @type {string} */ label, /** @type {string} */ where) => `${label} (${where})`,
|
|
51
|
+
counted: (/** @type {number} */ count) => `Of ${count} rows`,
|
|
52
|
+
outOf: (/** @type {number} */ count) => `${count} rows in all`,
|
|
53
|
+
unbound: "This metric reads no table yet: pick View › Metric in a table's menu.",
|
|
54
|
+
unread: "This metric's table cannot be read: it is not on the page, or it has merged cells.",
|
|
55
|
+
missing: (/** @type {string} */ name) => `No column “${name}”: was the table's column renamed?`,
|
|
56
|
+
noNumbers: "The table has no column of numbers: a sum and an average need one.",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const GEAR = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 4.5h6M12 4.5h1M3 11.5h1M7 11.5h6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="10.5" cy="4.5" r="1.5" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="5.5" cy="11.5" r="1.5" fill="none" stroke="currentColor" stroke-width="1.3"/></svg>';
|
|
61
|
+
|
|
62
|
+
/** The language the page speaks: the UI's where Gallery says it (data-ge-language), else its lang's. */
|
|
63
|
+
function languageOf(/** @type {HTMLElement} */ root) {
|
|
64
|
+
const ui = root.getAttribute("data-ge-language");
|
|
65
|
+
return (ui !== null ? ui === "en" : /^en(?:-|$)/i.test(root.lang)) ? "en" : "zh";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* What its settings ask (context.readJSON's; {} where none is set).
|
|
70
|
+
* @param {any} settings
|
|
71
|
+
* @returns {{ op: "count" | "sum" | "average", column: string, where: string, is: string, title: string, unit: string }}
|
|
72
|
+
*/
|
|
73
|
+
function askedOf(settings) {
|
|
74
|
+
const table = settings !== null && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
75
|
+
const text = (/** @type {string} */ key) => (typeof table[key] === "string" ? table[key].trim() : typeof table[key] === "number" ? String(table[key]) : "");
|
|
76
|
+
const op = table.op === "sum" || table.op === "average" ? table.op : "count";
|
|
77
|
+
return { op, column: text("column"), where: text("where"), is: text("is"), title: text("title"), unit: text("unit") };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The columns of `table` holding numbers: a number in a cell at least, and no words. */
|
|
81
|
+
const numberColumns = (/** @type {DocTableView} */ table) => table.columns.filter((name) => table.rows.some((row) => typeof row.values[name] === "number") && table.rows.every((row) => row.values[name] === null || typeof row.values[name] === "number"));
|
|
82
|
+
|
|
83
|
+
/** Whether a row's value is what `is` says: its words, or the same number. */
|
|
84
|
+
function matches(/** @type {string | number | null | undefined} */ value, /** @type {string} */ is) {
|
|
85
|
+
if (value === null || value === undefined) return is === "";
|
|
86
|
+
if (typeof value === "number") {
|
|
87
|
+
const plain = Number(is.replace(/[\s,,]/g, ""));
|
|
88
|
+
if (is.trim() !== "" && Number.isFinite(plain)) return plain === value;
|
|
89
|
+
}
|
|
90
|
+
return String(value).trim() === is;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The metric of `table` as `asked` says: { value, label, foot } or { line } where there is none to say.
|
|
95
|
+
* @param {DocTableView} table
|
|
96
|
+
* @param {ReturnType<typeof askedOf>} asked
|
|
97
|
+
* @param {typeof WORDS.zh} words
|
|
98
|
+
*/
|
|
99
|
+
function metricOf(table, asked, words) {
|
|
100
|
+
if (asked.where && !table.columns.includes(asked.where)) return { line: words.missing(asked.where) };
|
|
101
|
+
const rows = asked.where ? table.rows.filter((row) => matches(row.values[asked.where], asked.is)) : [...table.rows];
|
|
102
|
+
const where = asked.where ? words.named(asked.where, asked.is) : "";
|
|
103
|
+
if (asked.op === "count") {
|
|
104
|
+
return { value: rows.length, label: asked.title || where || words.rows, foot: asked.where ? words.outOf(table.rows.length) : null };
|
|
105
|
+
}
|
|
106
|
+
const column = asked.column || numberColumns(table)[0] || "";
|
|
107
|
+
if (!column) return { line: words.noNumbers };
|
|
108
|
+
if (!table.columns.includes(column)) return { line: words.missing(column) };
|
|
109
|
+
const numbers = rows.map((row) => row.values[column]).filter((value) => typeof value === "number");
|
|
110
|
+
const sum = numbers.reduce((total, value) => total + /** @type {number} */ (value), 0);
|
|
111
|
+
const value = asked.op === "sum" ? sum : numbers.length > 0 ? sum / numbers.length : null;
|
|
112
|
+
const named = asked.op === "sum" ? words.sum(column) : words.average(column);
|
|
113
|
+
return { value, label: asked.title || (where ? words.of(named, where) : named), foot: words.counted(numbers.length) };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* An element of the face, of `kind` (its class gallery-metric-<kind>).
|
|
118
|
+
* @template {keyof HTMLElementTagNameMap} K
|
|
119
|
+
* @param {K} tag
|
|
120
|
+
* @param {string} kind
|
|
121
|
+
* @param {string} [text]
|
|
122
|
+
*/
|
|
123
|
+
function made(tag, kind, text) {
|
|
124
|
+
const node = document.createElement(tag);
|
|
125
|
+
node.className = `gallery-metric-${kind}`;
|
|
126
|
+
if (text !== undefined) node.textContent = text;
|
|
127
|
+
return node;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The settings strip: a field a setting, a select of its choices (`options()`, [value, words]) or a
|
|
132
|
+
* text, shown while `shown()` says so, each committed (DocKit.input: Enter, the field left, a choice
|
|
133
|
+
* made) as one write of the settings part, a key set or, for "", taken off.
|
|
134
|
+
* @param {DocComponentContext} context
|
|
135
|
+
* @param {{ key: string, label: string, options?: () => [string, string][], shown?: () => boolean, value: () => string }[]} fields
|
|
136
|
+
*/
|
|
137
|
+
function settingsStrip(context, fields) {
|
|
138
|
+
const strip = made("div", "strip");
|
|
139
|
+
strip.hidden = true;
|
|
140
|
+
const write = (/** @type {string} */ key, /** @type {string} */ value) => context.editJSON("settings", (draft) => {
|
|
141
|
+
const settings = draft !== null && typeof draft === "object" && !Array.isArray(draft) ? draft : {};
|
|
142
|
+
if (value.trim() === "") delete settings[key];
|
|
143
|
+
else settings[key] = value.trim();
|
|
144
|
+
return settings;
|
|
145
|
+
});
|
|
146
|
+
const controls = fields.map((field) => {
|
|
147
|
+
const label = made("label", "setting");
|
|
148
|
+
const control = field.options ? document.createElement("select") : document.createElement("input");
|
|
149
|
+
if (control instanceof HTMLInputElement) control.type = "text";
|
|
150
|
+
label.append(made("span", "setting-name", field.label), control);
|
|
151
|
+
strip.append(label);
|
|
152
|
+
const handle = DocKit.input(control, { commit: (value) => void write(field.key, value), signal: context.signal });
|
|
153
|
+
return { field, label, control, handle, signed: "" };
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
strip,
|
|
157
|
+
/** The fields as the settings and the table say now: their choices, which show, their values. */
|
|
158
|
+
update() {
|
|
159
|
+
for (const each of controls) {
|
|
160
|
+
each.label.hidden = each.field.shown ? !each.field.shown() : false;
|
|
161
|
+
const { control, field } = each;
|
|
162
|
+
if (field.options && control instanceof HTMLSelectElement) {
|
|
163
|
+
const options = field.options();
|
|
164
|
+
const signed = JSON.stringify(options);
|
|
165
|
+
if (signed !== each.signed) {
|
|
166
|
+
each.signed = signed;
|
|
167
|
+
control.replaceChildren(...options.map(([value, words]) => new Option(words, value)));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
each.handle.set(field.value());
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
DocKit.component({
|
|
177
|
+
id: "gallery/metric",
|
|
178
|
+
on: ["source", "data", "editable"],
|
|
179
|
+
mount(host, context) {
|
|
180
|
+
const language = languageOf(document.documentElement);
|
|
181
|
+
const words = WORDS[language];
|
|
182
|
+
const format = new Intl.NumberFormat(language === "en" ? "en-US" : "zh-CN", { maximumFractionDigits: 2 });
|
|
183
|
+
const face = made("div", "face");
|
|
184
|
+
const head = made("div", "head");
|
|
185
|
+
const label = made("span", "label");
|
|
186
|
+
const gear = made("button", "gear");
|
|
187
|
+
gear.type = "button";
|
|
188
|
+
gear.title = words.settings;
|
|
189
|
+
gear.setAttribute("aria-label", words.settings);
|
|
190
|
+
gear.setAttribute("aria-expanded", "false");
|
|
191
|
+
gear.innerHTML = GEAR;
|
|
192
|
+
head.append(label, gear);
|
|
193
|
+
const value = made("p", "value");
|
|
194
|
+
const number = made("span", "number");
|
|
195
|
+
const unit = made("span", "unit");
|
|
196
|
+
value.append(number, unit);
|
|
197
|
+
const foot = made("p", "foot");
|
|
198
|
+
const line = made("p", "line");
|
|
199
|
+
|
|
200
|
+
/** @type {DocTableView | null} */
|
|
201
|
+
let table = null;
|
|
202
|
+
let asked = askedOf({});
|
|
203
|
+
const columnsOf = () => (table ? table.columns.map((name) => /** @type {[string, string]} */ ([name, name])) : []);
|
|
204
|
+
const { strip, update: updateStrip } = settingsStrip(context, [
|
|
205
|
+
{ key: "op", label: words.op, options: () => [["count", words.ops.count], ["sum", words.ops.sum], ["average", words.ops.average]], value: () => asked.op },
|
|
206
|
+
{ key: "column", label: words.column, options: () => (table ? numberColumns(table).map((name) => /** @type {[string, string]} */ ([name, name])) : []), shown: () => asked.op !== "count", value: () => asked.column || (table ? numberColumns(table)[0] ?? "" : "") },
|
|
207
|
+
{ key: "where", label: words.where, options: () => [["", words.every], ...columnsOf()], value: () => asked.where },
|
|
208
|
+
{ key: "is", label: words.is, shown: () => asked.where !== "", value: () => asked.is },
|
|
209
|
+
{ key: "title", label: words.title, value: () => asked.title },
|
|
210
|
+
{ key: "unit", label: words.unit, value: () => asked.unit },
|
|
211
|
+
]);
|
|
212
|
+
face.append(head, strip, value, foot, line);
|
|
213
|
+
host.append(face);
|
|
214
|
+
gear.addEventListener("click", () => {
|
|
215
|
+
strip.hidden = !strip.hidden;
|
|
216
|
+
gear.setAttribute("aria-expanded", String(!strip.hidden));
|
|
217
|
+
}, { signal: context.signal });
|
|
218
|
+
|
|
219
|
+
const show = (/** @type {HTMLElement} */ node, /** @type {string | null} */ text) => {
|
|
220
|
+
node.hidden = text === null;
|
|
221
|
+
if (node.textContent !== (text ?? "")) node.textContent = text ?? "";
|
|
222
|
+
};
|
|
223
|
+
const drawNow = () => {
|
|
224
|
+
table = context.table();
|
|
225
|
+
asked = askedOf(context.readJSON("settings"));
|
|
226
|
+
face.toggleAttribute("data-writes", context.canEdit());
|
|
227
|
+
head.hidden = !table;
|
|
228
|
+
updateStrip();
|
|
229
|
+
const metric = table ? metricOf(table, asked, words) : { line: host.hasAttribute("data-doc-from") ? words.unread : words.unbound };
|
|
230
|
+
if (metric.line !== undefined) {
|
|
231
|
+
show(label, asked.title || null);
|
|
232
|
+
value.hidden = true;
|
|
233
|
+
show(foot, null);
|
|
234
|
+
show(line, metric.line);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
show(label, metric.label);
|
|
238
|
+
value.hidden = false;
|
|
239
|
+
const said = metric.value === null ? "—" : format.format(metric.value);
|
|
240
|
+
if (number.textContent !== said) number.textContent = said;
|
|
241
|
+
show(unit, asked.unit || null);
|
|
242
|
+
show(foot, metric.foot);
|
|
243
|
+
show(line, null);
|
|
244
|
+
};
|
|
245
|
+
drawNow();
|
|
246
|
+
return { update: () => context.frame(drawNow) };
|
|
247
|
+
},
|
|
248
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 120"><rect width="240" height="120" rx="8" fill="#f2f4f7"/><rect x="16" y="20" width="98" height="80" rx="8" fill="#ffffff" stroke="#d0d5dd"/><rect x="28" y="32" width="44" height="6" rx="3" fill="#98a2b3"/><rect x="28" y="48" width="64" height="18" rx="4" fill="#344054"/><rect x="28" y="78" width="34" height="5" rx="2.5" fill="#d0d5dd"/><rect x="126" y="20" width="98" height="80" rx="8" fill="#ffffff" stroke="#d0d5dd"/><rect x="138" y="32" width="52" height="6" rx="3" fill="#98a2b3"/><rect x="138" y="48" width="30" height="18" rx="4" fill="#2678d8"/><rect x="172" y="58" width="14" height="8" rx="2" fill="#98a2b3"/><rect x="138" y="78" width="40" height="5" rx="2.5" fill="#d0d5dd"/></svg>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Every selector starts at the instance and goes down a step at a time (>), so that nothing here
|
|
2
|
+
reaches the rest of the page. A framed card of the kit's (its border, its radius, its surface): its
|
|
3
|
+
name small and muted, the number large under it with its unit in smaller muted print, a muted line
|
|
4
|
+
of what it was counted of (the number's weight, tracking and unit the kit's card tokens, as a page's
|
|
5
|
+
cards draw theirs); the settings (⚙) at the right of its head while the page edits, the strip
|
|
6
|
+
they open under it. Side by side in a document's columns they read as a row of tiles. The instance
|
|
7
|
+
is a container, so that the number fits a narrow column. Notion-flat, light and dark. */
|
|
8
|
+
[data-doc-component="gallery/metric"] {
|
|
9
|
+
container-type: inline-size;
|
|
10
|
+
margin: var(--doc-component-gap) 0;
|
|
11
|
+
padding: 14px 18px 14px;
|
|
12
|
+
border: 1px solid var(--doc-border);
|
|
13
|
+
border-radius: 10px;
|
|
14
|
+
background: var(--doc-surface);
|
|
15
|
+
> .gallery-metric-face { color: var(--doc-text); }
|
|
16
|
+
> .gallery-metric-face > .gallery-metric-head {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 8px;
|
|
20
|
+
min-height: 24px;
|
|
21
|
+
> .gallery-metric-label { flex: 1 1 auto; min-width: 0; font-size: 13px; line-height: 1.4; color: var(--doc-muted); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
22
|
+
> .gallery-metric-label[hidden] { display: block; visibility: hidden; }
|
|
23
|
+
> .gallery-metric-gear {
|
|
24
|
+
display: none;
|
|
25
|
+
flex: none;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
width: 24px;
|
|
29
|
+
height: 24px;
|
|
30
|
+
margin: -2px -6px -2px 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
border: 0;
|
|
33
|
+
border-radius: 6px;
|
|
34
|
+
background: transparent;
|
|
35
|
+
color: var(--doc-muted);
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
opacity: 0;
|
|
38
|
+
transition: opacity .12s;
|
|
39
|
+
}
|
|
40
|
+
> .gallery-metric-gear > svg { width: 16px; height: 16px; }
|
|
41
|
+
> .gallery-metric-gear:hover { background: var(--doc-hover); color: var(--doc-text); }
|
|
42
|
+
> .gallery-metric-gear[aria-expanded="true"] { background: var(--doc-press); color: var(--doc-text); opacity: 1; }
|
|
43
|
+
}
|
|
44
|
+
> .gallery-metric-face > .gallery-metric-head[hidden] { display: none; }
|
|
45
|
+
> .gallery-metric-face[data-writes] > .gallery-metric-head > .gallery-metric-gear { display: inline-flex; }
|
|
46
|
+
> .gallery-metric-face[data-writes]:hover > .gallery-metric-head > .gallery-metric-gear,
|
|
47
|
+
> .gallery-metric-face[data-writes]:focus-within > .gallery-metric-head > .gallery-metric-gear { opacity: 1; }
|
|
48
|
+
> .gallery-metric-face > .gallery-metric-strip {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
gap: 6px 14px;
|
|
52
|
+
margin: 6px 0 4px;
|
|
53
|
+
padding: 8px 10px;
|
|
54
|
+
border-radius: 8px;
|
|
55
|
+
background: var(--doc-hover);
|
|
56
|
+
font-size: 12px;
|
|
57
|
+
> .gallery-metric-setting { display: inline-flex; align-items: center; gap: 6px; color: var(--doc-muted); }
|
|
58
|
+
> .gallery-metric-setting[hidden] { display: none; }
|
|
59
|
+
> .gallery-metric-setting > select,
|
|
60
|
+
> .gallery-metric-setting > input {
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
max-width: 160px;
|
|
63
|
+
height: 24px;
|
|
64
|
+
padding: 2px 6px;
|
|
65
|
+
border: 1px solid var(--doc-border);
|
|
66
|
+
border-radius: 6px;
|
|
67
|
+
background: var(--doc-surface);
|
|
68
|
+
color: var(--doc-text);
|
|
69
|
+
font: inherit;
|
|
70
|
+
}
|
|
71
|
+
/* A select is a button that opens the kit's menu (DocKit's choices): no line, as every button. */
|
|
72
|
+
> .gallery-metric-setting > select { border: 0; cursor: pointer; }
|
|
73
|
+
> .gallery-metric-setting > input { width: 110px; }
|
|
74
|
+
}
|
|
75
|
+
> .gallery-metric-face > .gallery-metric-strip[hidden] { display: none; }
|
|
76
|
+
> .gallery-metric-face > .gallery-metric-value {
|
|
77
|
+
margin: 2px 0 0;
|
|
78
|
+
font-size: clamp(24px, 9cqi, 34px);
|
|
79
|
+
line-height: 1.2;
|
|
80
|
+
font-weight: var(--gallery-numeral-weight);
|
|
81
|
+
letter-spacing: var(--gallery-numeral-tracking);
|
|
82
|
+
font-variant-numeric: tabular-nums;
|
|
83
|
+
overflow-wrap: anywhere;
|
|
84
|
+
> .gallery-metric-unit { margin-inline-start: .25em; font-size: max(12px, var(--gallery-unit-size)); font-weight: 500; letter-spacing: 0; color: var(--doc-muted); }
|
|
85
|
+
> .gallery-metric-unit[hidden] { display: none; }
|
|
86
|
+
}
|
|
87
|
+
> .gallery-metric-face > .gallery-metric-value[hidden] { display: none; }
|
|
88
|
+
> .gallery-metric-face > .gallery-metric-foot { margin: 4px 0 0; font-size: 12px; line-height: 1.4; color: var(--doc-muted); }
|
|
89
|
+
> .gallery-metric-face > .gallery-metric-foot[hidden] { display: none; }
|
|
90
|
+
> .gallery-metric-face > .gallery-metric-line { margin: 2px 0 0; font-size: 13px; line-height: 1.5; color: var(--doc-muted); }
|
|
91
|
+
> .gallery-metric-face > .gallery-metric-line[hidden] { display: none; }
|
|
92
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 指标 (gallery/metric)
|
|
2
|
+
|
|
3
|
+
One number of a table of the document, large under its name: how many rows (all, or those of one status), or the sum or the average of a column of numbers. It is a view of the table, read again as the table is typed in: the table holds the data, the metric never writes it. Use one or a few above a table of sales, tasks, expenses; put two or three side by side in the document's columns for a row of tiles.
|
|
4
|
+
|
|
5
|
+
## How to write it
|
|
6
|
+
|
|
7
|
+
Write a table, then the metric, as example.html does (`gallery block show gallery/metric` prints it):
|
|
8
|
+
|
|
9
|
+
- the table: an ordinary table of the document whose `<table>` carries `data-doc-chart-source="<key>"`, a key of the document's own (a letter, then letters, digits, `_` or `-`; 32 at most), its header row naming the columns. No merged cells.
|
|
10
|
+
- the metric: `<div data-doc-component="gallery/metric" data-doc-from="<key>">`, the same key, holding its settings, one `<script type="application/json">` on one line, each optional:
|
|
11
|
+
- `op`: `"count"` (the rows; the default), `"sum"` or `"average"`;
|
|
12
|
+
- `column`: the column summed or averaged (its cells' numbers; a cell with no number is left out). Left out: the first column of numbers;
|
|
13
|
+
- `where` and `is`: only the rows whose cell of the column `where` is `is` (`{"where":"状态","is":"已成交"}`);
|
|
14
|
+
- `title`: its name. Left out, one is made (行数, 金额合计, 状态为“已成交”);
|
|
15
|
+
- `unit`: the words after the number (元, %, 个).
|
|
16
|
+
|
|
17
|
+
One table may have several views (a 看板, a 图表, more metrics), each with the same key.
|
|
18
|
+
|
|
19
|
+
The reader adds a metric in Gallery from a table's menu, 视图 › 指标, which writes the key on both; it is not in "/", as a metric needs its table. Its ⚙ (where the page edits) changes its settings.
|
|
20
|
+
|
|
21
|
+
## How it looks
|
|
22
|
+
|
|
23
|
+
A framed card: its name small and muted, the number large with its unit, and a muted line of what it counted (按 4 行算, 共 12 行). Numbers keep two decimals at most and group their thousands. In an exported file it shows the number the table holds there.
|