@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,295 @@
|
|
|
1
|
+
// gallery/habit (习惯打卡): one habit, checked in once a day. Its data part, a script of JSON, holds the
|
|
2
|
+
// habit's name and the days it was done, each YYYY-MM-DD by the reader's clock, in order:
|
|
3
|
+
// { "name": "喝八杯水", "days": ["2026-11-05", "2026-11-06"] }. The card draws the name in a field of its
|
|
4
|
+
// own (DocKit.input: written as the reader presses Enter or leaves it, never while an input method
|
|
5
|
+
// composes), the streak (the days in a row up to today, or up to yesterday while today is not checked
|
|
6
|
+
// yet, so that a streak lasts the day it is to be kept), how many days in all, the last seven days as
|
|
7
|
+
// dots, where the card has room the last 30 days as a ring (`rateOf`: the days done of them in its
|
|
8
|
+
// middle), and a button that checks today in, or takes today's check-in back when it is there. Each press
|
|
9
|
+
// and each name is one context.editJSON, one undo step, the JSON written back on one line as it was:
|
|
10
|
+
// what the reader does is in the file, and nothing else is (the streak and the dots are worked out as
|
|
11
|
+
// it draws). It draws from the part on every `source` (an undo, an Agent's write) and again as a day
|
|
12
|
+
// begins (the kit's `clock`, "day"), so that "today" moves at midnight. Where nothing takes its writes
|
|
13
|
+
// (an export, a page that has not come to edit yet: context.canEdit() false) the name is read-only and
|
|
14
|
+
// the button a plain line saying whether today is done. Its words are the page's language (the UI's
|
|
15
|
+
// where Gallery says it, else the page's lang). Its head is the cards' (the kit's card tokens): the chip,
|
|
16
|
+
// a flame for the streak, before the name, the days in all its quiet meta at the end; the streak is its
|
|
17
|
+
// label (连续打卡) over its count, large, and its unit. A habit never checked in (no days at all) shows
|
|
18
|
+
// neither the streak nor the ring, two zeros, but a muted line inviting the first (the face's data-fresh),
|
|
19
|
+
// the week and the button as ever; they come back with the first check-in. Start a block of your own from
|
|
20
|
+
// it with gallery block new <id> --recipe habit.
|
|
21
|
+
|
|
22
|
+
const WORDS = {
|
|
23
|
+
zh: {
|
|
24
|
+
name: "给习惯起个名字",
|
|
25
|
+
check: "打卡",
|
|
26
|
+
checked: "已打卡",
|
|
27
|
+
undo: "再按一下,撤回今天的打卡",
|
|
28
|
+
todayDone: "今天已打卡",
|
|
29
|
+
todayOpen: "今天还没打卡",
|
|
30
|
+
streak: "连续打卡",
|
|
31
|
+
unit: () => "天",
|
|
32
|
+
total: (/** @type {number} */ days) => `共 ${days} 天`,
|
|
33
|
+
rate: "近 30 天",
|
|
34
|
+
fresh: "还没打过卡,今天开始第 1 天",
|
|
35
|
+
rateSaid: (/** @type {number} */ done) => `近 30 天打卡 ${done} 天`,
|
|
36
|
+
week: "最近七天",
|
|
37
|
+
weekdays: ["日", "一", "二", "三", "四", "五", "六"],
|
|
38
|
+
today: "今",
|
|
39
|
+
day: (/** @type {number} */ month, /** @type {number} */ day, /** @type {boolean} */ done) => `${month}月${day}日${done ? ",已打卡" : ",没打卡"}`,
|
|
40
|
+
},
|
|
41
|
+
en: {
|
|
42
|
+
name: "Name this habit",
|
|
43
|
+
check: "Check in",
|
|
44
|
+
checked: "Done",
|
|
45
|
+
undo: "Press again to take today's check-in back",
|
|
46
|
+
todayDone: "Done today",
|
|
47
|
+
todayOpen: "Not done today",
|
|
48
|
+
streak: "Streak",
|
|
49
|
+
unit: (/** @type {number} */ days) => (days === 1 ? "day" : "days"),
|
|
50
|
+
total: (/** @type {number} */ days) => `${days} ${days === 1 ? "day" : "days"} in all`,
|
|
51
|
+
rate: "30 days",
|
|
52
|
+
fresh: "No check-ins yet: today can be day 1",
|
|
53
|
+
rateSaid: (/** @type {number} */ done) => `Done ${done} of the last 30 days`,
|
|
54
|
+
week: "The last seven days",
|
|
55
|
+
weekdays: ["S", "M", "T", "W", "T", "F", "S"],
|
|
56
|
+
today: "Today",
|
|
57
|
+
day: (/** @type {number} */ month, /** @type {number} */ day, /** @type {boolean} */ done) => `${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][month - 1]} ${day}${done ? ", done" : ", not done"}`,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** The language the page speaks: the UI's where Gallery says it (data-ge-language), else its lang's. */
|
|
62
|
+
function languageOf(/** @type {HTMLElement} */ root) {
|
|
63
|
+
const ui = root.getAttribute("data-ge-language");
|
|
64
|
+
return (ui !== null ? ui === "en" : /^en(?:-|$)/i.test(root.lang)) ? "en" : "zh";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A day of the reader's clock as the data writes it, YYYY-MM-DD.
|
|
71
|
+
* @param {Date} date
|
|
72
|
+
*/
|
|
73
|
+
function dayOf(date) {
|
|
74
|
+
const two = (/** @type {number} */ value) => String(value).padStart(2, "0");
|
|
75
|
+
return `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The day `count` days after `day` (before it for a count below 0), both YYYY-MM-DD, by the calendar
|
|
80
|
+
* (counted in UTC, so that a change of the clocks changes nothing).
|
|
81
|
+
* @param {string} day
|
|
82
|
+
* @param {number} count
|
|
83
|
+
*/
|
|
84
|
+
function dayAfter(day, count) {
|
|
85
|
+
const [year, month, date] = day.split("-").map(Number);
|
|
86
|
+
const moved = new Date(Date.UTC(year, month - 1, date + count));
|
|
87
|
+
const two = (/** @type {number} */ value) => String(value).padStart(2, "0");
|
|
88
|
+
return `${moved.getUTCFullYear()}-${two(moved.getUTCMonth() + 1)}-${two(moved.getUTCDate())}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The habit `data` is (context.readJSON's, or the draft context.editJSON hands a change): its name ("" for
|
|
93
|
+
* none yet) and its days, as they stand; throws saying why it is none, and the block draws nothing over it.
|
|
94
|
+
* @param {any} data
|
|
95
|
+
* @returns {{ name: string, days: string[] }}
|
|
96
|
+
*/
|
|
97
|
+
function readHabit(data) {
|
|
98
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) throw new Error("its data is no object: a habit holds { \"name\": \"…\", \"days\": [\"YYYY-MM-DD\", …] }");
|
|
99
|
+
const name = data.name ?? "";
|
|
100
|
+
if (typeof name !== "string") throw new Error("its name is no string");
|
|
101
|
+
const days = data.days ?? [];
|
|
102
|
+
if (!Array.isArray(days) || !days.every((day) => typeof day === "string" && DAY.test(day))) throw new Error("its days are no list of dates written YYYY-MM-DD");
|
|
103
|
+
return { name, days };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The days in a row that end today, or yesterday while today is not checked yet (a streak lasts the day
|
|
108
|
+
* it is to be kept); 0 when neither was done.
|
|
109
|
+
* @param {string[]} days
|
|
110
|
+
* @param {string} today
|
|
111
|
+
*/
|
|
112
|
+
function streakOf(days, today) {
|
|
113
|
+
const done = new Set(days);
|
|
114
|
+
let day = done.has(today) ? today : dayAfter(today, -1);
|
|
115
|
+
let count = 0;
|
|
116
|
+
while (done.has(day)) {
|
|
117
|
+
count += 1;
|
|
118
|
+
day = dayAfter(day, -1);
|
|
119
|
+
}
|
|
120
|
+
return count;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* `days` with `today` checked in, or taken back where it was: in order, each once.
|
|
125
|
+
* @param {string[]} days
|
|
126
|
+
* @param {string} today
|
|
127
|
+
*/
|
|
128
|
+
function toggled(days, today) {
|
|
129
|
+
const done = new Set(days);
|
|
130
|
+
if (done.has(today)) done.delete(today);
|
|
131
|
+
else done.add(today);
|
|
132
|
+
return [...done].sort();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The seven days that end today, the oldest first: each its day (YYYY-MM-DD), its weekday (0 for
|
|
137
|
+
* Sunday) and whether it was done.
|
|
138
|
+
* @param {string[]} days
|
|
139
|
+
* @param {string} today
|
|
140
|
+
*/
|
|
141
|
+
function weekOf(days, today) {
|
|
142
|
+
const done = new Set(days);
|
|
143
|
+
return Array.from({ length: 7 }, (_, index) => {
|
|
144
|
+
const day = dayAfter(today, index - 6);
|
|
145
|
+
const [year, month, date] = day.split("-").map(Number);
|
|
146
|
+
return { day, weekday: new Date(Date.UTC(year, month - 1, date)).getUTCDay(), done: done.has(day) };
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* How many of the `span` days that end today were done (the ring's count: 30 days by default).
|
|
152
|
+
* @param {string[]} days
|
|
153
|
+
* @param {string} today
|
|
154
|
+
* @param {number} [span]
|
|
155
|
+
*/
|
|
156
|
+
function rateOf(days, today, span = 30) {
|
|
157
|
+
const first = dayAfter(today, 1 - span);
|
|
158
|
+
return new Set(days.filter((day) => day >= first && day <= today)).size;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Today checked in, or taken back: one write (context.editJSON), what a press of the button calls, and
|
|
163
|
+
* the example too. Whether it was taken.
|
|
164
|
+
* @param {DocComponentContext} context
|
|
165
|
+
* @param {string} today
|
|
166
|
+
*/
|
|
167
|
+
function pressToday(context, today) {
|
|
168
|
+
return context.editJSON("habit", (data) => {
|
|
169
|
+
data.days = toggled(readHabit(data).days, today);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The habit named `name`, one write (context.editJSON): what the name's field calls once the reader is
|
|
175
|
+
* done with it. Whether it was taken.
|
|
176
|
+
* @param {DocComponentContext} context
|
|
177
|
+
* @param {string} name
|
|
178
|
+
*/
|
|
179
|
+
function rename(context, name) {
|
|
180
|
+
return context.editJSON("habit", (data) => {
|
|
181
|
+
readHabit(data);
|
|
182
|
+
data.name = name.trim();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* An element of the face, of `kind` (its class).
|
|
188
|
+
* @template {keyof HTMLElementTagNameMap} K
|
|
189
|
+
* @param {K} tag
|
|
190
|
+
* @param {string} kind
|
|
191
|
+
*/
|
|
192
|
+
function made(tag, kind) {
|
|
193
|
+
const node = document.createElement(tag);
|
|
194
|
+
node.className = `gallery-habit-${kind}`;
|
|
195
|
+
return node;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
DocKit.component({
|
|
199
|
+
id: "gallery/habit",
|
|
200
|
+
on: ["source", "editable", "clock"],
|
|
201
|
+
mount(host, context) {
|
|
202
|
+
const words = WORDS[languageOf(document.documentElement)];
|
|
203
|
+
const face = made("div", "face");
|
|
204
|
+
const head = made("div", "head");
|
|
205
|
+
const chip = made("span", "chip");
|
|
206
|
+
chip.setAttribute("aria-hidden", "true");
|
|
207
|
+
chip.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 10.9c2.3-3.3.2-7.8-1-8.9 0 3.4-2.2 5.3-3.7 6.7C5.9 10.1 5 12.3 5 14.3 5 18 8.1 21 12 21s7-3 7-6.7c0-1.7-1.2-4.4-2.3-5.6-2.1 3.4-3.3 3.4-4.7 2.2"/></svg>';
|
|
208
|
+
const name = made("input", "name");
|
|
209
|
+
name.type = "text";
|
|
210
|
+
name.placeholder = words.name;
|
|
211
|
+
name.spellcheck = false;
|
|
212
|
+
name.setAttribute("aria-label", words.name);
|
|
213
|
+
const total = made("span", "total");
|
|
214
|
+
head.append(chip, name, total);
|
|
215
|
+
const press = made("button", "check");
|
|
216
|
+
press.type = "button";
|
|
217
|
+
// The streak: its label over its count, the count large and its unit after it.
|
|
218
|
+
const streak = made("p", "streak");
|
|
219
|
+
const label = made("span", "label");
|
|
220
|
+
label.textContent = words.streak;
|
|
221
|
+
const count = made("strong", "count");
|
|
222
|
+
const unit = made("span", "unit");
|
|
223
|
+
streak.append(label, " ", count, " ", unit);
|
|
224
|
+
// The last 30 days as a ring, its count in the middle (shown where the card has room for it).
|
|
225
|
+
const ring = made("div", "ring");
|
|
226
|
+
ring.setAttribute("role", "img");
|
|
227
|
+
ring.innerHTML = '<svg viewBox="0 0 100 100" aria-hidden="true"><circle class="gallery-habit-track" cx="50" cy="50" r="43"/><circle class="gallery-habit-arc" cx="50" cy="50" r="43" pathLength="100" transform="rotate(-90 50 50)"/></svg>';
|
|
228
|
+
const rate = made("span", "rate");
|
|
229
|
+
const rateLabel = made("span", "rate-label");
|
|
230
|
+
rateLabel.textContent = words.rate;
|
|
231
|
+
ring.append(rate, rateLabel);
|
|
232
|
+
const week = made("ol", "week");
|
|
233
|
+
week.setAttribute("aria-label", words.week);
|
|
234
|
+
// Never checked in: this line where the streak stands, no ring.
|
|
235
|
+
const fresh = made("p", "fresh");
|
|
236
|
+
fresh.textContent = words.fresh;
|
|
237
|
+
face.append(head, ring, streak, fresh, press, week);
|
|
238
|
+
host.append(face);
|
|
239
|
+
const named = DocKit.input(name, { signal: context.signal, commit: (value) => rename(context, value) });
|
|
240
|
+
press.addEventListener("click", () => {
|
|
241
|
+
if (context.canEdit()) pressToday(context, dayOf(new Date()));
|
|
242
|
+
}, { signal: context.signal });
|
|
243
|
+
|
|
244
|
+
const draw = (/** @type {string} */ reason = "", /** @type {unknown} */ detail = null) => {
|
|
245
|
+
if (reason === "clock" && detail !== "day") return; // a minute moves no day
|
|
246
|
+
const data = context.readJSON("habit");
|
|
247
|
+
if (data === undefined) return; // no JSON: the instance has failed, and Gallery shows what it holds
|
|
248
|
+
let habit;
|
|
249
|
+
try {
|
|
250
|
+
habit = readHabit(data);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return context.fail(error);
|
|
253
|
+
}
|
|
254
|
+
const today = dayOf(new Date());
|
|
255
|
+
const writes = context.canEdit();
|
|
256
|
+
const done = habit.days.includes(today);
|
|
257
|
+
name.readOnly = !writes;
|
|
258
|
+
named.set(habit.name);
|
|
259
|
+
// Where nothing is written, the button is a line saying how today stands: nothing to press.
|
|
260
|
+
press.disabled = !writes;
|
|
261
|
+
press.classList.toggle("gallery-habit-done", done);
|
|
262
|
+
press.textContent = writes ? (done ? `✓ ${words.checked}` : words.check) : (done ? words.todayDone : words.todayOpen);
|
|
263
|
+
press.title = writes && done ? words.undo : "";
|
|
264
|
+
press.setAttribute("aria-pressed", String(done));
|
|
265
|
+
face.toggleAttribute("data-fresh", habit.days.length === 0);
|
|
266
|
+
const days = streakOf(habit.days, today);
|
|
267
|
+
count.textContent = String(days);
|
|
268
|
+
unit.textContent = words.unit(days);
|
|
269
|
+
total.textContent = habit.days.length > 0 ? words.total(new Set(habit.days).size) : "";
|
|
270
|
+
const lately = rateOf(habit.days, today);
|
|
271
|
+
rate.textContent = String(lately);
|
|
272
|
+
ring.setAttribute("aria-label", words.rateSaid(lately));
|
|
273
|
+
ring.style.setProperty("--gallery-habit-share", String(lately / 30));
|
|
274
|
+
ring.toggleAttribute("data-none", lately === 0);
|
|
275
|
+
week.replaceChildren(...weekOf(habit.days, today).map(({ day, weekday, done: kept }) => {
|
|
276
|
+
const item = made("li", "day");
|
|
277
|
+
item.classList.toggle("gallery-habit-kept", kept);
|
|
278
|
+
item.classList.toggle("gallery-habit-today", day === today);
|
|
279
|
+
const [, month, date] = day.split("-").map(Number);
|
|
280
|
+
item.setAttribute("aria-label", words.day(month, date, kept));
|
|
281
|
+
item.title = words.day(month, date, kept);
|
|
282
|
+
const dot = made("span", "dot");
|
|
283
|
+
const label = made("span", "weekday");
|
|
284
|
+
label.textContent = day === today ? words.today : words.weekdays[weekday];
|
|
285
|
+
item.append(dot, label);
|
|
286
|
+
return item;
|
|
287
|
+
}));
|
|
288
|
+
};
|
|
289
|
+
draw();
|
|
290
|
+
return { update: draw };
|
|
291
|
+
},
|
|
292
|
+
example(host, context) {
|
|
293
|
+
pressToday(context, dayOf(new Date()));
|
|
294
|
+
},
|
|
295
|
+
});
|
|
@@ -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="#f3f3f1"/><rect x="16.5" y="12.5" width="207" height="95" rx="12" fill="#ffffff" stroke="#e7e6e3"/><rect x="30" y="24" width="14" height="14" rx="4.5" fill="#ebf2fa"/><g transform="translate(32 26) scale(.4167)" fill="none" stroke="#2e7cd1" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 10.9c2.3-3.3.2-7.8-1-8.9 0 3.4-2.2 5.3-3.7 6.7C5.9 10.1 5 12.3 5 14.3 5 18 8.1 21 12 21s7-3 7-6.7c0-1.7-1.2-4.4-2.3-5.6-2.1 3.4-3.3 3.4-4.7 2.2"/></g><rect x="50" y="28" width="52" height="6" rx="3" fill="#3b3a37"/><rect x="180" y="29" width="26" height="4" rx="2" fill="#c4c2bd"/><circle cx="51" cy="76" r="19" fill="none" stroke="#ecebe8" stroke-width="4.5"/><circle cx="51" cy="76" r="19" fill="none" stroke="#2e7cd1" stroke-width="4.5" stroke-linecap="round" stroke-dasharray="95.5 119.4" transform="rotate(-90 51 76)"/><text x="51" y="79" text-anchor="middle" font-family="Segoe UI, Helvetica, Arial, sans-serif" font-size="11" font-weight="600" fill="#3b3a37">24</text><rect x="45" y="84" width="12" height="2.5" rx="1.25" fill="#c4c2bd"/><rect x="84" y="47" width="24" height="4" rx="2" fill="#c4c2bd"/><text x="83" y="69" font-family="Segoe UI, Helvetica, Arial, sans-serif" font-size="18" font-weight="600" letter-spacing="-.5" fill="#3b3a37">6</text><rect x="96" y="63" width="9" height="4" rx="2" fill="#c4c2bd"/><rect x="172" y="51" width="38" height="15" rx="5" fill="#ebf2fa"/><rect x="183" y="56" width="16" height="5" rx="2.5" fill="#2e7cd1"/><circle cx="89" cy="86" r="5.5" fill="#2e7cd1"/><path d="M86.44 86.16l1.76 1.76 3.52-3.68" fill="none" stroke="#fff" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/><rect x="86.5" y="95" width="5" height="2.5" rx="1.25" fill="#c4c2bd"/><circle cx="108" cy="86" r="5.5" fill="#ecebe8"/><rect x="105.5" y="95" width="5" height="2.5" rx="1.25" fill="#c4c2bd"/><circle cx="127" cy="86" r="5.5" fill="#2e7cd1"/><path d="M124.44 86.16l1.76 1.76 3.52-3.68" fill="none" stroke="#fff" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/><rect x="124.5" y="95" width="5" height="2.5" rx="1.25" fill="#c4c2bd"/><circle cx="146" cy="86" r="5.5" fill="#2e7cd1"/><path d="M143.44 86.16l1.76 1.76 3.52-3.68" fill="none" stroke="#fff" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/><rect x="143.5" y="95" width="5" height="2.5" rx="1.25" fill="#c4c2bd"/><circle cx="165" cy="86" r="5.5" fill="#2e7cd1"/><path d="M162.44 86.16l1.76 1.76 3.52-3.68" fill="none" stroke="#fff" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/><rect x="162.5" y="95" width="5" height="2.5" rx="1.25" fill="#c4c2bd"/><circle cx="184" cy="86" r="5.5" fill="#2e7cd1"/><path d="M181.44 86.16l1.76 1.76 3.52-3.68" fill="none" stroke="#fff" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/><rect x="181.5" y="95" width="5" height="2.5" rx="1.25" fill="#c4c2bd"/><circle cx="203" cy="86" r="4.9" fill="none" stroke="#2e7cd1" stroke-width="1.2"/><rect x="200.5" y="95" width="5" height="2.5" rx="1.25" fill="#2e7cd1"/></svg>
|
|
@@ -0,0 +1,246 @@
|
|
|
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 face fills its card on a custom page (the board draws the frame), in
|
|
3
|
+
the kit's card tokens, laid out as a grid: its head (the chip, the name, the days in all as its quiet
|
|
4
|
+
meta at the right, its end clear of the card's grip); the streak, its label small and muted over its
|
|
5
|
+
count, large, its unit after it, with the button at its right; the last seven days at the bottom, each
|
|
6
|
+
a dot over its weekday (today's 今), a day done filled with the accent and ticked, an open one the
|
|
7
|
+
track's gray, today ringed in the accent. Where the card has the room (past 300px wide and 130px high:
|
|
8
|
+
a 4 x 2 card from a window 1440px wide) the last 30 days as a ring at the left of the streak and the week, the
|
|
9
|
+
days done of them in its middle. The button is a primary one, pale blue with no line ("打卡"); once
|
|
10
|
+
today is done a pale green "✓ 已打卡" that a press takes back; where nothing is written, a plain muted
|
|
11
|
+
line. Low (under 124px), the streak's label goes; a narrow card (2 x 2) leaves the week out and puts
|
|
12
|
+
the button under the streak, as wide as its words. Never checked in (data-fresh): no streak and no
|
|
13
|
+
ring, a muted line where the streak stands. Light and dark. */
|
|
14
|
+
[data-doc-component="gallery/habit"] {
|
|
15
|
+
container-type: size;
|
|
16
|
+
height: 10em;
|
|
17
|
+
> .gallery-habit-face {
|
|
18
|
+
display: grid;
|
|
19
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
20
|
+
grid-template-rows: auto 1fr auto;
|
|
21
|
+
grid-template-areas: "head head" "streak press" "week week";
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: 8px 12px;
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
height: 100%;
|
|
26
|
+
min-height: 0;
|
|
27
|
+
color: var(--gallery-text);
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
line-height: 1.4;
|
|
30
|
+
}
|
|
31
|
+
> .gallery-habit-face > .gallery-habit-head {
|
|
32
|
+
grid-area: head;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 8px;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
padding-inline-end: var(--gallery-grip-room, 0px);
|
|
38
|
+
}
|
|
39
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-chip {
|
|
40
|
+
flex: none;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
width: var(--gallery-chip-size);
|
|
45
|
+
height: var(--gallery-chip-size);
|
|
46
|
+
border-radius: var(--gallery-chip-radius);
|
|
47
|
+
background: var(--gallery-accent-soft);
|
|
48
|
+
color: var(--gallery-accent);
|
|
49
|
+
}
|
|
50
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-chip > svg { width: var(--gallery-chip-icon); height: var(--gallery-chip-icon); }
|
|
51
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-name {
|
|
52
|
+
flex: 1 1 auto;
|
|
53
|
+
min-width: 0;
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
width: 0;
|
|
56
|
+
margin: 0 0 0 -6px;
|
|
57
|
+
padding: 3px 6px;
|
|
58
|
+
border: 0;
|
|
59
|
+
border-radius: 6px;
|
|
60
|
+
outline: 0;
|
|
61
|
+
background: transparent;
|
|
62
|
+
font: inherit;
|
|
63
|
+
font-size: var(--gallery-title-size);
|
|
64
|
+
line-height: var(--gallery-title-line);
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
color: var(--gallery-text-strong);
|
|
67
|
+
text-overflow: ellipsis;
|
|
68
|
+
}
|
|
69
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-name::placeholder { color: var(--doc-muted); font-weight: 500; opacity: .8; }
|
|
70
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-name:not(:read-only):hover,
|
|
71
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-name:not(:read-only):focus { background: var(--doc-hover); }
|
|
72
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-name:read-only { cursor: default; }
|
|
73
|
+
> .gallery-habit-face > .gallery-habit-head > .gallery-habit-total {
|
|
74
|
+
flex: none;
|
|
75
|
+
margin-inline-end: calc(-1 * var(--gallery-grip-room, 0px)); opacity: calc(1 - var(--gallery-grip-shown, 0)); transition: opacity .12s ease-out;
|
|
76
|
+
font-size: var(--gallery-meta-size);
|
|
77
|
+
line-height: var(--gallery-title-line);
|
|
78
|
+
color: var(--doc-muted);
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
font-variant-numeric: tabular-nums;
|
|
81
|
+
}
|
|
82
|
+
> .gallery-habit-face > .gallery-habit-check {
|
|
83
|
+
grid-area: press;
|
|
84
|
+
justify-self: end;
|
|
85
|
+
align-self: center;
|
|
86
|
+
padding: 5px 14px;
|
|
87
|
+
border: 0;
|
|
88
|
+
border-radius: 8px;
|
|
89
|
+
background: var(--gallery-accent-soft);
|
|
90
|
+
font: inherit;
|
|
91
|
+
font-size: 13px;
|
|
92
|
+
line-height: 18px;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
color: var(--gallery-accent);
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
}
|
|
98
|
+
> .gallery-habit-face > .gallery-habit-check:hover { background: color-mix(in srgb, var(--gallery-accent) 15%, transparent); }
|
|
99
|
+
> .gallery-habit-face > .gallery-habit-check:active { background: color-mix(in srgb, var(--gallery-accent) 21%, transparent); }
|
|
100
|
+
> .gallery-habit-face > .gallery-habit-check.gallery-habit-done { background: var(--gallery-good-soft); color: var(--gallery-good); }
|
|
101
|
+
> .gallery-habit-face > .gallery-habit-check.gallery-habit-done:hover { background: color-mix(in srgb, var(--gallery-good) 16%, transparent); }
|
|
102
|
+
/* Nothing to press (an export, a page that takes no edits): a line saying how today stands. */
|
|
103
|
+
> .gallery-habit-face > .gallery-habit-check:disabled,
|
|
104
|
+
> .gallery-habit-face > .gallery-habit-check.gallery-habit-done:disabled {
|
|
105
|
+
padding-inline: 0;
|
|
106
|
+
background: transparent;
|
|
107
|
+
font-weight: 400;
|
|
108
|
+
color: var(--doc-muted);
|
|
109
|
+
cursor: default;
|
|
110
|
+
}
|
|
111
|
+
/* The streak: its label over its count, the count large, its unit small and muted after it. */
|
|
112
|
+
> .gallery-habit-face > .gallery-habit-streak {
|
|
113
|
+
grid-area: streak;
|
|
114
|
+
align-self: center;
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-wrap: wrap;
|
|
117
|
+
align-items: baseline;
|
|
118
|
+
min-width: 0;
|
|
119
|
+
margin: 0;
|
|
120
|
+
}
|
|
121
|
+
> .gallery-habit-face > .gallery-habit-streak > .gallery-habit-label {
|
|
122
|
+
flex: 0 0 100%;
|
|
123
|
+
margin-bottom: 2px;
|
|
124
|
+
font-size: var(--gallery-meta-size);
|
|
125
|
+
line-height: 16px;
|
|
126
|
+
color: var(--doc-muted);
|
|
127
|
+
}
|
|
128
|
+
> .gallery-habit-face > .gallery-habit-streak > .gallery-habit-count {
|
|
129
|
+
font-size: clamp(28px, 100cqh - 100px, var(--gallery-hero-size)); /* the hero, a little less on a card too low for it beside the week */
|
|
130
|
+
line-height: 1;
|
|
131
|
+
font-weight: var(--gallery-numeral-weight);
|
|
132
|
+
letter-spacing: var(--gallery-numeral-tracking);
|
|
133
|
+
color: var(--gallery-text-strong);
|
|
134
|
+
font-variant-numeric: tabular-nums;
|
|
135
|
+
}
|
|
136
|
+
> .gallery-habit-face > .gallery-habit-streak > .gallery-habit-unit {
|
|
137
|
+
margin-inline-start: var(--gallery-unit-gap);
|
|
138
|
+
font-size: max(12px, calc(var(--gallery-hero-size) * .4)); /* the hero's unit (--gallery-unit-size of it) */
|
|
139
|
+
font-weight: 500;
|
|
140
|
+
color: var(--doc-muted);
|
|
141
|
+
}
|
|
142
|
+
/* Never checked in: a muted line where the streak stands. */
|
|
143
|
+
> .gallery-habit-face > .gallery-habit-fresh { display: none; }
|
|
144
|
+
> .gallery-habit-face[data-fresh] > .gallery-habit-streak { display: none; }
|
|
145
|
+
> .gallery-habit-face[data-fresh] > .gallery-habit-fresh {
|
|
146
|
+
grid-area: streak;
|
|
147
|
+
align-self: center;
|
|
148
|
+
display: block;
|
|
149
|
+
min-width: 0;
|
|
150
|
+
margin: 0;
|
|
151
|
+
font-size: 13px;
|
|
152
|
+
line-height: 1.45;
|
|
153
|
+
color: var(--doc-muted);
|
|
154
|
+
}
|
|
155
|
+
/* The last 30 days: a ring of the accent on the track, the days done in its middle over its label. */
|
|
156
|
+
> .gallery-habit-face > .gallery-habit-ring {
|
|
157
|
+
grid-area: ring;
|
|
158
|
+
display: none;
|
|
159
|
+
position: relative;
|
|
160
|
+
width: clamp(76px, calc(100cqh - 52px), 128px);
|
|
161
|
+
aspect-ratio: 1;
|
|
162
|
+
align-self: center;
|
|
163
|
+
}
|
|
164
|
+
> .gallery-habit-face > .gallery-habit-ring > svg { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
165
|
+
> .gallery-habit-face > .gallery-habit-ring > svg > circle { fill: none; stroke-width: 9; }
|
|
166
|
+
> .gallery-habit-face > .gallery-habit-ring > svg > .gallery-habit-track { stroke: var(--gallery-track); }
|
|
167
|
+
> .gallery-habit-face > .gallery-habit-ring > svg > .gallery-habit-arc {
|
|
168
|
+
stroke: var(--gallery-accent);
|
|
169
|
+
stroke-linecap: round;
|
|
170
|
+
stroke-dasharray: calc(var(--gallery-habit-share, 0) * 100) 100;
|
|
171
|
+
}
|
|
172
|
+
> .gallery-habit-face > .gallery-habit-ring[data-none] > svg > .gallery-habit-arc { display: none; }
|
|
173
|
+
> .gallery-habit-face > .gallery-habit-ring > .gallery-habit-rate {
|
|
174
|
+
position: relative;
|
|
175
|
+
font-size: clamp(20px, 7cqi, 28px);
|
|
176
|
+
line-height: 1;
|
|
177
|
+
font-weight: var(--gallery-numeral-weight);
|
|
178
|
+
letter-spacing: var(--gallery-numeral-tracking);
|
|
179
|
+
font-variant-numeric: tabular-nums;
|
|
180
|
+
text-align: center;
|
|
181
|
+
}
|
|
182
|
+
> .gallery-habit-face > .gallery-habit-ring > .gallery-habit-rate-label {
|
|
183
|
+
position: relative;
|
|
184
|
+
margin-top: 4px;
|
|
185
|
+
font-size: 11px;
|
|
186
|
+
line-height: 14px;
|
|
187
|
+
color: var(--doc-muted);
|
|
188
|
+
text-align: center;
|
|
189
|
+
}
|
|
190
|
+
> .gallery-habit-face > .gallery-habit-week {
|
|
191
|
+
grid-area: week;
|
|
192
|
+
display: grid;
|
|
193
|
+
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
194
|
+
gap: 4px;
|
|
195
|
+
margin: 0;
|
|
196
|
+
padding: 0;
|
|
197
|
+
list-style: none;
|
|
198
|
+
}
|
|
199
|
+
> .gallery-habit-face > .gallery-habit-week > .gallery-habit-day {
|
|
200
|
+
display: flex;
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 5px;
|
|
204
|
+
font-size: 11px;
|
|
205
|
+
line-height: 1;
|
|
206
|
+
color: var(--doc-muted);
|
|
207
|
+
}
|
|
208
|
+
> .gallery-habit-face > .gallery-habit-week > .gallery-habit-day > .gallery-habit-dot {
|
|
209
|
+
box-sizing: border-box;
|
|
210
|
+
width: clamp(18px, 6.4cqi, 26px);
|
|
211
|
+
aspect-ratio: 1;
|
|
212
|
+
border-radius: 50%;
|
|
213
|
+
background: var(--gallery-track);
|
|
214
|
+
}
|
|
215
|
+
> .gallery-habit-face > .gallery-habit-week > .gallery-habit-kept > .gallery-habit-dot {
|
|
216
|
+
background: var(--gallery-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 8.3l2.2 2.2 4.8-4.9' fill='none' stroke='white' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 72% no-repeat;
|
|
217
|
+
}
|
|
218
|
+
> .gallery-habit-face > .gallery-habit-week > .gallery-habit-today { color: var(--gallery-accent); font-weight: 600; }
|
|
219
|
+
> .gallery-habit-face > .gallery-habit-week > .gallery-habit-today:not(.gallery-habit-kept) > .gallery-habit-dot { background: transparent; box-shadow: inset 0 0 0 1.5px var(--gallery-accent); }
|
|
220
|
+
}
|
|
221
|
+
/* Room for the ring: at the left of the streak and the week, the dots no wider than they need. */
|
|
222
|
+
@container (min-width: 300px) and (min-height: 130px) {
|
|
223
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face {
|
|
224
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
225
|
+
grid-template-areas: "head head head" "ring streak press" "ring week week";
|
|
226
|
+
column-gap: 20px;
|
|
227
|
+
}
|
|
228
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face > .gallery-habit-ring { display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
|
229
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face > .gallery-habit-week { align-self: end; }
|
|
230
|
+
/* Never checked in: no ring, the line and the week from the card's left. */
|
|
231
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face[data-fresh] { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "head head" "streak press" "week week"; }
|
|
232
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face[data-fresh] > .gallery-habit-ring { display: none; }
|
|
233
|
+
}
|
|
234
|
+
/* Low: the count alone, no label over it. */
|
|
235
|
+
@container (max-height: 124px) {
|
|
236
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face > .gallery-habit-streak > .gallery-habit-label { display: none; }
|
|
237
|
+
}
|
|
238
|
+
/* A narrow card: the head, the streak, the button under it at its start, as wide as its words, the two in the middle of the room under the head; no week, no days in all. */
|
|
239
|
+
@container (max-width: 220px) {
|
|
240
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr auto auto 1fr; grid-template-areas: "head" "." "streak" "press" "."; }
|
|
241
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face > .gallery-habit-check { justify-self: start; }
|
|
242
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face > .gallery-habit-week,
|
|
243
|
+
[data-doc-component="gallery/habit"] > .gallery-habit-face > .gallery-habit-head > .gallery-habit-total { display: none; }
|
|
244
|
+
}
|
|
245
|
+
/* On a custom page the face is the card's: as high as what the card holds inside its padding. */
|
|
246
|
+
.gallery-card > [data-doc-component="gallery/habit"] { height: 100%; }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 习惯打卡 (gallery/habit)
|
|
2
|
+
|
|
3
|
+
A card for a custom page (its places are `page`): one habit, checked in once a day. It shows the habit's name, how many days in a row it was done (up to today, or up to yesterday while today is not checked yet), how many days in all, and the last seven days as dots. Use it for something the user means to do every day. For a habit counted otherwise (glasses of water in a day, minutes, a goal a week), start a block of your own from it: `gallery block new <scope>/<name> --recipe habit` copies its files, which are written to be read and changed.
|
|
4
|
+
|
|
5
|
+
## How to write it
|
|
6
|
+
|
|
7
|
+
Put it on a custom page as a card of its board, `<section class="gallery-card" data-card-id="…" data-x data-y data-w data-h>`, holding one instance, after `gallery block add habit`, the instance as template.html has it: its data part `habit`, a `<script type="application/json">`, holding `{"name":"…","days":[]}` (on one line and compact, as the block writes it back):
|
|
8
|
+
|
|
9
|
+
- `name`: the habit, a few words (每天读书半小时); `""` shows 给习惯起个名字, and the user names it on the card.
|
|
10
|
+
- `days`: the days it was done, each `YYYY-MM-DD` by the user's clock, in order, each once. Leave the days of a card that is there alone: they are the user's record.
|
|
11
|
+
|
|
12
|
+
Sizes: 4 × 2 (the default: the name, the streak and the button, the week, and where the window is wide enough the last 30 days as a ring) and 2 × 2 (the name, the streak, the button under it).
|
|
13
|
+
|
|
14
|
+
## What the user does
|
|
15
|
+
|
|
16
|
+
- presses 打卡: today goes in `days`; pressed again (✓ 已打卡), today comes out. Each press is one write, one step of the page's undo.
|
|
17
|
+
- clicks the name and types another: written as they press Enter or leave the field (never while a Chinese input method composes); Esc puts back the name there was.
|
|
18
|
+
|
|
19
|
+
## How it looks
|
|
20
|
+
|
|
21
|
+
The name beside a flame's chip on the top line, the days in all at its end; the streak under it, 连续打卡 over its count, large, with the button at its right (打卡, pale blue; once done a pale green ✓ 已打卡 that a press takes back), the week at the bottom, a day done a dot filled with the accent and ticked, today ringed and named 今; on a card with the room for it, the last 30 days as a ring at the left, the days done of them in its middle; in the kit's card tokens (as every Gallery card: a head of a tinted chip and a title, its end clear of the card's grip), light and dark. It moves to the next day by itself at midnight (it hears the kit's clock). A habit never checked in shows neither the streak nor the ring but a muted line, 还没打过卡,今天开始第 1 天, until its first check-in. Where nothing can be written (an export) the button is a line saying whether today is done. The script runs with the rights of the page's own scripts, as any block's does.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "gallery/heatmap",
|
|
3
|
+
"apiVersion": 1,
|
|
4
|
+
"version": "1.3.0",
|
|
5
|
+
"name": { "zh": "编辑热力图", "en": "Editing heatmap" },
|
|
6
|
+
"description": { "zh": "过去一年每天编辑了几次或写了多少字,像 GitHub 的贡献图", "en": "How often, or how many words, the workspace was edited each day of the past year, like GitHub's contribution graph" },
|
|
7
|
+
"words": "bianjireilitu reilitu heatmap activity contributions calendar 热力图 编辑 贡献 日历 字数 每天",
|
|
8
|
+
"parts": {
|
|
9
|
+
"settings": { "at": ":scope > script", "edit": "data" }
|
|
10
|
+
},
|
|
11
|
+
"runtime": { "generated": [":scope > .gallery-heatmap-face"] },
|
|
12
|
+
"places": ["page"],
|
|
13
|
+
"card": { "sizes": [[8, 3], [12, 3], [4, 3]], "resize": true },
|
|
14
|
+
"settings": [
|
|
15
|
+
{ "key": "words", "label": { "zh": "按字数显示", "en": "Show words, not edits" }, "type": "switch", "default": false }
|
|
16
|
+
],
|
|
17
|
+
"needs": ["workspace"]
|
|
18
|
+
}
|