@heycys/gallery 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/browser/editor/components/align.mjs +58 -0
- package/browser/editor/components/code/index.mjs +1 -1
- package/browser/editor/components/embed/page.mjs +1 -1
- package/browser/editor/components/file/page.mjs +4 -4
- package/browser/editor/components/heading/index.mjs +7 -6
- package/browser/editor/components/icon/page.mjs +6 -4
- package/browser/editor/components/icons.mjs +5 -2
- package/browser/editor/components/image/index.mjs +2 -0
- package/browser/editor/components/image/page.mjs +172 -90
- package/browser/editor/components/installed/index.mjs +6 -2
- package/browser/editor/components/installed/page.mjs +80 -13
- package/browser/editor/components/media/icons.mjs +13 -8
- package/browser/editor/components/media/page.mjs +5 -3
- package/browser/editor/components/media/toolbar.mjs +12 -3
- package/browser/editor/components/paragraph/index.mjs +4 -3
- package/browser/editor/components/registry.mjs +17 -5
- package/browser/editor/components/table/index.mjs +4 -2
- package/browser/editor/page/after/index.mjs +288 -0
- package/browser/editor/page/align.mjs +91 -0
- package/browser/editor/page/beside.mjs +5 -6
- package/browser/editor/page/block-menu/index.mjs +6 -3
- package/browser/editor/page/blocks.mjs +3 -1
- package/browser/editor/page/board/hint.mjs +64 -27
- package/browser/editor/page/board/index.mjs +160 -71
- package/browser/editor/page/board/motion.mjs +177 -0
- package/browser/editor/page/board/settings.mjs +4 -3
- package/browser/editor/page/clipboard/import/read.mjs +20 -2
- package/browser/editor/page/clipboard/index.mjs +10 -3
- package/browser/editor/page/clipboard/own/blocks.mjs +3 -4
- package/browser/editor/page/clipboard/own/describe.mjs +6 -2
- package/browser/editor/page/commit.mjs +2 -2
- package/browser/editor/page/core.mjs +3 -1
- package/browser/editor/page/custom-page.mjs +15 -11
- package/browser/editor/page/editor.mjs +31 -14
- package/browser/editor/page/history.mjs +19 -4
- package/browser/editor/page/insert/index.mjs +20 -22
- package/browser/editor/page/menu.mjs +15 -7
- package/browser/editor/page/page-objects.mjs +3 -7
- package/browser/editor/page/present-links.mjs +5 -4
- package/browser/editor/page/present-shell.mjs +63 -0
- package/browser/editor/page/selection/index.mjs +10 -2
- package/browser/editor/page/table/binding.mjs +87 -0
- package/browser/editor/page/table/icons.mjs +0 -2
- package/browser/editor/page/table/index.mjs +10 -14
- package/browser/editor/page/table/views.mjs +5 -52
- package/browser/editor/page/table/widths.mjs +82 -10
- package/browser/editor/page/typing/index.mjs +44 -12
- package/browser/editor/page/typing/leave.mjs +19 -5
- package/browser/shared/block-packs.mjs +23 -8
- package/browser/shared/component-catalog.mjs +15 -8
- package/browser/shared/component-source.mjs +3 -1
- package/browser/shared/components/heading.mjs +2 -1
- package/browser/shared/components/installed.mjs +1 -1
- package/browser/shared/components/page.mjs +2 -0
- package/browser/shared/components/paragraph.mjs +8 -1
- package/browser/shared/components/table.mjs +1 -0
- package/browser/shared/document-copy.mjs +43 -0
- package/browser/shared/drafts.mjs +10 -3
- package/browser/shared/emoji/icon-value.mjs +2 -2
- package/browser/shared/emoji/line-icons.mjs +37 -0
- package/browser/shared/extensions.mjs +83 -7
- package/browser/shared/http.mjs +9 -0
- package/browser/shared/i18n/en.mjs +50 -17
- package/browser/shared/outline-shape.mjs +6 -0
- package/browser/shared/outline.mjs +165 -12
- package/browser/shared/pack-manifest.mjs +12 -1
- package/browser/shared/pages.mjs +169 -57
- package/browser/shared/pinyin.mjs +92 -0
- package/browser/shared/present-step.mjs +17 -0
- package/browser/shared/sidebar-head.mjs +70 -0
- package/browser/shared/web-links.mjs +5 -0
- package/browser/shared/workspace-columns.mjs +11 -7
- package/dist/{chunk-N7AZZLGT.mjs → chunk-2GKGEVMR.mjs} +29 -24
- package/dist/chunk-2OHANJ6Q.mjs +2 -0
- package/dist/{chunk-HYMEEXKN.mjs → chunk-2RKIHJI2.mjs} +1 -1
- package/dist/{chunk-742D2WWJ.mjs → chunk-36FIRJIF.mjs} +2 -2
- package/dist/{chunk-BPL64HGA.mjs → chunk-3A5I2GJK.mjs} +1 -1
- package/dist/{chunk-RFDZXFJF.mjs → chunk-3OFXMN6N.mjs} +1 -1
- package/dist/chunk-46ACXOU5.mjs +2 -0
- package/dist/{chunk-PUOQ65NC.mjs → chunk-4AMTKWDO.mjs} +1 -1
- package/dist/{chunk-VTKNEYT3.mjs → chunk-4DNHHJSL.mjs} +1 -1
- package/dist/chunk-5DPABPBI.mjs +2 -0
- package/dist/chunk-5IIH335D.mjs +3 -0
- package/dist/chunk-5L26JUYA.mjs +2 -0
- package/dist/{chunk-3NJACR4W.mjs → chunk-5MEXZ2UF.mjs} +1 -1
- package/dist/{chunk-ZHEHPMBK.mjs → chunk-6MPKCH3P.mjs} +1 -1
- package/dist/{chunk-C564NU2H.mjs → chunk-74RPP2M2.mjs} +1 -1
- package/dist/chunk-7M2BQQGZ.mjs +2 -0
- package/dist/{chunk-VXACTLO3.mjs → chunk-7M7UPWU6.mjs} +1 -1
- package/dist/{chunk-ZB4H7ZN4.mjs → chunk-7N2LPG33.mjs} +1 -1
- package/dist/{chunk-UBBRTJ2R.mjs → chunk-7XDYK3UC.mjs} +1 -1
- package/dist/chunk-AEMVZYZQ.mjs +166 -0
- package/dist/{chunk-POQSE4C2.mjs → chunk-AFGUR7SY.mjs} +1 -1
- package/dist/{chunk-QKXMPUXC.mjs → chunk-AMDGVDNV.mjs} +1 -1
- package/dist/{chunk-KTKHRC6H.mjs → chunk-ASAXY2DJ.mjs} +1 -1
- package/dist/{chunk-NVRNLK25.mjs → chunk-BYSF5UMM.mjs} +2 -2
- package/dist/{chunk-U3SSPL4K.mjs → chunk-CCSGFCLZ.mjs} +1 -1
- package/dist/{chunk-NWFBE2YA.mjs → chunk-CG3UHDKH.mjs} +1 -1
- package/dist/chunk-CPS4SRPF.mjs +5 -0
- package/dist/{chunk-B4P2CM2J.mjs → chunk-CZ5KJSGK.mjs} +1 -1
- package/dist/chunk-DAMVDXR3.mjs +2 -0
- package/dist/{chunk-NG375ILQ.mjs → chunk-DH6GMDNN.mjs} +1 -1
- package/dist/chunk-DHB7UZ7F.mjs +3 -0
- package/dist/{chunk-MHW7ZKOU.mjs → chunk-DKR7LFIY.mjs} +1 -1
- package/dist/chunk-DLFZJJEE.mjs +2 -0
- package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
- package/dist/{chunk-6VTU6KEO.mjs → chunk-FCK6NXON.mjs} +1 -1
- package/dist/{chunk-ZWPMCK7P.mjs → chunk-FX42XU6J.mjs} +1 -1
- package/dist/chunk-FXQBADMN.mjs +5 -0
- package/dist/{chunk-W3BGBJQW.mjs → chunk-G4YP7GVC.mjs} +1 -1
- package/dist/{chunk-V6XZ4VIR.mjs → chunk-GWOYMENX.mjs} +1 -1
- package/dist/chunk-HATA2W7X.mjs +2 -0
- package/dist/{chunk-YI43RG4P.mjs → chunk-HBAUKKQH.mjs} +1 -1
- package/dist/{chunk-MAFIGVXY.mjs → chunk-HM4E43VM.mjs} +1 -1
- package/dist/{chunk-CRJEG7MT.mjs → chunk-JI6T3A4Y.mjs} +1 -1
- package/dist/{chunk-NFMCP2HI.mjs → chunk-JJNRIFND.mjs} +2 -2
- package/dist/chunk-JL6R4FG4.mjs +4 -0
- package/dist/{chunk-UMHUSOPJ.mjs → chunk-JXLSHVJ2.mjs} +1 -1
- package/dist/{chunk-FJYN7RTH.mjs → chunk-KFZBYDIS.mjs} +1 -1
- package/dist/{chunk-ZTHICSSF.mjs → chunk-KH24LWAT.mjs} +1 -1
- package/dist/{chunk-RKPWLGZ3.mjs → chunk-KUILYI7H.mjs} +1 -1
- package/dist/{chunk-RMECZA2K.mjs → chunk-L7W624FD.mjs} +1 -1
- package/dist/chunk-LE44MD3O.mjs +4 -0
- package/dist/{chunk-4K5KC6V2.mjs → chunk-LV24RY2E.mjs} +1 -1
- package/dist/{chunk-43B75J5T.mjs → chunk-MKTX5PGV.mjs} +1 -1
- package/dist/{chunk-LKX55H76.mjs → chunk-NAGMRVW4.mjs} +1 -1
- package/dist/{chunk-D4CKRD3V.mjs → chunk-NFABH3QW.mjs} +2 -2
- package/dist/{chunk-PVER7LSC.mjs → chunk-NIW7F7LU.mjs} +2 -2
- package/dist/{chunk-PAREWQGB.mjs → chunk-NWRZXELO.mjs} +1 -1
- package/dist/{chunk-OJKMINIU.mjs → chunk-OU3S466R.mjs} +15 -15
- package/dist/{chunk-5ZJA7CXT.mjs → chunk-PAFSFVYB.mjs} +1 -1
- package/dist/chunk-QCEATQC4.mjs +2 -0
- package/dist/{chunk-Y34NR5JL.mjs → chunk-QJ3TFJLQ.mjs} +1 -1
- package/dist/{chunk-IA4PSIMD.mjs → chunk-QK3VISCA.mjs} +1 -1
- package/dist/{chunk-ZUNVAXLH.mjs → chunk-QXVNUASA.mjs} +1 -1
- package/dist/{chunk-GRAXKKG4.mjs → chunk-QZ35UAB4.mjs} +1 -1
- package/dist/{chunk-UGWYKHBL.mjs → chunk-S2Y7VI4H.mjs} +2 -2
- package/dist/{chunk-OW5NWPOJ.mjs → chunk-SAHS56QM.mjs} +1 -1
- package/dist/{chunk-QOUEFUCH.mjs → chunk-SOD23RGL.mjs} +1 -1
- package/dist/chunk-TDWJKOLN.mjs +2 -0
- package/dist/{chunk-V6RIYQZZ.mjs → chunk-TOE6DPLH.mjs} +1 -1
- package/dist/chunk-UGO5N3EI.mjs +15 -0
- package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
- package/dist/{chunk-NWR2LT5B.mjs → chunk-V5JRSGZF.mjs} +1 -1
- package/dist/{chunk-BZ5RAF7L.mjs → chunk-VA3KAQZD.mjs} +1 -1
- package/dist/chunk-VG5JEIUQ.mjs +12 -0
- package/dist/{chunk-UNOYVIYR.mjs → chunk-VGTMLQWZ.mjs} +1 -1
- package/dist/chunk-VKSRM7EY.mjs +22 -0
- package/dist/{chunk-YQGT4AB2.mjs → chunk-VNUWREXU.mjs} +1 -1
- package/dist/{chunk-AGDILNWB.mjs → chunk-VPVOAJ7X.mjs} +1 -1
- package/dist/{chunk-HLYV7RIH.mjs → chunk-WCWYNFDF.mjs} +1 -1
- package/dist/chunk-WTEGUPFG.mjs +16 -0
- package/dist/{chunk-N76HWBA3.mjs → chunk-XKOHV4H6.mjs} +1 -1
- package/dist/{chunk-HMYTM3GT.mjs → chunk-XMKHCUI6.mjs} +1 -1
- package/dist/{chunk-OUUP3WKT.mjs → chunk-XQRUG4G3.mjs} +1 -1
- package/dist/{chunk-32ZUEFZ7.mjs → chunk-YBB5LWZK.mjs} +1 -1
- package/dist/{chunk-SX4LJYT6.mjs → chunk-YFJU3O7G.mjs} +1 -1
- package/dist/chunk-YPPJVDQI.mjs +2 -0
- package/dist/chunk-ZANMKAWS.mjs +2 -0
- package/dist/{chunk-6QBHU4DF.mjs → chunk-ZCP7BTOZ.mjs} +1 -1
- package/dist/{chunk-ERLAIBOA.mjs → chunk-ZEY2RLBT.mjs} +2 -2
- package/dist/{chunk-SQ6Y2ZMA.mjs → chunk-ZO7SFB2E.mjs} +2 -2
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +5 -5
- package/kit/code.js +15 -15
- package/kit/math.js +1 -1
- package/kit/report.css +123 -44
- package/kit/runtime.js +45 -7
- package/kit/tables.js +2 -0
- package/kit/ui.js +2 -0
- package/package.json +1 -1
- package/packs/gallery/chart-view/kit.js +3 -3
- package/packs/gallery/chart-view/writing.md +1 -1
- package/packs/gallery/countdown/component.json +1 -1
- package/packs/gallery/countdown/kit.js +89 -29
- package/packs/gallery/countdown/preview.svg +1 -1
- package/packs/gallery/countdown/style.css +88 -100
- package/packs/gallery/countdown/writing.md +2 -2
- package/packs/gallery/habit/component.json +3 -3
- package/packs/gallery/habit/kit.js +114 -75
- package/packs/gallery/habit/preview.svg +1 -1
- package/packs/gallery/habit/style.css +133 -205
- package/packs/gallery/habit/writing.md +2 -2
- package/packs/gallery/heatmap/component.json +2 -2
- package/packs/gallery/heatmap/kit.js +335 -173
- package/packs/gallery/heatmap/preview.svg +1 -1
- package/packs/gallery/heatmap/style.css +158 -170
- package/packs/gallery/heatmap/writing.md +2 -2
- package/packs/gallery/kanban/kit.js +3 -3
- package/packs/gallery/kanban/writing.md +1 -1
- package/packs/gallery/metric/kit.js +3 -3
- package/packs/gallery/metric/writing.md +1 -1
- package/packs/gallery/note/component.json +3 -3
- package/packs/gallery/note/kit.js +40 -13
- package/packs/gallery/note/preview.svg +1 -1
- package/packs/gallery/note/style.css +51 -30
- package/packs/gallery/note/writing.md +2 -2
- package/packs/gallery/recent/component.json +2 -2
- package/packs/gallery/recent/kit.js +141 -86
- package/packs/gallery/recent/preview.svg +1 -1
- package/packs/gallery/recent/style.css +53 -126
- package/packs/gallery/recent/writing.md +2 -2
- package/packs/gallery/static/component.json +2 -2
- package/packs/gallery/static/preview.svg +1 -1
- package/packs/gallery/static/style.css +99 -66
- package/packs/gallery/static/writing.md +2 -2
- package/packs/gallery/stats/component.json +3 -3
- package/packs/gallery/stats/kit.js +253 -72
- package/packs/gallery/stats/preview.svg +1 -1
- package/packs/gallery/stats/style.css +64 -142
- package/packs/gallery/stats/writing.md +2 -2
- package/packs/gallery/today/component.json +2 -2
- package/packs/gallery/today/kit.js +62 -27
- package/packs/gallery/today/preview.svg +1 -1
- package/packs/gallery/today/style.css +101 -72
- package/packs/gallery/today/writing.md +2 -2
- package/packs/gallery/todos/component.json +1 -1
- package/packs/gallery/todos/kit.js +218 -71
- package/packs/gallery/todos/preview.svg +1 -1
- package/packs/gallery/todos/style.css +170 -223
- package/packs/gallery/todos/writing.md +1 -1
- package/packs/gallery/weather/component.json +2 -2
- package/packs/gallery/weather/kit.js +165 -90
- package/packs/gallery/weather/preview.svg +1 -1
- package/packs/gallery/weather/style.css +100 -218
- package/packs/gallery/weather/writing.md +1 -1
- package/packs/gallery-soft/countdown/component.json +18 -0
- package/packs/gallery-soft/countdown/example.html +3 -0
- package/packs/gallery-soft/countdown/kit.js +206 -0
- package/packs/gallery-soft/countdown/preview.svg +1 -0
- package/packs/gallery-soft/countdown/style.css +211 -0
- package/packs/gallery-soft/countdown/template.html +3 -0
- package/packs/gallery-soft/countdown/writing.md +24 -0
- package/packs/gallery-soft/habit/component.json +14 -0
- package/packs/gallery-soft/habit/example.html +3 -0
- package/packs/gallery-soft/habit/kit.js +366 -0
- package/packs/gallery-soft/habit/preview.svg +1 -0
- package/packs/gallery-soft/habit/style.css +251 -0
- package/packs/gallery-soft/habit/template.html +3 -0
- package/packs/gallery-soft/habit/writing.md +23 -0
- package/packs/gallery-soft/heatmap/component.json +18 -0
- package/packs/gallery-soft/heatmap/example.html +3 -0
- package/packs/gallery-soft/heatmap/kit.js +498 -0
- package/packs/gallery-soft/heatmap/preview.svg +1 -0
- package/packs/gallery-soft/heatmap/style.css +181 -0
- package/packs/gallery-soft/heatmap/template.html +3 -0
- package/packs/gallery-soft/heatmap/writing.md +23 -0
- package/packs/gallery-soft/note/component.json +14 -0
- package/packs/gallery-soft/note/example.html +3 -0
- package/packs/gallery-soft/note/kit.js +162 -0
- package/packs/gallery-soft/note/preview.svg +1 -0
- package/packs/gallery-soft/note/style.css +148 -0
- package/packs/gallery-soft/note/template.html +3 -0
- package/packs/gallery-soft/note/writing.md +20 -0
- package/packs/gallery-soft/recent/component.json +13 -0
- package/packs/gallery-soft/recent/example.html +1 -0
- package/packs/gallery-soft/recent/kit.js +335 -0
- package/packs/gallery-soft/recent/preview.svg +1 -0
- package/packs/gallery-soft/recent/style.css +160 -0
- package/packs/gallery-soft/recent/template.html +1 -0
- package/packs/gallery-soft/recent/writing.md +17 -0
- package/packs/gallery-soft/static/component.json +13 -0
- package/packs/gallery-soft/static/example.html +12 -0
- package/packs/gallery-soft/static/preview.svg +1 -0
- package/packs/gallery-soft/static/style.css +186 -0
- package/packs/gallery-soft/static/template.html +1 -0
- package/packs/gallery-soft/static/writing.md +20 -0
- package/packs/gallery-soft/stats/component.json +13 -0
- package/packs/gallery-soft/stats/example.html +1 -0
- package/packs/gallery-soft/stats/kit.js +341 -0
- package/packs/gallery-soft/stats/preview.svg +1 -0
- package/packs/gallery-soft/stats/style.css +119 -0
- package/packs/gallery-soft/stats/template.html +1 -0
- package/packs/gallery-soft/stats/writing.md +13 -0
- package/packs/gallery-soft/today/component.json +12 -0
- package/packs/gallery-soft/today/example.html +1 -0
- package/packs/gallery-soft/today/kit.js +222 -0
- package/packs/gallery-soft/today/preview.svg +1 -0
- package/packs/gallery-soft/today/style.css +178 -0
- package/packs/gallery-soft/today/template.html +1 -0
- package/packs/gallery-soft/today/writing.md +16 -0
- package/packs/gallery-soft/todos/component.json +19 -0
- package/packs/gallery-soft/todos/example.html +3 -0
- package/packs/gallery-soft/todos/kit.js +527 -0
- package/packs/gallery-soft/todos/preview.svg +1 -0
- package/packs/gallery-soft/todos/style.css +212 -0
- package/packs/gallery-soft/todos/template.html +3 -0
- package/packs/gallery-soft/todos/writing.md +32 -0
- package/packs/gallery-soft/weather/component.json +19 -0
- package/packs/gallery-soft/weather/example.html +3 -0
- package/packs/gallery-soft/weather/kit.js +411 -0
- package/packs/gallery-soft/weather/preview.svg +1 -0
- package/packs/gallery-soft/weather/style.css +185 -0
- package/packs/gallery-soft/weather/template.html +3 -0
- package/packs/gallery-soft/weather/writing.md +28 -0
- package/release.json +10 -10
- package/skill/SKILL.md +15 -14
- package/skill/references/building-blocks.md +4 -4
- package/skill/references/extensions.md +11 -7
- package/skill/references/pages-and-data.md +13 -9
- package/skill/references/writing-html.md +2 -0
- package/web/assets/index-BHC5ZOMh.js +15 -0
- package/web/assets/index-xP0uw3a0.css +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-2NRBY6AJ.mjs +0 -2
- package/dist/chunk-35NQ4PLQ.mjs +0 -2
- package/dist/chunk-3KJHLHUO.mjs +0 -2
- package/dist/chunk-4AV456RQ.mjs +0 -2
- package/dist/chunk-5XYHDBJ5.mjs +0 -2
- package/dist/chunk-6A53MZYZ.mjs +0 -2
- package/dist/chunk-6EXH3ORZ.mjs +0 -166
- package/dist/chunk-BVEFGE4B.mjs +0 -2
- package/dist/chunk-D4EURAX4.mjs +0 -4
- package/dist/chunk-DQCE7TJY.mjs +0 -5
- package/dist/chunk-FWNRPMCJ.mjs +0 -12
- package/dist/chunk-FWW4CSAZ.mjs +0 -2
- package/dist/chunk-GY3FAUVM.mjs +0 -2
- package/dist/chunk-JVHHHGEA.mjs +0 -3
- package/dist/chunk-L7UP52JO.mjs +0 -2
- package/dist/chunk-NAMN3RAF.mjs +0 -2
- package/dist/chunk-PZDLJJTJ.mjs +0 -3
- package/dist/chunk-SXHBYZST.mjs +0 -16
- package/dist/chunk-TEZAWDSQ.mjs +0 -2
- package/dist/chunk-UFNYFFKO.mjs +0 -20
- package/dist/chunk-UKSHDZZ6.mjs +0 -2
- package/dist/chunk-VJPFVNNW.mjs +0 -15
- package/dist/chunk-VS3ZMVGX.mjs +0 -5
- package/web/assets/index-BK2e_07w.js +0 -13
- package/web/assets/index-CWFI83mF.css +0 -1
- /package/dist/{chunk-YEY3RSFQ.mjs → chunk-5MMN3N5J.mjs} +0 -0
- /package/dist/{chunk-UH4SIXTR.mjs → chunk-74UW7QSL.mjs} +0 -0
- /package/dist/{chunk-W7XHE4CT.mjs → chunk-EKGYYOSP.mjs} +0 -0
- /package/dist/{chunk-QAIHRH3I.mjs → chunk-KNZJT4ET.mjs} +0 -0
- /package/dist/{chunk-A55HN5OK.mjs → chunk-N3DSBH5S.mjs} +0 -0
- /package/dist/{chunk-QR6A54KJ.mjs → chunk-ZW2A6GBW.mjs} +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/* 柔光风格's countdown. Every selector starts at the instance and goes down a step at a time (>), so that
|
|
2
|
+
nothing here reaches the rest of the page. The soft look's tokens first, light and dark (light-dark(): the
|
|
3
|
+
page's color scheme, which follows its theme), then the face: a faint wash of the accent from the card's
|
|
4
|
+
top over the soft card's fill; a head of an hourglass's chip and the day's name, one line; the count the
|
|
5
|
+
hero at the bottom, its words before it and its unit after it muted, the date under it; on a card wider
|
|
6
|
+
than two cells a small page of a calendar beside the count (the month on a band of the accent, the day
|
|
7
|
+
large), on the day 就是今天 in the accent; with no date the cards' empty face (a calendar in a soft disc,
|
|
8
|
+
a line, where to set one). On a custom page the face is the card's whole room, taken out over the card's
|
|
9
|
+
padding and given the soft frame's (data-pad: 18px on a 2 × 2 card, 20px on a 4 × 2), the board's line
|
|
10
|
+
around it; where no card is (a document, as gallery block check shows it) the face frames itself at the
|
|
11
|
+
size of a 2 × 2 card. */
|
|
12
|
+
[data-doc-component="gallery-soft/countdown"] {
|
|
13
|
+
--soft-font: "PingFang SC", "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
|
|
14
|
+
--soft-num: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
15
|
+
--soft-card: light-dark(#ffffff, #1f1f20);
|
|
16
|
+
--soft-card-line: light-dark(rgb(28 28 32 / 6.5%), rgb(255 255 255 / 7.5%));
|
|
17
|
+
--soft-card-shadow: 0 0 0 1px var(--soft-card-line), 0 1px 2px light-dark(rgb(28 28 32 / 3.5%), rgb(0 0 0 / 0%)), 0 8px 24px -14px light-dark(rgb(28 28 32 / 12%), rgb(0 0 0 / 60%));
|
|
18
|
+
--soft-wash: linear-gradient(180deg, light-dark(rgb(62 122 198 / 6%), rgb(126 173 226 / 7%)) 0%, transparent 52%);
|
|
19
|
+
--soft-text: light-dark(#37352f, #d9d9dc);
|
|
20
|
+
--soft-strong: light-dark(#1f1f1f, #f1f1f2);
|
|
21
|
+
--soft-muted: light-dark(#7a7a7f, #9d9da3);
|
|
22
|
+
--soft-accent: light-dark(#3e7ac6, #7eade2);
|
|
23
|
+
--soft-accent-ink: light-dark(#356cb3, #98bee9);
|
|
24
|
+
--soft-accent-soft: light-dark(#ecf3fb, rgb(126 173 226 / 14%));
|
|
25
|
+
--soft-on-accent: light-dark(#ffffff, #152436);
|
|
26
|
+
--soft-track: light-dark(rgb(28 28 32 / 7%), rgb(255 255 255 / 8%));
|
|
27
|
+
--soft-grip-room: 0px;
|
|
28
|
+
> .gallery-soft-countdown-face {
|
|
29
|
+
--soft-pad: 18px;
|
|
30
|
+
position: relative;
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
width: 144px;
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
height: 144px;
|
|
38
|
+
padding: calc(var(--soft-pad) - var(--soft-edge, 0px));
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
border-radius: 16px;
|
|
41
|
+
box-shadow: var(--soft-card-shadow);
|
|
42
|
+
background: var(--soft-wash), var(--soft-card);
|
|
43
|
+
color: var(--soft-text);
|
|
44
|
+
font: 13px/1.5 var(--soft-font);
|
|
45
|
+
-webkit-font-smoothing: antialiased;
|
|
46
|
+
}
|
|
47
|
+
> .gallery-soft-countdown-face[data-pad="m"] { --soft-pad: 20px; }
|
|
48
|
+
> .gallery-soft-countdown-face[data-pad="l"] { --soft-pad: 24px; }
|
|
49
|
+
> .gallery-soft-countdown-face[data-pad="xs"] { padding: 0 calc(16px - var(--soft-edge, 0px)); justify-content: center; }
|
|
50
|
+
> .gallery-soft-countdown-face:not([data-w="1"], [data-w="2"]) { width: 304px; }
|
|
51
|
+
/* The head: the hourglass's chip and the day's name. */
|
|
52
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-head {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 10px;
|
|
56
|
+
flex: none;
|
|
57
|
+
height: 28px;
|
|
58
|
+
min-width: 0;
|
|
59
|
+
padding-inline-end: var(--soft-grip-room);
|
|
60
|
+
}
|
|
61
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-head > .gallery-soft-countdown-chip {
|
|
62
|
+
display: grid;
|
|
63
|
+
place-items: center;
|
|
64
|
+
flex: none;
|
|
65
|
+
width: 28px;
|
|
66
|
+
height: 28px;
|
|
67
|
+
border-radius: 8px;
|
|
68
|
+
background: var(--soft-accent-soft);
|
|
69
|
+
color: var(--soft-accent);
|
|
70
|
+
}
|
|
71
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-head > .gallery-soft-countdown-chip > svg { display: block; width: 16px; height: 16px; }
|
|
72
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-head > .gallery-soft-countdown-name {
|
|
73
|
+
min-width: 0;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
white-space: nowrap;
|
|
76
|
+
text-overflow: ellipsis;
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
color: var(--soft-strong);
|
|
80
|
+
}
|
|
81
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-head { gap: 8px; height: 24px; }
|
|
82
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-head > .gallery-soft-countdown-chip { width: 24px; height: 24px; border-radius: 7px; }
|
|
83
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-head > .gallery-soft-countdown-chip > svg { width: 14px; height: 14px; }
|
|
84
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-head > .gallery-soft-countdown-name { font-size: 13px; }
|
|
85
|
+
/* The count at the bottom, the calendar's page beside it on a wide card. */
|
|
86
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: flex-end;
|
|
89
|
+
gap: 16px;
|
|
90
|
+
margin-top: auto;
|
|
91
|
+
min-width: 0;
|
|
92
|
+
}
|
|
93
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-grow { flex: 1; min-width: 0; }
|
|
94
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-count {
|
|
95
|
+
margin: 0;
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
font-family: var(--soft-num);
|
|
98
|
+
font-size: 44px;
|
|
99
|
+
font-weight: 700;
|
|
100
|
+
line-height: 1;
|
|
101
|
+
letter-spacing: -.025em;
|
|
102
|
+
font-variant-numeric: tabular-nums;
|
|
103
|
+
color: var(--soft-strong);
|
|
104
|
+
}
|
|
105
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-count { font-size: 40px; }
|
|
106
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-count > :is(.gallery-soft-countdown-label, .gallery-soft-countdown-unit) {
|
|
107
|
+
font-family: var(--soft-font);
|
|
108
|
+
font-size: 13px;
|
|
109
|
+
font-weight: 500;
|
|
110
|
+
letter-spacing: 0;
|
|
111
|
+
color: var(--soft-muted);
|
|
112
|
+
}
|
|
113
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-count > .gallery-soft-countdown-label { margin-right: 6px; }
|
|
114
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-count > .gallery-soft-countdown-unit { margin-left: 4px; }
|
|
115
|
+
/* On the day: its words in the accent. */
|
|
116
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-words {
|
|
117
|
+
font-family: var(--soft-font);
|
|
118
|
+
font-size: 26px;
|
|
119
|
+
line-height: 1.1;
|
|
120
|
+
letter-spacing: .02em;
|
|
121
|
+
color: var(--soft-accent-ink);
|
|
122
|
+
}
|
|
123
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-words { font-size: 24px; }
|
|
124
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-grow > .gallery-soft-countdown-date {
|
|
125
|
+
margin: 8px 0 0;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
text-overflow: ellipsis;
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
color: var(--soft-muted);
|
|
131
|
+
}
|
|
132
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-page {
|
|
133
|
+
position: relative;
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
flex: none;
|
|
137
|
+
width: 62px;
|
|
138
|
+
height: 70px;
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
border-radius: 12px;
|
|
141
|
+
background: var(--soft-card);
|
|
142
|
+
box-shadow: 0 0 0 1px var(--soft-card-line), 0 6px 14px -8px rgb(0 0 0 / 25%);
|
|
143
|
+
}
|
|
144
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-page > .gallery-soft-countdown-month {
|
|
145
|
+
height: 22px;
|
|
146
|
+
background: var(--soft-accent);
|
|
147
|
+
color: var(--soft-on-accent);
|
|
148
|
+
font: 600 11.5px/22px var(--soft-font);
|
|
149
|
+
text-align: center;
|
|
150
|
+
}
|
|
151
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-body > .gallery-soft-countdown-page > .gallery-soft-countdown-day {
|
|
152
|
+
display: grid;
|
|
153
|
+
place-items: center;
|
|
154
|
+
flex: 1;
|
|
155
|
+
font: 700 28px/1 var(--soft-num);
|
|
156
|
+
letter-spacing: -.02em;
|
|
157
|
+
color: var(--soft-strong);
|
|
158
|
+
}
|
|
159
|
+
/* No date: the cards' empty face. */
|
|
160
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-empty {
|
|
161
|
+
display: flex;
|
|
162
|
+
flex-direction: column;
|
|
163
|
+
align-items: center;
|
|
164
|
+
justify-content: center;
|
|
165
|
+
gap: 4px;
|
|
166
|
+
flex: 1;
|
|
167
|
+
min-height: 0;
|
|
168
|
+
text-align: center;
|
|
169
|
+
}
|
|
170
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-empty > .gallery-soft-countdown-disc {
|
|
171
|
+
display: grid;
|
|
172
|
+
place-items: center;
|
|
173
|
+
flex: none;
|
|
174
|
+
width: 44px;
|
|
175
|
+
height: 44px;
|
|
176
|
+
margin-bottom: 8px;
|
|
177
|
+
border-radius: 50%;
|
|
178
|
+
background: var(--soft-track);
|
|
179
|
+
color: var(--soft-muted);
|
|
180
|
+
}
|
|
181
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-empty > .gallery-soft-countdown-disc > svg { display: block; width: 20px; height: 20px; }
|
|
182
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-empty > .gallery-soft-countdown-line { margin: 0; font-size: 13px; color: var(--soft-text); }
|
|
183
|
+
> .gallery-soft-countdown-face > .gallery-soft-countdown-empty > .gallery-soft-countdown-how { margin: 0; font-size: 12px; color: var(--soft-muted); }
|
|
184
|
+
/* A 2 × 2 card: a smaller disc, a line each, so that the three stand clear of the head. */
|
|
185
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-empty { gap: 0; }
|
|
186
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-disc { width: 30px; height: 30px; margin-bottom: 4px; }
|
|
187
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-disc > svg { width: 15px; height: 15px; }
|
|
188
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-empty > :is(.gallery-soft-countdown-line, .gallery-soft-countdown-how) { max-width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
189
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-line { font-size: 12.5px; line-height: 18px; }
|
|
190
|
+
> .gallery-soft-countdown-face[data-pad="s"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-how { line-height: 16px; }
|
|
191
|
+
/* A card 2 cells high and wider: the disc beside its line and what to do, which under the head have no room for a column. */
|
|
192
|
+
> .gallery-soft-countdown-face[data-pad="m"] > .gallery-soft-countdown-empty { display: grid; grid-template-columns: auto minmax(0, max-content); column-gap: 12px; row-gap: 2px; align-content: center; justify-content: center; text-align: left; }
|
|
193
|
+
> .gallery-soft-countdown-face[data-pad="m"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-disc { grid-row: 1 / span 2; width: 36px; height: 36px; margin: 0; }
|
|
194
|
+
> .gallery-soft-countdown-face[data-pad="m"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-disc > svg { width: 17px; height: 17px; }
|
|
195
|
+
> .gallery-soft-countdown-face[data-pad="m"] > .gallery-soft-countdown-empty > :is(.gallery-soft-countdown-line, .gallery-soft-countdown-how) { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
196
|
+
> .gallery-soft-countdown-face[data-pad="m"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-line { align-self: end; }
|
|
197
|
+
> .gallery-soft-countdown-face[data-pad="m"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-how { align-self: start; }
|
|
198
|
+
> .gallery-soft-countdown-face[data-pad="m"] > .gallery-soft-countdown-empty > .gallery-soft-countdown-line:last-child { grid-row: 1 / span 2; align-self: center; }
|
|
199
|
+
}
|
|
200
|
+
/* On a custom page: the face over the card's whole room, its padding the soft frame's, the board's line around it; the head's end kept clear of the grip's corner. */
|
|
201
|
+
.gallery-card > [data-doc-component="gallery-soft/countdown"] { height: 100%; }
|
|
202
|
+
.gallery-card > [data-doc-component="gallery-soft/countdown"] > .gallery-soft-countdown-face {
|
|
203
|
+
--soft-edge: 1px;
|
|
204
|
+
--soft-grip-room: max(0px, calc(40px - var(--soft-pad)));
|
|
205
|
+
width: auto;
|
|
206
|
+
max-width: none;
|
|
207
|
+
height: calc(100% + 2 * var(--gallery-card-pad));
|
|
208
|
+
margin: calc(-1 * var(--gallery-card-pad));
|
|
209
|
+
border-radius: calc(var(--gallery-card-radius) - 1px);
|
|
210
|
+
box-shadow: none;
|
|
211
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 倒计时 (gallery-soft/countdown)
|
|
2
|
+
|
|
3
|
+
A card for a custom page (its places are `page`): how many days are left until a date, 就是今天 on the day, and how many days have gone since after it. Use it for a day the user waits for or counts from (a holiday, a deadline, an anniversary).
|
|
4
|
+
|
|
5
|
+
It is 柔光风格's (the pack `gallery-soft`, off until the user turns it on in 设置 › 组件列表): the same card as Gallery 风格's `gallery/countdown`, its settings, its data and what the user does on it the same, in the soft look of design A (a faint wash of blue from the card's top, rounded chips, big numbers). Write it only where the user asked for 柔光风格 or the page already holds its cards.
|
|
6
|
+
|
|
7
|
+
## Settings
|
|
8
|
+
|
|
9
|
+
Its two settings are the card's form, which the user opens from the grip at the card's top right › 设置…:
|
|
10
|
+
|
|
11
|
+
| Key | Label | Type | What it is |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| `title` | 名称 | text | the name of the day, as 春节 or 项目上线; left empty, the card says 还有 / 已经过去 alone |
|
|
14
|
+
| `date` | 日期 | date | the day, `YYYY-MM-DD` |
|
|
15
|
+
|
|
16
|
+
They are kept in its data part `settings`, a `<script type="application/json">`, as example.html does: `{"title":"春节","date":"2027-02-06"}` (on one line and compact, as the card's 设置… writes it back). Write them there when the user tells you the day, or `gallery page add <slug> gallery-soft/countdown --set date=2027-02-06 --set title=春节` with the add; the date is the one setting it cannot show anything without (`required` in its manifest): ask the user for it when they did not say which day. `{}` (template.html, what a menu adds) is a card with no date yet, which asks for one (右上角手柄 › 设置…), and `gallery page check` notes it. The block reads them and writes nothing.
|
|
17
|
+
|
|
18
|
+
## How to write it
|
|
19
|
+
|
|
20
|
+
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 gallery-soft/countdown`, the instance as template.html has it, then its settings as above. Sizes: 2 × 2 (the default) and 4 × 2 (a small page of a calendar beside the count).
|
|
21
|
+
|
|
22
|
+
## How it looks
|
|
23
|
+
|
|
24
|
+
The day's name beside an hourglass's chip on the top line (春节; 倒计时 where it has none), the count of days large at the bottom, 还有 (or 已过) before it and 天 after it, muted, the date under it (2月6日 周六; on a 4 × 2 card 2027年2月6日 星期六, and a small page of a calendar at the right, the month on a band of the accent over the day); on the day itself 就是今天 in the accent; with no date a calendar in a soft disc over 还没有设日期 and where to set it (右上角手柄 › 设置…); in 柔光风格's soft look (as every card of it: a faint wash of blue from the card's top, a head of a rounded tinted chip and a title, its end clear of the card's grip), light and dark. The count moves by itself at midnight (it hears the kit's clock). The script runs with the rights of the page's own scripts, as any block's does.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "gallery-soft/habit",
|
|
3
|
+
"apiVersion": 1,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"name": { "zh": "习惯打卡", "en": "Habit tracker" },
|
|
6
|
+
"description": { "zh": "每天打一次卡,显示连续了几天和最近一周,大卡片还有最近四周;名字在卡片上直接改", "en": "A check-in a day, the streak and the last seven days, the last four weeks on a large card; the habit's name is edited on the card" },
|
|
7
|
+
"words": "xiguandaka xiguan daka habit streak checkin tracker 习惯 打卡 连续 坚持",
|
|
8
|
+
"parts": {
|
|
9
|
+
"habit": { "at": ":scope > script", "edit": "data" }
|
|
10
|
+
},
|
|
11
|
+
"runtime": { "generated": [":scope > .gallery-soft-habit-face"] },
|
|
12
|
+
"places": ["page"],
|
|
13
|
+
"card": { "sizes": [[4, 2], [2, 2], [4, 4]], "resize": true }
|
|
14
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// gallery-soft/habit (习惯打卡, 柔光风格): Gallery 风格's gallery/habit in the soft look, the same logic and
|
|
2
|
+
// data. One habit, checked in once a day. Its data part, a script of JSON, holds the habit's name and the
|
|
3
|
+
// days it was done, each YYYY-MM-DD by the reader's clock, in order: { "name": "喝八杯水", "days":
|
|
4
|
+
// ["2026-11-05", "2026-11-06"] }. The card draws the name in a field of its own in its head (DocKit.input:
|
|
5
|
+
// written as the reader presses Enter or leaves it, never while an input method composes), the streak (the
|
|
6
|
+
// days in a row up to today, or up to yesterday while today is not checked yet, so that a streak lasts the
|
|
7
|
+
// day it is to be kept), how many days in all, and a way to check today in, or take today's check-in back
|
|
8
|
+
// when it is there. Each press and each name is one context.editJSON, one undo step, the JSON written back on
|
|
9
|
+
// one line as it was: what the reader does is in the file, and nothing else is (the streak, the days and the
|
|
10
|
+
// calendar are worked out as it draws). It draws from the part on every `source` (an undo, an Agent's write)
|
|
11
|
+
// and again as a day begins (the kit's `clock`, "day"), so that "today" moves at midnight. Where nothing
|
|
12
|
+
// takes its writes (an export, a page that has not come to edit yet: context.canEdit() false) the name is
|
|
13
|
+
// read-only and nothing can be pressed: the button a plain line saying whether today is done.
|
|
14
|
+
//
|
|
15
|
+
// Its face, laid out by its card's size (`context.size`, read again on `layout`; data-w, data-h, data-pad):
|
|
16
|
+
// a head of a flame's warm chip, the name and the days in all at its end (共 42 天); the streak the hero
|
|
17
|
+
// (连续打卡 over its count). A 4 × 2 card sets the last seven days beside it as a row of dots, a check in
|
|
18
|
+
// each one done, today's the button itself (a ring with a plus that a press fills); a 2 × 2 card the streak
|
|
19
|
+
// over a button as wide as the card (打卡, or 已打卡 in green); a 4 × 4 card the streak, the longest one and
|
|
20
|
+
// the button, over the last four weeks as a calendar, Monday first, each day done tinted, today ringed. A
|
|
21
|
+
// habit never checked in shows, where the streak stands, a line inviting the first (还没打过卡, 从今天开始).
|
|
22
|
+
// Its words are the page's language (the UI's where Gallery says it, else the page's lang).
|
|
23
|
+
|
|
24
|
+
const WORDS = {
|
|
25
|
+
zh: {
|
|
26
|
+
name: "给习惯起个名字",
|
|
27
|
+
nameShort: "起个名字",
|
|
28
|
+
check: "打卡",
|
|
29
|
+
checked: "已打卡",
|
|
30
|
+
undo: "再按一下,撤回今天的打卡",
|
|
31
|
+
todayDone: "今天已打卡",
|
|
32
|
+
todayOpen: "今天还没打卡",
|
|
33
|
+
streak: "连续打卡",
|
|
34
|
+
streakShort: "连续",
|
|
35
|
+
unit: () => "天",
|
|
36
|
+
total: (/** @type {number} */ days) => `共 ${days} 天`,
|
|
37
|
+
longest: (/** @type {number} */ days) => `最长 ${days} 天`,
|
|
38
|
+
fresh: "还没打过卡",
|
|
39
|
+
freshHow: "从今天开始",
|
|
40
|
+
week: "最近七天",
|
|
41
|
+
weekdays: ["日", "一", "二", "三", "四", "五", "六"],
|
|
42
|
+
monday: ["一", "二", "三", "四", "五", "六", "日"],
|
|
43
|
+
today: "今",
|
|
44
|
+
day: (/** @type {number} */ month, /** @type {number} */ day, /** @type {boolean} */ done) => `${month}月${day}日${done ? ",已打卡" : ",没打卡"}`,
|
|
45
|
+
},
|
|
46
|
+
en: {
|
|
47
|
+
name: "Name this habit",
|
|
48
|
+
nameShort: "Name it",
|
|
49
|
+
check: "Check in",
|
|
50
|
+
checked: "Done",
|
|
51
|
+
undo: "Press again to take today's check-in back",
|
|
52
|
+
todayDone: "Done today",
|
|
53
|
+
todayOpen: "Not done today",
|
|
54
|
+
streak: "Streak",
|
|
55
|
+
streakShort: "Streak",
|
|
56
|
+
unit: (/** @type {number} */ days) => (days === 1 ? "day" : "days"),
|
|
57
|
+
total: (/** @type {number} */ days) => `${days} ${days === 1 ? "day" : "days"} in all`,
|
|
58
|
+
longest: (/** @type {number} */ days) => `Best ${days}`,
|
|
59
|
+
fresh: "No check-ins yet",
|
|
60
|
+
freshHow: "Start today",
|
|
61
|
+
week: "The last seven days",
|
|
62
|
+
weekdays: ["S", "M", "T", "W", "T", "F", "S"],
|
|
63
|
+
monday: ["M", "T", "W", "T", "F", "S", "S"],
|
|
64
|
+
today: "Today",
|
|
65
|
+
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"}`,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** The language the page speaks: the UI's where Gallery says it (data-ge-language), else its lang's. */
|
|
70
|
+
function languageOf(/** @type {HTMLElement} */ root) {
|
|
71
|
+
const ui = root.getAttribute("data-ge-language");
|
|
72
|
+
return (ui !== null ? ui === "en" : /^en(?:-|$)/i.test(root.lang)) ? "en" : "zh";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A day of the reader's clock as the data writes it, YYYY-MM-DD.
|
|
79
|
+
* @param {Date} date
|
|
80
|
+
*/
|
|
81
|
+
function dayOf(date) {
|
|
82
|
+
const two = (/** @type {number} */ value) => String(value).padStart(2, "0");
|
|
83
|
+
return `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The day `count` days after `day` (before it for a count below 0), both YYYY-MM-DD, by the calendar
|
|
88
|
+
* (counted in UTC, so that a change of the clocks changes nothing).
|
|
89
|
+
* @param {string} day
|
|
90
|
+
* @param {number} count
|
|
91
|
+
*/
|
|
92
|
+
function dayAfter(day, count) {
|
|
93
|
+
const [year, month, date] = day.split("-").map(Number);
|
|
94
|
+
const moved = new Date(Date.UTC(year, month - 1, date + count));
|
|
95
|
+
const two = (/** @type {number} */ value) => String(value).padStart(2, "0");
|
|
96
|
+
return `${moved.getUTCFullYear()}-${two(moved.getUTCMonth() + 1)}-${two(moved.getUTCDate())}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The weekday of a day (YYYY-MM-DD), 0 for Sunday. @param {string} day */
|
|
100
|
+
function weekdayOf(day) {
|
|
101
|
+
const [year, month, date] = day.split("-").map(Number);
|
|
102
|
+
return new Date(Date.UTC(year, month - 1, date)).getUTCDay();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The habit `data` is (context.readJSON's, or the draft context.editJSON hands a change): its name ("" for
|
|
107
|
+
* none yet) and its days, as they stand; throws saying why it is none, and the block draws nothing over it.
|
|
108
|
+
* @param {any} data
|
|
109
|
+
* @returns {{ name: string, days: string[] }}
|
|
110
|
+
*/
|
|
111
|
+
function readHabit(data) {
|
|
112
|
+
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\", …] }");
|
|
113
|
+
const name = data.name ?? "";
|
|
114
|
+
if (typeof name !== "string") throw new Error("its name is no string");
|
|
115
|
+
const days = data.days ?? [];
|
|
116
|
+
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");
|
|
117
|
+
return { name, days };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The days in a row that end today, or yesterday while today is not checked yet (a streak lasts the day
|
|
122
|
+
* it is to be kept); 0 when neither was done.
|
|
123
|
+
* @param {string[]} days
|
|
124
|
+
* @param {string} today
|
|
125
|
+
*/
|
|
126
|
+
function streakOf(days, today) {
|
|
127
|
+
const done = new Set(days);
|
|
128
|
+
let day = done.has(today) ? today : dayAfter(today, -1);
|
|
129
|
+
let count = 0;
|
|
130
|
+
while (done.has(day)) {
|
|
131
|
+
count += 1;
|
|
132
|
+
day = dayAfter(day, -1);
|
|
133
|
+
}
|
|
134
|
+
return count;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The longest run of days in a row the habit was done, 0 for none (the 4 × 4 card's 最长).
|
|
139
|
+
* @param {string[]} days
|
|
140
|
+
*/
|
|
141
|
+
function longestOf(days) {
|
|
142
|
+
const sorted = [...new Set(days)].sort();
|
|
143
|
+
let longest = 0;
|
|
144
|
+
let run = 0;
|
|
145
|
+
sorted.forEach((day, index) => {
|
|
146
|
+
run = index > 0 && dayAfter(sorted[index - 1], 1) === day ? run + 1 : 1;
|
|
147
|
+
longest = Math.max(longest, run);
|
|
148
|
+
});
|
|
149
|
+
return longest;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* `days` with `today` checked in, or taken back where it was: in order, each once.
|
|
154
|
+
* @param {string[]} days
|
|
155
|
+
* @param {string} today
|
|
156
|
+
*/
|
|
157
|
+
function toggled(days, today) {
|
|
158
|
+
const done = new Set(days);
|
|
159
|
+
if (done.has(today)) done.delete(today);
|
|
160
|
+
else done.add(today);
|
|
161
|
+
return [...done].sort();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The seven days that end today, the oldest first: each its day (YYYY-MM-DD), its weekday (0 for
|
|
166
|
+
* Sunday) and whether it was done.
|
|
167
|
+
* @param {string[]} days
|
|
168
|
+
* @param {string} today
|
|
169
|
+
*/
|
|
170
|
+
function weekOf(days, today) {
|
|
171
|
+
const done = new Set(days);
|
|
172
|
+
return Array.from({ length: 7 }, (_, index) => {
|
|
173
|
+
const day = dayAfter(today, index - 6);
|
|
174
|
+
return { day, weekday: weekdayOf(day), done: done.has(day) };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The last `weeks` weeks to the one of `today`, Monday first, as the 4 × 4 card's calendar draws them: each
|
|
180
|
+
* day its date (YYYY-MM-DD), whether it was done, whether it is today and whether it is still to come.
|
|
181
|
+
* @param {string[]} days
|
|
182
|
+
* @param {string} today
|
|
183
|
+
* @param {number} [weeks]
|
|
184
|
+
*/
|
|
185
|
+
function calendarOf(days, today, weeks = 4) {
|
|
186
|
+
const done = new Set(days);
|
|
187
|
+
const first = dayAfter(today, -((weekdayOf(today) + 6) % 7) - 7 * (weeks - 1));
|
|
188
|
+
return Array.from({ length: 7 * weeks }, (_, index) => {
|
|
189
|
+
const day = dayAfter(first, index);
|
|
190
|
+
return { day, done: done.has(day), today: day === today, future: day > today };
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Today checked in, or taken back: one write (context.editJSON), what a press of the button calls, and
|
|
196
|
+
* the example too. Whether it was taken.
|
|
197
|
+
* @param {DocComponentContext} context
|
|
198
|
+
* @param {string} today
|
|
199
|
+
*/
|
|
200
|
+
function pressToday(context, today) {
|
|
201
|
+
return context.editJSON("habit", (data) => {
|
|
202
|
+
data.days = toggled(readHabit(data).days, today);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The habit named `name`, one write (context.editJSON): what the name's field calls once the reader is
|
|
208
|
+
* done with it. Whether it was taken.
|
|
209
|
+
* @param {DocComponentContext} context
|
|
210
|
+
* @param {string} name
|
|
211
|
+
*/
|
|
212
|
+
function rename(context, name) {
|
|
213
|
+
return context.editJSON("habit", (data) => {
|
|
214
|
+
readHabit(data);
|
|
215
|
+
data.name = name.trim();
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The size the face is laid out by: its card's cells (`context.size`), or `fallback` (the manifest's first
|
|
221
|
+
* size) where no card is; and the frame's padding for it, as the soft cards pad theirs: "xs" on a card one
|
|
222
|
+
* cell high, "s" on a small one (2 × 2 and less), "l" on one 3 cells high or more, else "m".
|
|
223
|
+
* @param {DocComponentSize | null | undefined} size
|
|
224
|
+
* @param {[number, number]} fallback
|
|
225
|
+
*/
|
|
226
|
+
function sizeOf(size, fallback) {
|
|
227
|
+
const w = size?.w ?? fallback[0];
|
|
228
|
+
const h = size?.h ?? fallback[1];
|
|
229
|
+
return { w, h, pad: h === 1 ? "xs" : w <= 2 && h <= 2 ? "s" : h >= 3 ? "l" : "m" };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* An element of the face, of `kind` (its class).
|
|
234
|
+
* @template {keyof HTMLElementTagNameMap} K
|
|
235
|
+
* @param {K} tag
|
|
236
|
+
* @param {string} kind
|
|
237
|
+
* @param {string} [text]
|
|
238
|
+
*/
|
|
239
|
+
function made(tag, kind, text) {
|
|
240
|
+
const node = document.createElement(tag);
|
|
241
|
+
node.className = `gallery-soft-habit-${kind}`;
|
|
242
|
+
if (text !== undefined) node.textContent = text;
|
|
243
|
+
return node;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.3 4.3L19 7.3"/></svg>';
|
|
247
|
+
const PLUS = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>';
|
|
248
|
+
|
|
249
|
+
DocKit.component({
|
|
250
|
+
id: "gallery-soft/habit",
|
|
251
|
+
on: ["source", "editable", "clock", "layout"],
|
|
252
|
+
mount(host, context) {
|
|
253
|
+
const words = WORDS[languageOf(document.documentElement)];
|
|
254
|
+
const face = made("div", "face");
|
|
255
|
+
const head = made("div", "head");
|
|
256
|
+
const chip = made("span", "chip");
|
|
257
|
+
chip.setAttribute("aria-hidden", "true");
|
|
258
|
+
chip.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.2c.6 3 4.8 5 4.8 9.8a4.8 4.8 0 0 1-9.6 0c0-2.1.9-3.6 2.1-4.6.2 1.6 1 2.5 2 2.8-.6-2.6-.3-5.2.7-8z"/></svg>';
|
|
259
|
+
const name = made("input", "name");
|
|
260
|
+
name.type = "text";
|
|
261
|
+
name.placeholder = words.name;
|
|
262
|
+
name.spellcheck = false;
|
|
263
|
+
name.setAttribute("aria-label", words.name);
|
|
264
|
+
const total = made("span", "total");
|
|
265
|
+
head.append(chip, name, total);
|
|
266
|
+
// The streak: its label over its count, the count large and its unit after it; the longest beside it on a tall card.
|
|
267
|
+
const streak = made("div", "streak");
|
|
268
|
+
const label = made("span", "label", words.streak);
|
|
269
|
+
const figure = made("p", "figure");
|
|
270
|
+
const before = made("span", "before", words.streakShort);
|
|
271
|
+
const count = made("strong", "count");
|
|
272
|
+
const unit = made("span", "unit");
|
|
273
|
+
figure.append(before, count, unit);
|
|
274
|
+
const longest = made("span", "longest");
|
|
275
|
+
const row = made("div", "row");
|
|
276
|
+
row.append(figure, longest);
|
|
277
|
+
streak.append(label, row);
|
|
278
|
+
// Never checked in: this, where the streak stands.
|
|
279
|
+
const fresh = made("p", "fresh");
|
|
280
|
+
fresh.append(made("span", "fresh-line", words.fresh), made("span", "fresh-how", words.freshHow));
|
|
281
|
+
const press = made("button", "check");
|
|
282
|
+
press.type = "button";
|
|
283
|
+
const week = made("ol", "week");
|
|
284
|
+
week.setAttribute("aria-label", words.week);
|
|
285
|
+
const calendar = made("div", "calendar");
|
|
286
|
+
calendar.setAttribute("aria-hidden", "true");
|
|
287
|
+
const top = made("div", "top");
|
|
288
|
+
top.append(streak, fresh, press);
|
|
289
|
+
face.append(head, top, week, calendar);
|
|
290
|
+
host.append(face);
|
|
291
|
+
const named = DocKit.input(name, { signal: context.signal, commit: (value) => rename(context, value) });
|
|
292
|
+
const pressed = () => {
|
|
293
|
+
if (context.canEdit()) pressToday(context, dayOf(new Date()));
|
|
294
|
+
};
|
|
295
|
+
press.addEventListener("click", pressed, { signal: context.signal });
|
|
296
|
+
|
|
297
|
+
const draw = (/** @type {string} */ reason = "", /** @type {unknown} */ detail = null) => {
|
|
298
|
+
if (reason === "clock" && detail !== "day") return; // a minute moves no day
|
|
299
|
+
const data = context.readJSON("habit");
|
|
300
|
+
if (data === undefined) return; // no JSON: the instance has failed, and Gallery shows what it holds
|
|
301
|
+
let habit;
|
|
302
|
+
try {
|
|
303
|
+
habit = readHabit(data);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return context.fail(error);
|
|
306
|
+
}
|
|
307
|
+
const { w, h, pad } = sizeOf(context.size, [4, 2]);
|
|
308
|
+
Object.assign(face.dataset, { w: String(w), h: String(h), pad });
|
|
309
|
+
name.placeholder = pad === "s" ? words.nameShort : words.name; // a 2 × 2 card's head holds no more
|
|
310
|
+
const today = dayOf(new Date());
|
|
311
|
+
const writes = context.canEdit();
|
|
312
|
+
const done = habit.days.includes(today);
|
|
313
|
+
name.readOnly = !writes;
|
|
314
|
+
named.set(habit.name);
|
|
315
|
+
face.toggleAttribute("data-named", habit.name.trim() !== "");
|
|
316
|
+
// Where nothing is written, the button is a line saying how today stands: nothing to press.
|
|
317
|
+
press.disabled = !writes;
|
|
318
|
+
press.classList.toggle("gallery-soft-habit-done", done);
|
|
319
|
+
press.innerHTML = writes ? `${done ? CHECK : PLUS}<span></span>` : "<span></span>";
|
|
320
|
+
/** @type {HTMLElement} */ (press.lastElementChild).textContent = writes ? (done ? words.checked : words.check) : (done ? words.todayDone : words.todayOpen);
|
|
321
|
+
press.title = writes && done ? words.undo : "";
|
|
322
|
+
press.setAttribute("aria-pressed", String(done));
|
|
323
|
+
face.toggleAttribute("data-fresh", habit.days.length === 0);
|
|
324
|
+
const days = streakOf(habit.days, today);
|
|
325
|
+
count.textContent = String(days);
|
|
326
|
+
unit.textContent = words.unit(days);
|
|
327
|
+
total.textContent = habit.days.length > 0 ? words.total(new Set(habit.days).size) : "";
|
|
328
|
+
longest.textContent = habit.days.length > 0 ? words.longest(longestOf(habit.days)) : "";
|
|
329
|
+
// The last seven days, today's dot the button.
|
|
330
|
+
week.replaceChildren(...weekOf(habit.days, today).map(({ day, weekday, done: kept }) => {
|
|
331
|
+
const item = made("li", "day");
|
|
332
|
+
const now = day === today;
|
|
333
|
+
item.classList.toggle("gallery-soft-habit-kept", kept);
|
|
334
|
+
item.classList.toggle("gallery-soft-habit-today", now);
|
|
335
|
+
const [, month, date] = day.split("-").map(Number);
|
|
336
|
+
item.title = now && writes && kept ? words.undo : words.day(month, date, kept);
|
|
337
|
+
const dot = made(now ? "button" : "span", now ? "today-press" : "dot");
|
|
338
|
+
dot.innerHTML = kept ? CHECK : now ? PLUS : "";
|
|
339
|
+
if (now) {
|
|
340
|
+
// Today's dot is the button: pressed, today is checked in or taken back.
|
|
341
|
+
const button = /** @type {HTMLButtonElement} */ (dot);
|
|
342
|
+
button.type = "button";
|
|
343
|
+
button.disabled = !writes;
|
|
344
|
+
button.setAttribute("aria-pressed", String(kept));
|
|
345
|
+
button.setAttribute("aria-label", writes ? (kept ? words.checked : words.check) : (kept ? words.todayDone : words.todayOpen));
|
|
346
|
+
button.addEventListener("click", pressed);
|
|
347
|
+
} else item.setAttribute("aria-label", words.day(month, date, kept));
|
|
348
|
+
item.append(dot, made("span", "weekday", now ? words.today : words.weekdays[weekday]));
|
|
349
|
+
return item;
|
|
350
|
+
}));
|
|
351
|
+
// The last four weeks, Monday first, under their weekdays' initials.
|
|
352
|
+
calendar.replaceChildren(...words.monday.map((initial) => made("span", "initial", initial)), ...calendarOf(habit.days, today).map((one) => {
|
|
353
|
+
const cell = made("span", "date", String(Number(one.day.slice(8))));
|
|
354
|
+
cell.classList.toggle("gallery-soft-habit-on", one.done && !one.future);
|
|
355
|
+
cell.classList.toggle("gallery-soft-habit-now", one.today && !one.done);
|
|
356
|
+
cell.classList.toggle("gallery-soft-habit-future", one.future);
|
|
357
|
+
return cell;
|
|
358
|
+
}));
|
|
359
|
+
};
|
|
360
|
+
draw();
|
|
361
|
+
return { update: draw };
|
|
362
|
+
},
|
|
363
|
+
example(host, context) {
|
|
364
|
+
pressToday(context, dayOf(new Date()));
|
|
365
|
+
},
|
|
366
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 120"><defs><linearGradient id="w" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#3e7ac6" stop-opacity=".07"/><stop offset=".52" stop-color="#3e7ac6" stop-opacity="0"/></linearGradient></defs><rect width="240" height="120" rx="8" fill="#f3f3f1"/><rect x="16.5" y="12.5" width="207" height="95" rx="14" fill="#ffffff" stroke="#e8e7e4"/><rect x="17" y="13" width="206" height="94" rx="13.5" fill="url(#w)"/><rect x="30" y="24" width="14" height="14" rx="4.5" fill="#fdf1df"/><rect x="34" y="30" width="6" height="2" rx="1" fill="#e59a2c"/><rect x="50" y="28" width="60" height="6" rx="3" fill="#37352f"/><rect x="180" y="28" width="24" height="6" rx="3" fill="#c4c2bd"/><rect x="30" y="56" width="32" height="5" rx="2.5" fill="#c4c2bd"/><rect x="30" y="68" width="26" height="22" rx="11" fill="#1f1f1f"/><circle cx="110" cy="74" r="6" fill="#3e7ac6"/><circle cx="125.5" cy="74" r="6" fill="#3e7ac6"/><circle cx="141" cy="74" r="6" fill="#3e7ac6"/><circle cx="156.5" cy="74" r="6" fill="#3e7ac6"/><circle cx="172" cy="74" r="6" fill="#3e7ac6"/><circle cx="187.5" cy="74" r="6" fill="#3e7ac6"/><circle cx="203" cy="74" r="6" fill="none" stroke="#3e7ac6" stroke-width="1.5"/><rect x="108" y="86" width="4" height="3" rx="1.5" fill="#c4c2bd"/><rect x="123.5" y="86" width="4" height="3" rx="1.5" fill="#c4c2bd"/><rect x="139" y="86" width="4" height="3" rx="1.5" fill="#c4c2bd"/><rect x="154.5" y="86" width="4" height="3" rx="1.5" fill="#c4c2bd"/><rect x="170" y="86" width="4" height="3" rx="1.5" fill="#c4c2bd"/><rect x="185.5" y="86" width="4" height="3" rx="1.5" fill="#c4c2bd"/><rect x="201" y="86" width="4" height="3" rx="1.5" fill="#3e7ac6"/></svg>
|