@heycys/gallery 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/browser/editor/components/align.mjs +58 -0
- package/browser/editor/components/code/index.mjs +1 -1
- package/browser/editor/components/embed/page.mjs +1 -1
- package/browser/editor/components/file/page.mjs +4 -4
- package/browser/editor/components/heading/index.mjs +7 -6
- package/browser/editor/components/icons.mjs +3 -0
- package/browser/editor/components/image/index.mjs +2 -0
- package/browser/editor/components/image/page.mjs +172 -90
- package/browser/editor/components/installed/index.mjs +6 -2
- package/browser/editor/components/installed/page.mjs +80 -13
- package/browser/editor/components/media/icons.mjs +13 -8
- package/browser/editor/components/media/page.mjs +5 -3
- package/browser/editor/components/media/toolbar.mjs +12 -3
- package/browser/editor/components/paragraph/index.mjs +4 -3
- package/browser/editor/components/registry.mjs +17 -5
- package/browser/editor/components/table/index.mjs +4 -2
- package/browser/editor/page/after/index.mjs +288 -0
- package/browser/editor/page/align.mjs +91 -0
- package/browser/editor/page/beside.mjs +5 -6
- package/browser/editor/page/block-menu/index.mjs +6 -3
- package/browser/editor/page/blocks.mjs +3 -1
- package/browser/editor/page/board/hint.mjs +64 -27
- package/browser/editor/page/board/index.mjs +160 -71
- package/browser/editor/page/board/motion.mjs +177 -0
- package/browser/editor/page/board/settings.mjs +4 -3
- package/browser/editor/page/clipboard/index.mjs +10 -3
- package/browser/editor/page/clipboard/own/blocks.mjs +3 -4
- package/browser/editor/page/clipboard/own/describe.mjs +6 -2
- package/browser/editor/page/commit.mjs +2 -2
- package/browser/editor/page/core.mjs +3 -1
- package/browser/editor/page/custom-page.mjs +15 -11
- package/browser/editor/page/editor.mjs +31 -14
- package/browser/editor/page/history.mjs +19 -4
- package/browser/editor/page/insert/index.mjs +20 -22
- package/browser/editor/page/menu.mjs +15 -7
- package/browser/editor/page/page-objects.mjs +3 -7
- package/browser/editor/page/present-links.mjs +5 -4
- package/browser/editor/page/present-shell.mjs +63 -0
- package/browser/editor/page/selection/index.mjs +10 -2
- package/browser/editor/page/table/binding.mjs +87 -0
- package/browser/editor/page/table/icons.mjs +0 -2
- package/browser/editor/page/table/index.mjs +10 -14
- package/browser/editor/page/table/views.mjs +5 -52
- package/browser/editor/page/table/widths.mjs +82 -10
- package/browser/editor/page/typing/index.mjs +44 -12
- package/browser/editor/page/typing/leave.mjs +19 -5
- package/browser/shared/block-packs.mjs +23 -8
- package/browser/shared/component-catalog.mjs +15 -8
- package/browser/shared/component-source.mjs +3 -1
- package/browser/shared/components/heading.mjs +2 -1
- package/browser/shared/components/installed.mjs +1 -1
- package/browser/shared/components/page.mjs +2 -0
- package/browser/shared/components/paragraph.mjs +8 -1
- package/browser/shared/components/table.mjs +1 -0
- package/browser/shared/emoji/line-icons.mjs +37 -0
- package/browser/shared/http.mjs +9 -0
- package/browser/shared/i18n/en.mjs +40 -17
- package/browser/shared/outline.mjs +30 -2
- package/browser/shared/pack-manifest.mjs +12 -1
- package/browser/shared/pages.mjs +169 -57
- package/browser/shared/pinyin.mjs +92 -0
- package/browser/shared/present-step.mjs +17 -0
- package/browser/shared/sidebar-head.mjs +70 -0
- package/browser/shared/web-links.mjs +5 -0
- package/dist/chunk-2OHANJ6Q.mjs +2 -0
- package/dist/{chunk-QOUEFUCH.mjs → chunk-3MBRL2PM.mjs} +1 -1
- package/dist/{chunk-NWR2LT5B.mjs → chunk-4OKWZY5E.mjs} +1 -1
- package/dist/{chunk-RMECZA2K.mjs → chunk-5K44HLTZ.mjs} +1 -1
- package/dist/{chunk-NFMCP2HI.mjs → chunk-5XA2ZBAE.mjs} +2 -2
- package/dist/{chunk-OUUP3WKT.mjs → chunk-67N2QJYT.mjs} +1 -1
- package/dist/chunk-6KBYRA2C.mjs +2 -0
- package/dist/{chunk-SX4LJYT6.mjs → chunk-6N3PX4XX.mjs} +1 -1
- package/dist/{chunk-RFDZXFJF.mjs → chunk-7JDX4E65.mjs} +1 -1
- package/dist/chunk-7M2BQQGZ.mjs +2 -0
- package/dist/{chunk-ZUNVAXLH.mjs → chunk-7OG26PGF.mjs} +1 -1
- package/dist/{chunk-B4P2CM2J.mjs → chunk-AHN3NFKJ.mjs} +1 -1
- package/dist/chunk-ALZ6ZENZ.mjs +3 -0
- package/dist/{chunk-UGWYKHBL.mjs → chunk-AR5D57ZD.mjs} +2 -2
- package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
- package/dist/{chunk-HLYV7RIH.mjs → chunk-CMA72MAG.mjs} +1 -1
- package/dist/{chunk-D4CKRD3V.mjs → chunk-DAZL73VK.mjs} +2 -2
- package/dist/{chunk-6VTU6KEO.mjs → chunk-DJISHPUL.mjs} +1 -1
- package/dist/{chunk-BPL64HGA.mjs → chunk-EAIFITAV.mjs} +1 -1
- package/dist/chunk-EP25HDAJ.mjs +2 -0
- package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
- package/dist/{chunk-3KJHLHUO.mjs → chunk-F6HVDZR4.mjs} +1 -1
- package/dist/{chunk-3NJACR4W.mjs → chunk-FDMAXMQA.mjs} +1 -1
- package/dist/chunk-FETSREUN.mjs +5 -0
- package/dist/{chunk-RKPWLGZ3.mjs → chunk-GHOYKMQ3.mjs} +1 -1
- package/dist/chunk-HDWTZ763.mjs +12 -0
- package/dist/{chunk-VTKNEYT3.mjs → chunk-HPOI4ZX7.mjs} +1 -1
- package/dist/{chunk-OW5NWPOJ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
- package/dist/{chunk-V6RIYQZZ.mjs → chunk-ITOH5EGO.mjs} +1 -1
- package/dist/{chunk-ZWPMCK7P.mjs → chunk-IV64FFI4.mjs} +1 -1
- package/dist/chunk-J43Z6LSS.mjs +2 -0
- package/dist/chunk-J5PUIV3Y.mjs +4 -0
- package/dist/{chunk-KTKHRC6H.mjs → chunk-JA7G53PC.mjs} +1 -1
- package/dist/{chunk-NWFBE2YA.mjs → chunk-JLTZF4LA.mjs} +1 -1
- package/dist/{chunk-GY3FAUVM.mjs → chunk-JZT7I2OJ.mjs} +1 -1
- package/dist/{chunk-QKXMPUXC.mjs → chunk-K5XATEOO.mjs} +1 -1
- package/dist/chunk-L2SUIS5S.mjs +2 -0
- package/dist/{chunk-TEZAWDSQ.mjs → chunk-LM4XFQHZ.mjs} +1 -1
- package/dist/{chunk-5ZJA7CXT.mjs → chunk-MN7BZL4R.mjs} +1 -1
- package/dist/chunk-MTYYHM2G.mjs +15 -0
- package/dist/{chunk-HMYTM3GT.mjs → chunk-MXUBKDRO.mjs} +1 -1
- package/dist/{chunk-MHW7ZKOU.mjs → chunk-OEXWRJME.mjs} +1 -1
- package/dist/{chunk-CRJEG7MT.mjs → chunk-OFHXGWOX.mjs} +1 -1
- package/dist/chunk-ORID3AE7.mjs +166 -0
- package/dist/{chunk-32ZUEFZ7.mjs → chunk-PGDE24N5.mjs} +1 -1
- package/dist/{chunk-UNOYVIYR.mjs → chunk-POTA2FV4.mjs} +1 -1
- package/dist/{chunk-N7AZZLGT.mjs → chunk-QE5RG76S.mjs} +18 -17
- package/dist/{chunk-N76HWBA3.mjs → chunk-R2ZDLJYT.mjs} +1 -1
- package/dist/chunk-SWUTTZCN.mjs +2 -0
- package/dist/{chunk-POQSE4C2.mjs → chunk-TR7TWGHB.mjs} +1 -1
- package/dist/chunk-U5XL7D6A.mjs +3 -0
- package/dist/{chunk-PVER7LSC.mjs → chunk-U7NZI6N4.mjs} +2 -2
- package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
- package/dist/chunk-UJQJKNGK.mjs +2 -0
- package/dist/chunk-UYVCQK3H.mjs +22 -0
- package/dist/{chunk-FJYN7RTH.mjs → chunk-VFX2KNHP.mjs} +1 -1
- package/dist/{chunk-UBBRTJ2R.mjs → chunk-VS7JIGJW.mjs} +1 -1
- package/dist/{chunk-NVRNLK25.mjs → chunk-W7665ZEC.mjs} +2 -2
- package/dist/{chunk-NG375ILQ.mjs → chunk-WQDQWCX5.mjs} +1 -1
- package/dist/chunk-YLPSMDIS.mjs +5 -0
- package/dist/{chunk-VXACTLO3.mjs → chunk-YNAWBAYN.mjs} +1 -1
- package/dist/{chunk-ZB4H7ZN4.mjs → chunk-YROX2O22.mjs} +1 -1
- package/dist/{chunk-YQGT4AB2.mjs → chunk-ZDLPUOAG.mjs} +1 -1
- package/dist/chunk-ZQS6H473.mjs +16 -0
- package/dist/{chunk-OJKMINIU.mjs → chunk-ZWWC4KLF.mjs} +15 -15
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +5 -5
- package/kit/code.js +15 -15
- package/kit/report.css +120 -41
- package/kit/runtime.js +45 -7
- package/kit/tables.js +2 -0
- package/kit/ui.js +2 -0
- package/package.json +1 -1
- package/packs/gallery/chart-view/kit.js +3 -3
- package/packs/gallery/chart-view/writing.md +1 -1
- package/packs/gallery/countdown/component.json +1 -1
- package/packs/gallery/countdown/kit.js +89 -29
- package/packs/gallery/countdown/preview.svg +1 -1
- package/packs/gallery/countdown/style.css +88 -100
- package/packs/gallery/countdown/writing.md +2 -2
- package/packs/gallery/habit/component.json +3 -3
- package/packs/gallery/habit/kit.js +114 -75
- package/packs/gallery/habit/preview.svg +1 -1
- package/packs/gallery/habit/style.css +133 -205
- package/packs/gallery/habit/writing.md +2 -2
- package/packs/gallery/heatmap/component.json +2 -2
- package/packs/gallery/heatmap/kit.js +335 -173
- package/packs/gallery/heatmap/preview.svg +1 -1
- package/packs/gallery/heatmap/style.css +158 -170
- package/packs/gallery/heatmap/writing.md +2 -2
- package/packs/gallery/kanban/kit.js +3 -3
- package/packs/gallery/kanban/writing.md +1 -1
- package/packs/gallery/metric/kit.js +3 -3
- package/packs/gallery/metric/writing.md +1 -1
- package/packs/gallery/note/component.json +3 -3
- package/packs/gallery/note/kit.js +40 -13
- package/packs/gallery/note/preview.svg +1 -1
- package/packs/gallery/note/style.css +51 -30
- package/packs/gallery/note/writing.md +2 -2
- package/packs/gallery/recent/component.json +2 -2
- package/packs/gallery/recent/kit.js +141 -86
- package/packs/gallery/recent/preview.svg +1 -1
- package/packs/gallery/recent/style.css +53 -126
- package/packs/gallery/recent/writing.md +2 -2
- package/packs/gallery/static/component.json +2 -2
- package/packs/gallery/static/preview.svg +1 -1
- package/packs/gallery/static/style.css +99 -66
- package/packs/gallery/static/writing.md +2 -2
- package/packs/gallery/stats/component.json +3 -3
- package/packs/gallery/stats/kit.js +253 -72
- package/packs/gallery/stats/preview.svg +1 -1
- package/packs/gallery/stats/style.css +64 -142
- package/packs/gallery/stats/writing.md +2 -2
- package/packs/gallery/today/component.json +2 -2
- package/packs/gallery/today/kit.js +62 -27
- package/packs/gallery/today/preview.svg +1 -1
- package/packs/gallery/today/style.css +101 -72
- package/packs/gallery/today/writing.md +2 -2
- package/packs/gallery/todos/component.json +1 -1
- package/packs/gallery/todos/kit.js +218 -71
- package/packs/gallery/todos/preview.svg +1 -1
- package/packs/gallery/todos/style.css +170 -223
- package/packs/gallery/todos/writing.md +1 -1
- package/packs/gallery/weather/component.json +2 -2
- package/packs/gallery/weather/kit.js +165 -90
- package/packs/gallery/weather/preview.svg +1 -1
- package/packs/gallery/weather/style.css +100 -218
- package/packs/gallery/weather/writing.md +1 -1
- package/packs/gallery-soft/countdown/component.json +18 -0
- package/packs/gallery-soft/countdown/example.html +3 -0
- package/packs/gallery-soft/countdown/kit.js +206 -0
- package/packs/gallery-soft/countdown/preview.svg +1 -0
- package/packs/gallery-soft/countdown/style.css +211 -0
- package/packs/gallery-soft/countdown/template.html +3 -0
- package/packs/gallery-soft/countdown/writing.md +24 -0
- package/packs/gallery-soft/habit/component.json +14 -0
- package/packs/gallery-soft/habit/example.html +3 -0
- package/packs/gallery-soft/habit/kit.js +366 -0
- package/packs/gallery-soft/habit/preview.svg +1 -0
- package/packs/gallery-soft/habit/style.css +251 -0
- package/packs/gallery-soft/habit/template.html +3 -0
- package/packs/gallery-soft/habit/writing.md +23 -0
- package/packs/gallery-soft/heatmap/component.json +18 -0
- package/packs/gallery-soft/heatmap/example.html +3 -0
- package/packs/gallery-soft/heatmap/kit.js +498 -0
- package/packs/gallery-soft/heatmap/preview.svg +1 -0
- package/packs/gallery-soft/heatmap/style.css +181 -0
- package/packs/gallery-soft/heatmap/template.html +3 -0
- package/packs/gallery-soft/heatmap/writing.md +23 -0
- package/packs/gallery-soft/note/component.json +14 -0
- package/packs/gallery-soft/note/example.html +3 -0
- package/packs/gallery-soft/note/kit.js +162 -0
- package/packs/gallery-soft/note/preview.svg +1 -0
- package/packs/gallery-soft/note/style.css +148 -0
- package/packs/gallery-soft/note/template.html +3 -0
- package/packs/gallery-soft/note/writing.md +20 -0
- package/packs/gallery-soft/recent/component.json +13 -0
- package/packs/gallery-soft/recent/example.html +1 -0
- package/packs/gallery-soft/recent/kit.js +335 -0
- package/packs/gallery-soft/recent/preview.svg +1 -0
- package/packs/gallery-soft/recent/style.css +160 -0
- package/packs/gallery-soft/recent/template.html +1 -0
- package/packs/gallery-soft/recent/writing.md +17 -0
- package/packs/gallery-soft/static/component.json +13 -0
- package/packs/gallery-soft/static/example.html +12 -0
- package/packs/gallery-soft/static/preview.svg +1 -0
- package/packs/gallery-soft/static/style.css +186 -0
- package/packs/gallery-soft/static/template.html +1 -0
- package/packs/gallery-soft/static/writing.md +20 -0
- package/packs/gallery-soft/stats/component.json +13 -0
- package/packs/gallery-soft/stats/example.html +1 -0
- package/packs/gallery-soft/stats/kit.js +341 -0
- package/packs/gallery-soft/stats/preview.svg +1 -0
- package/packs/gallery-soft/stats/style.css +119 -0
- package/packs/gallery-soft/stats/template.html +1 -0
- package/packs/gallery-soft/stats/writing.md +13 -0
- package/packs/gallery-soft/today/component.json +12 -0
- package/packs/gallery-soft/today/example.html +1 -0
- package/packs/gallery-soft/today/kit.js +222 -0
- package/packs/gallery-soft/today/preview.svg +1 -0
- package/packs/gallery-soft/today/style.css +178 -0
- package/packs/gallery-soft/today/template.html +1 -0
- package/packs/gallery-soft/today/writing.md +16 -0
- package/packs/gallery-soft/todos/component.json +19 -0
- package/packs/gallery-soft/todos/example.html +3 -0
- package/packs/gallery-soft/todos/kit.js +527 -0
- package/packs/gallery-soft/todos/preview.svg +1 -0
- package/packs/gallery-soft/todos/style.css +212 -0
- package/packs/gallery-soft/todos/template.html +3 -0
- package/packs/gallery-soft/todos/writing.md +32 -0
- package/packs/gallery-soft/weather/component.json +19 -0
- package/packs/gallery-soft/weather/example.html +3 -0
- package/packs/gallery-soft/weather/kit.js +411 -0
- package/packs/gallery-soft/weather/preview.svg +1 -0
- package/packs/gallery-soft/weather/style.css +185 -0
- package/packs/gallery-soft/weather/template.html +3 -0
- package/packs/gallery-soft/weather/writing.md +28 -0
- package/release.json +9 -9
- package/skill/SKILL.md +12 -12
- package/skill/references/building-blocks.md +4 -4
- package/skill/references/pages-and-data.md +13 -9
- package/skill/references/writing-html.md +2 -0
- package/web/assets/index-BM_K-Auu.css +1 -0
- package/web/assets/index-DRslOBiv.js +15 -0
- package/web/index.html +2 -2
- package/dist/chunk-2NRBY6AJ.mjs +0 -2
- package/dist/chunk-35NQ4PLQ.mjs +0 -2
- package/dist/chunk-4AV456RQ.mjs +0 -2
- package/dist/chunk-5XYHDBJ5.mjs +0 -2
- package/dist/chunk-6A53MZYZ.mjs +0 -2
- package/dist/chunk-6EXH3ORZ.mjs +0 -166
- package/dist/chunk-BVEFGE4B.mjs +0 -2
- package/dist/chunk-D4EURAX4.mjs +0 -4
- package/dist/chunk-DQCE7TJY.mjs +0 -5
- package/dist/chunk-FWNRPMCJ.mjs +0 -12
- package/dist/chunk-FWW4CSAZ.mjs +0 -2
- package/dist/chunk-JVHHHGEA.mjs +0 -3
- package/dist/chunk-PZDLJJTJ.mjs +0 -3
- package/dist/chunk-SXHBYZST.mjs +0 -16
- package/dist/chunk-UFNYFFKO.mjs +0 -20
- package/dist/chunk-UKSHDZZ6.mjs +0 -2
- package/dist/chunk-VJPFVNNW.mjs +0 -15
- package/dist/chunk-VS3ZMVGX.mjs +0 -5
- package/web/assets/index-BK2e_07w.js +0 -13
- package/web/assets/index-CWFI83mF.css +0 -1
|
@@ -2,84 +2,109 @@
|
|
|
2
2
|
// contributions: a column a week (Monday at the top, as a Chinese calendar starts it), a square a day,
|
|
3
3
|
// darker for a day of more, read through context.workspace.query("activity") (its manifest's needs hold
|
|
4
4
|
// "workspace": the server's record, .gallery/state/activity.json, a reader's commits and the Agent's
|
|
5
|
-
// writes, by day). Its one setting, `words` (按字数显示, the card's form: the grip at its top right ›
|
|
6
|
-
// `settings`, which the board writes and this block only reads), draws the
|
|
7
|
-
// change, a day that took more away than it added drawn as none) instead of
|
|
8
|
-
// many
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// (
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
5
|
+
// writes, by day). Its one setting, `words` (按字数显示, the card's form: the grip at its top right ›
|
|
6
|
+
// 设置…, kept in its data part `settings`, which the board writes and this block only reads), draws the
|
|
7
|
+
// words a day added (its net change, a day that took more away than it added drawn as none) instead of
|
|
8
|
+
// how many edits it had. It asks for every day 53 weeks draw (the record keeps as many); a day before the
|
|
9
|
+
// record started is a small gray dot, never an empty square, and a day to come is not drawn. It reads again
|
|
10
|
+
// whenever the workspace says its record changed (the `data` signal, "activity") and as a day begins (the
|
|
11
|
+
// kit's clock), and draws again as its card changes size (`layout`, and its own ResizeObserver on the room
|
|
12
|
+
// the squares have) or its settings change (`source`). Where the workspace cannot be read (an exported
|
|
13
|
+
// file, where it has no server; a server that does not answer) the squares are empty and a line over them
|
|
14
|
+
// says why. The words are the page's language (the UI's where Gallery says it, else the page's lang).
|
|
15
|
+
//
|
|
16
|
+
// Its face (the pack's design B, in the kit's card tokens): a head of the chip (a calendar), 编辑热力图
|
|
17
|
+
// and its quiet meta; the squares the hero, five shades of the accent by how a day stands among the days
|
|
18
|
+
// recorded (`levelsOf`: the quarter of them with least, and so on, never by a straight line to the most,
|
|
19
|
+
// so that a busy day does not make every other pale); today's square ringed; the months' names over the
|
|
20
|
+
// weeks they begin in and 一 三 五 beside the rows; under them, less to more. Laid out by the card's cells
|
|
21
|
+
// (`data-layout`, by context.size): "full" (8 × 4, the default, and 12 × 4) squares of 23px, 4px apart,
|
|
22
|
+
// as many weeks as fit (14, 26), and at their left the figures (`summaryOf`): what the weeks are (近 14 周),
|
|
23
|
+
// the edits (or words) in them, large, the average a day, the days with any, the longest run of days in
|
|
24
|
+
// a row and today's, and a bar of how they split between the reader and the Agent; "compact" (4 × 4) squares
|
|
25
|
+
// of 21px over a line of the total and the days with any; "strip" (12 × 3, a card three cells high or
|
|
26
|
+
// less) squares of 14px (smaller where the card is lower), a year of them, the total and the days in the
|
|
27
|
+
// head's meta, and the day the record began said by the legend's dot. A record begun within the week is
|
|
28
|
+
// said so (从 11月11日 开始记录), and an export's figures say the time they are of (截至 …). A pointer on a
|
|
29
|
+
// square shows its day: its edits (or words), and the reader's and the Agent's.
|
|
29
30
|
|
|
30
31
|
const WORDS = {
|
|
31
32
|
zh: {
|
|
32
33
|
title: "编辑热力图",
|
|
33
|
-
|
|
34
|
-
edits: (/** @type {string} */ period, /** @type {number} */ count) => `${period} ${count} 次编辑`,
|
|
35
|
-
words: (/** @type {string} */ period, /** @type {string} */ count) => `${period}写了 ${count} 字`,
|
|
34
|
+
weeks: (/** @type {number} */ count) => `近 ${count} 周`,
|
|
36
35
|
year: "过去一年",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
before: (/** @type {string} */ date) => `${date}:还没开始记录`,
|
|
40
|
-
date: (/** @type {Date} */ date) => `${date.getMonth() + 1}月${date.getDate()}日`,
|
|
41
|
-
weekday: (/** @type {Date} */ date) => `${date.getMonth() + 1}月${date.getDate()}日 ${["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"][date.getDay()]}`,
|
|
42
|
-
month: (/** @type {number} */ month) => `${month + 1}月`,
|
|
43
|
-
rows: ["一", "", "三", "", "五", "", ""],
|
|
44
|
-
yearLabel: "过去一年",
|
|
45
|
-
editsLabel: "期间编辑",
|
|
46
|
-
wordsLabel: "期间写作",
|
|
47
|
-
editsUnit: "次",
|
|
36
|
+
added: "新增",
|
|
37
|
+
editsUnit: "次编辑",
|
|
48
38
|
wordsUnit: "字",
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
tenThousand: "万字",
|
|
40
|
+
average: (/** @type {string} */ value, /** @type {boolean} */ words) => ["平均每天 ", value, words ? " 字" : " 次"],
|
|
41
|
+
active: "有编辑的天数",
|
|
42
|
+
run: "最长连续",
|
|
43
|
+
today: "今天",
|
|
44
|
+
days: "天",
|
|
45
|
+
times: "次",
|
|
46
|
+
you: "你",
|
|
47
|
+
agent: "Agent",
|
|
48
|
+
activeDays: (/** @type {number} */ count) => `${count} 天有编辑`,
|
|
49
|
+
stripMeta: (/** @type {string} */ period, /** @type {string} */ total, /** @type {number} */ days) => [`${period} ${total}`, `${days} 天`],
|
|
50
|
+
totalEdits: (/** @type {string} */ count) => `${count} 次编辑`,
|
|
51
|
+
byWords: "按字数",
|
|
52
|
+
fresh: "刚开始记录",
|
|
53
|
+
freshSince: (/** @type {string} */ date) => `从 ${date} 开始记录`,
|
|
54
|
+
freshSaid: ["以后每天写了多少,", "都会在右边亮起一格"],
|
|
55
|
+
before: "开始记录之前",
|
|
56
|
+
beforeSince: (/** @type {string} */ date) => `${date} 开始记录之前`,
|
|
57
|
+
upTo: (/** @type {string} */ when) => `截至 ${when}`,
|
|
52
58
|
less: "少",
|
|
53
59
|
more: "多",
|
|
60
|
+
tip: (/** @type {string} */ date, /** @type {number} */ edits, /** @type {number} */ words, /** @type {boolean} */ byWords) => `${date} · ${byWords ? `${words >= 0 ? "+" : "−"}${Math.abs(words).toLocaleString("zh-CN")} 字` : `${edits} 次编辑`}`,
|
|
61
|
+
split: (/** @type {number} */ you, /** @type {number} */ agent) => `你 ${you} 次 · Agent ${agent} 次`,
|
|
62
|
+
unrecorded: "还没开始记录",
|
|
54
63
|
offline: "在 Gallery 里打开时显示",
|
|
55
64
|
failed: "读不到工作区的编辑记录",
|
|
65
|
+
date: (/** @type {Date} */ date) => `${date.getMonth() + 1}月${date.getDate()}日`,
|
|
66
|
+
weekday: (/** @type {Date} */ date) => `${date.getMonth() + 1}月${date.getDate()}日 ${["周日", "周一", "周二", "周三", "周四", "周五", "周六"][date.getDay()]}`,
|
|
67
|
+
month: (/** @type {number} */ month) => `${month + 1}月`,
|
|
68
|
+
rows: ["一", "", "三", "", "五", "", ""],
|
|
56
69
|
locale: "zh-CN",
|
|
57
70
|
},
|
|
58
71
|
en: {
|
|
59
72
|
title: "Editing heatmap",
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
year: "in the past year",
|
|
64
|
-
after: (/** @type {string} */ date) => `since ${date}`,
|
|
65
|
-
day: (/** @type {string} */ date, /** @type {number} */ edits, /** @type {number} */ words) => (edits === 0 ? `${date}: no edits` : `${date}: ${edits} ${edits === 1 ? "edit" : "edits"}, ${words >= 0 ? "+" : "−"}${Math.abs(words)} words`),
|
|
66
|
-
before: (/** @type {string} */ date) => `${date}: not recorded yet`,
|
|
67
|
-
date: (/** @type {Date} */ date) => `${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()]} ${date.getDate()}`,
|
|
68
|
-
weekday: (/** @type {Date} */ date) => `${["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][date.getDay()]}, ${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()]} ${date.getDate()}`,
|
|
69
|
-
month: (/** @type {number} */ month) => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][month],
|
|
70
|
-
rows: ["Mon", "", "Wed", "", "Fri", "", ""],
|
|
71
|
-
yearLabel: "The past year",
|
|
72
|
-
editsLabel: "Edits",
|
|
73
|
-
wordsLabel: "Words written",
|
|
73
|
+
weeks: (/** @type {number} */ count) => `Last ${count} weeks`,
|
|
74
|
+
year: "Past year",
|
|
75
|
+
added: " added",
|
|
74
76
|
editsUnit: "edits",
|
|
75
77
|
wordsUnit: "words",
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
tenThousand: "",
|
|
79
|
+
average: (/** @type {string} */ value, /** @type {boolean} */ words) => ["", value, words ? " words a day" : " a day"],
|
|
80
|
+
active: "Days with edits",
|
|
81
|
+
run: "Longest run",
|
|
82
|
+
today: "Today",
|
|
83
|
+
days: "d",
|
|
84
|
+
times: "",
|
|
85
|
+
you: "You",
|
|
86
|
+
agent: "Agent",
|
|
87
|
+
activeDays: (/** @type {number} */ count) => `${count} ${count === 1 ? "day" : "days"} with edits`,
|
|
88
|
+
stripMeta: (/** @type {string} */ period, /** @type {string} */ total, /** @type {number} */ days) => [`${period}: ${total}`, `${days} ${days === 1 ? "day" : "days"}`],
|
|
89
|
+
totalEdits: (/** @type {string} */ count) => `${count} edits`,
|
|
90
|
+
byWords: "by words",
|
|
91
|
+
fresh: "Just started",
|
|
92
|
+
freshSince: (/** @type {string} */ date) => `Recording since ${date}`,
|
|
93
|
+
freshSaid: ["Each day you write lights a square on the right"],
|
|
94
|
+
before: "Before recording",
|
|
95
|
+
beforeSince: (/** @type {string} */ date) => `Before ${date}`,
|
|
96
|
+
upTo: (/** @type {string} */ when) => `As of ${when}`,
|
|
79
97
|
less: "Less",
|
|
80
98
|
more: "More",
|
|
99
|
+
tip: (/** @type {string} */ date, /** @type {number} */ edits, /** @type {number} */ words, /** @type {boolean} */ byWords) => `${date} · ${byWords ? `${words >= 0 ? "+" : "−"}${Math.abs(words).toLocaleString("en")} words` : `${edits} ${edits === 1 ? "edit" : "edits"}`}`,
|
|
100
|
+
split: (/** @type {number} */ you, /** @type {number} */ agent) => `You ${you} · Agent ${agent}`,
|
|
101
|
+
unrecorded: "Not recorded yet",
|
|
81
102
|
offline: "Shown when opened in Gallery",
|
|
82
103
|
failed: "The workspace's record of edits could not be read",
|
|
104
|
+
date: (/** @type {Date} */ date) => `${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()]} ${date.getDate()}`,
|
|
105
|
+
weekday: (/** @type {Date} */ date) => `${["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][date.getDay()]}, ${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()]} ${date.getDate()}`,
|
|
106
|
+
month: (/** @type {number} */ month) => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][month],
|
|
107
|
+
rows: ["Mon", "", "Wed", "", "Fri", "", ""],
|
|
83
108
|
locale: "en",
|
|
84
109
|
},
|
|
85
110
|
};
|
|
@@ -93,6 +118,9 @@ function languageOf(/** @type {HTMLElement} */ root) {
|
|
|
93
118
|
const MOST_WEEKS = 53;
|
|
94
119
|
/** The days of the record it reads: as many as 53 weeks draw at the most (on a Sunday, its first square 370 days back). */
|
|
95
120
|
const DAYS = MOST_WEEKS * 7;
|
|
121
|
+
/** The weekdays' column and the months' row, in px. */
|
|
122
|
+
const LABELS = 20;
|
|
123
|
+
const MONTHS = 18;
|
|
96
124
|
const two = (/** @type {number} */ value) => String(value).padStart(2, "0");
|
|
97
125
|
/** A local day as the record writes it, YYYY-MM-DD. @param {Date} date */
|
|
98
126
|
const dayOf = (date) => `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}`;
|
|
@@ -100,27 +128,38 @@ const dayOf = (date) => `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two
|
|
|
100
128
|
const after = (date, count) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + count, 12);
|
|
101
129
|
|
|
102
130
|
/**
|
|
103
|
-
* How
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
* How a card of `size` (context.size: its cells, or null where no card holds it, a document) lays its face
|
|
132
|
+
* out: "grid" two cells high or less (8 × 2: the squares alone, the user's word of 2026-11-09), "strip" three
|
|
133
|
+
* cells high, "compact" four wide or less, else "full".
|
|
134
|
+
* @param {{ w: number, h: number } | null | undefined} size
|
|
135
|
+
* @returns {"full" | "compact" | "strip" | "grid"}
|
|
136
|
+
*/
|
|
137
|
+
function layoutOf(size) {
|
|
138
|
+
if (!size) return "full";
|
|
139
|
+
if (size.h <= 2) return "grid";
|
|
140
|
+
if (size.h <= 3) return "strip";
|
|
141
|
+
return size.w <= 4 ? "compact" : "full";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The squares of a layout in a room of `width` x `height` px (the field's: the weekdays' column and the
|
|
146
|
+
* months' row inside it): { pitch (from one square to the next), cell (a square's side), gap, weeks }.
|
|
147
|
+
* "full" 23px squares 4px apart, "compact" 21px; "strip" and "grid" 14px, 3px apart, smaller where the room is
|
|
148
|
+
* lower (seven of them and the months' row in its height, 4px at the least, 2px apart under 10px); as many
|
|
149
|
+
* weeks as the width holds, 1 to 53.
|
|
150
|
+
* @param {"full" | "compact" | "strip" | "grid"} layout
|
|
112
151
|
* @param {number} width
|
|
113
152
|
* @param {number} height
|
|
114
153
|
*/
|
|
115
|
-
function fitOf(width, height) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
return { cell, gap, weeks
|
|
154
|
+
function fitOf(layout, width, height) {
|
|
155
|
+
let pitch = layout === "full" ? 27 : layout === "compact" ? 25 : 17;
|
|
156
|
+
let gap = pitch === 17 ? 3 : 4;
|
|
157
|
+
if ((layout === "strip" || layout === "grid") && Number.isFinite(height)) {
|
|
158
|
+
pitch = Math.max(6, Math.min(17, Math.floor((height - MONTHS + gap) / 7)));
|
|
159
|
+
gap = pitch < 12 ? 2 : 3;
|
|
160
|
+
}
|
|
161
|
+
const weeks = Math.max(1, Math.min(MOST_WEEKS, Math.floor((width - LABELS + gap) / pitch)));
|
|
162
|
+
return { pitch, cell: pitch - gap, gap, weeks };
|
|
124
163
|
}
|
|
125
164
|
|
|
126
165
|
/**
|
|
@@ -143,50 +182,65 @@ function weeksOf(today, weeks) {
|
|
|
143
182
|
}
|
|
144
183
|
|
|
145
184
|
/**
|
|
146
|
-
* The
|
|
147
|
-
*
|
|
185
|
+
* The bounds of the levels of `values` (a day's edits, or the words it added, of every day recorded): the
|
|
186
|
+
* values at 38%, 68% and 90% of the days with any, so that each shade holds a share of the days; for fewer
|
|
187
|
+
* than five such days, a quarter, a half and three quarters of the most.
|
|
188
|
+
* @param {number[]} values
|
|
189
|
+
*/
|
|
190
|
+
function levelsOf(values) {
|
|
191
|
+
const some = values.filter((value) => value > 0).sort((a, b) => a - b);
|
|
192
|
+
if (some.length < 5) {
|
|
193
|
+
const most = some[some.length - 1] ?? 0;
|
|
194
|
+
return [most / 4, most / 2, (3 * most) / 4];
|
|
195
|
+
}
|
|
196
|
+
return [some[Math.floor(some.length * 0.38)], some[Math.floor(some.length * 0.68)], some[Math.floor(some.length * 0.9)]];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The level of a day's square, 0 (none) to 4 (the most), of `value` by the bounds `levels` (levelsOf's).
|
|
148
201
|
* @param {number} value
|
|
149
|
-
* @param {number}
|
|
202
|
+
* @param {number[]} levels
|
|
150
203
|
*/
|
|
151
|
-
function levelOf(value,
|
|
152
|
-
if (!(value > 0)
|
|
153
|
-
|
|
204
|
+
function levelOf(value, levels) {
|
|
205
|
+
if (!(value > 0)) return 0;
|
|
206
|
+
if (value <= levels[0]) return 1;
|
|
207
|
+
if (value <= levels[1]) return 2;
|
|
208
|
+
return value <= levels[2] ? 3 : 4;
|
|
154
209
|
}
|
|
155
210
|
|
|
156
211
|
/**
|
|
157
|
-
* What the card says of the record `days` (workspace.query("activity")'s data: [{ date, edits, words
|
|
158
|
-
* from the day it started or 53 weeks back to today)
|
|
159
|
-
*
|
|
160
|
-
* first
|
|
161
|
-
*
|
|
212
|
+
* What the card says of the record `days` (workspace.query("activity")'s data: [{ date, edits, words,
|
|
213
|
+
* reader, agent }], from the day it started or 53 weeks back to today) over the `weeks` it draws (`today`,
|
|
214
|
+
* a Date), counting `words` (the setting) or edits: `values` (each day's, by its date), `levels`, `since`
|
|
215
|
+
* (the first day recorded), and the figures: `total` (in the weeks drawn), `average` (a day, of the days
|
|
216
|
+
* recorded in them), `active` (days with any edit), `run` (the longest run of them), `today` (its value),
|
|
217
|
+
* `you` and `agent` (the edits of each), and `fresh` (the record began within the week).
|
|
218
|
+
* @param {Array<{ date: string, edits: number, words: number, reader?: { edits: number }, agent?: { edits: number } }>} days
|
|
162
219
|
* @param {boolean} words
|
|
163
|
-
* @param {
|
|
220
|
+
* @param {number} weeks
|
|
221
|
+
* @param {Date} today
|
|
164
222
|
*/
|
|
165
|
-
function summaryOf(days, words,
|
|
166
|
-
const
|
|
167
|
-
const values = new Map(days.map((day) => [day.date,
|
|
223
|
+
function summaryOf(days, words, weeks, today) {
|
|
224
|
+
const valueOf = (/** @type {{ edits: number, words: number }} */ day) => (words ? Math.max(0, day.words) : day.edits);
|
|
225
|
+
const values = new Map(days.map((day) => [day.date, valueOf(day)]));
|
|
168
226
|
const since = days[0]?.date ?? null;
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
let
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
total: words ? format(Math.max(0, written)) : format(edits),
|
|
186
|
-
unit: words ? said.wordsUnit : said.editsUnit,
|
|
187
|
-
more: [said.active(days.filter((day) => day.edits > 0).length), ...(today && today.edits > 0 ? [words ? said.todayWords(signed(today.words)) : said.todayEdits(today.edits)] : [])],
|
|
227
|
+
const first = dayOf(after(today, -((today.getDay() + 6) % 7) - 7 * (weeks - 1)));
|
|
228
|
+
const shown = days.filter((day) => day.date >= first && day.date <= dayOf(today));
|
|
229
|
+
let run = 0;
|
|
230
|
+
let longest = 0;
|
|
231
|
+
for (const day of shown) {
|
|
232
|
+
run = day.edits > 0 ? run + 1 : 0;
|
|
233
|
+
longest = Math.max(longest, run);
|
|
234
|
+
}
|
|
235
|
+
const total = shown.reduce((sum, day) => sum + valueOf(day), 0);
|
|
236
|
+
const agent = shown.reduce((sum, day) => sum + (day.agent?.edits ?? 0), 0);
|
|
237
|
+
const you = shown.reduce((sum, day) => sum + (day.reader?.edits ?? 0), 0);
|
|
238
|
+
const now = days.find((day) => day.date === dayOf(today));
|
|
239
|
+
return {
|
|
240
|
+
values, since, levels: levelsOf([...values.values()]),
|
|
241
|
+
total, average: shown.length > 0 ? total / shown.length : 0, active: shown.filter((day) => day.edits > 0).length, run: longest,
|
|
242
|
+
today: now ? valueOf(now) : 0, you, agent, fresh: since !== null && since > dayOf(after(today, -7)),
|
|
188
243
|
};
|
|
189
|
-
return { line, values, since, figures };
|
|
190
244
|
}
|
|
191
245
|
|
|
192
246
|
/**
|
|
@@ -205,87 +259,160 @@ function made(tag, kind, text) {
|
|
|
205
259
|
|
|
206
260
|
DocKit.component({
|
|
207
261
|
id: "gallery/heatmap",
|
|
208
|
-
on: ["data", "source", "
|
|
262
|
+
on: ["data", "source", "layout", "clock"],
|
|
209
263
|
mount(host, context) {
|
|
210
264
|
const language = languageOf(document.documentElement);
|
|
211
265
|
const words = WORDS[language];
|
|
266
|
+
const format = (/** @type {number} */ value) => new Intl.NumberFormat(words.locale, { maximumFractionDigits: 0 }).format(value);
|
|
212
267
|
const face = made("div", "face");
|
|
213
268
|
const head = made("div", "head");
|
|
214
|
-
const
|
|
269
|
+
const meta = made("span", "line");
|
|
215
270
|
const chip = made("span", "chip");
|
|
216
271
|
chip.setAttribute("aria-hidden", "true");
|
|
217
|
-
chip.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><
|
|
218
|
-
head.append(chip, made("span", "title", words.title),
|
|
272
|
+
chip.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5" width="17" height="15.5" rx="3"/><path d="M3.5 10h17M8 3v4M16 3v4"/></svg>';
|
|
273
|
+
head.append(chip, made("span", "title", words.title), meta);
|
|
219
274
|
const body = made("div", "body");
|
|
220
|
-
|
|
221
|
-
const rows = made("div", "rows");
|
|
222
|
-
for (const label of words.rows) rows.append(made("span", "row", label));
|
|
223
|
-
const grid = made("div", "grid");
|
|
224
|
-
const area = made("div", "area");
|
|
225
|
-
area.append(months, grid);
|
|
226
|
-
// Beside the squares on a wide card: what they count, its edits (or words) large, and a muted line of the days with any and today's.
|
|
275
|
+
// The figures beside the squares (a full card), or the line over them (a compact one).
|
|
227
276
|
const aside = made("div", "aside");
|
|
228
277
|
const period = made("span", "label");
|
|
229
278
|
const total = made("span", "total");
|
|
230
279
|
const totalNumber = made("span", "number");
|
|
231
280
|
const totalUnit = made("span", "unit");
|
|
232
281
|
total.append(totalNumber, totalUnit);
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
282
|
+
const average = made("span", "average");
|
|
283
|
+
const facts = made("dl", "facts");
|
|
284
|
+
const who = made("div", "who");
|
|
285
|
+
const split = made("span", "split");
|
|
286
|
+
const youBar = made("i", "you-bar");
|
|
287
|
+
const agentBar = made("i", "agent-bar");
|
|
288
|
+
split.append(youBar, agentBar);
|
|
289
|
+
const whoWords = made("span", "who-words");
|
|
290
|
+
who.append(split, whoWords);
|
|
291
|
+
aside.append(period, total, average, facts, who);
|
|
292
|
+
const field = made("div", "field");
|
|
293
|
+
const area = made("div", "area");
|
|
294
|
+
const months = made("div", "months");
|
|
295
|
+
const rows = made("div", "rows");
|
|
296
|
+
for (const label of words.rows) rows.append(made("span", "row", label));
|
|
297
|
+
const grid = made("div", "grid");
|
|
298
|
+
// The tip stands over the card, in the page's top layer (a popover; where a browser has none, fixed to the
|
|
299
|
+
// window), never inside the card, whose overflow a tip near its edge would widen into a scrollbar.
|
|
300
|
+
const tip = made("div", "tip");
|
|
301
|
+
const popover = typeof tip.showPopover === "function";
|
|
302
|
+
if (popover) tip.popover = "manual";
|
|
303
|
+
else tip.hidden = true;
|
|
304
|
+
tip.setAttribute("aria-hidden", "true");
|
|
305
|
+
area.append(months, rows, grid, tip);
|
|
236
306
|
const legend = made("div", "legend");
|
|
307
|
+
const note = made("span", "note");
|
|
237
308
|
legend.append(made("span", "less", words.less), ...[0, 1, 2, 3, 4].map((level) => {
|
|
238
309
|
const square = made("span", "square");
|
|
239
310
|
square.dataset.level = String(level);
|
|
240
311
|
return square;
|
|
241
|
-
}), made("span", "more", words.more));
|
|
242
|
-
|
|
312
|
+
}), made("span", "more", words.more), note);
|
|
313
|
+
const overlay = made("p", "overlay");
|
|
314
|
+
overlay.hidden = true;
|
|
315
|
+
field.append(area, legend, overlay);
|
|
316
|
+
body.append(aside, field);
|
|
317
|
+
// As wide as the corner a board's grip takes of the content's top right (--gallery-grip-room, a custom
|
|
318
|
+
// property CSS alone resolves), measured where the months' row is the face's first (the squares alone).
|
|
319
|
+
const gripRoom = made("i", "grip-room");
|
|
320
|
+
gripRoom.setAttribute("aria-hidden", "true");
|
|
321
|
+
face.append(head, body, gripRoom);
|
|
243
322
|
host.append(face);
|
|
244
323
|
const workspace = context.workspace;
|
|
245
324
|
if (!workspace) return void context.fail(new Error("its manifest's needs hold \"workspace\": context.workspace is not there"));
|
|
246
325
|
|
|
247
|
-
/** @type {Array<{ date: string, edits: number, words: number }> | null} */
|
|
326
|
+
/** @type {Array<{ date: string, edits: number, words: number, reader?: { edits: number, words: number }, agent?: { edits: number, words: number } }> | null} */
|
|
248
327
|
let days = null;
|
|
328
|
+
let at = "";
|
|
249
329
|
let failure = "";
|
|
250
330
|
let drawn = "";
|
|
331
|
+
const kv = (/** @type {string} */ name, /** @type {string} */ value, /** @type {string} */ unit) => {
|
|
332
|
+
const row = made("div", "fact");
|
|
333
|
+
const said = made("dd", "fact-value", value);
|
|
334
|
+
if (unit) said.append(made("small", "fact-unit", unit));
|
|
335
|
+
row.append(made("dt", "fact-name", name), said);
|
|
336
|
+
return row;
|
|
337
|
+
};
|
|
338
|
+
const bigOf = (/** @type {number} */ value, /** @type {boolean} */ byWords) => {
|
|
339
|
+
if (!byWords) return [format(value), language === "zh" ? words.editsUnit : ` ${words.editsUnit}`];
|
|
340
|
+
if (language === "zh" && value >= 10_000) return [(value / 10_000).toFixed(1), words.tenThousand];
|
|
341
|
+
return [format(value), language === "zh" ? words.wordsUnit : ` ${words.wordsUnit}`];
|
|
342
|
+
};
|
|
251
343
|
const draw = () => {
|
|
252
344
|
const settings = context.readJSON("settings");
|
|
253
345
|
if (settings === undefined) return; // no JSON: the instance has failed, and Gallery shows what it holds
|
|
254
346
|
const byWords = settings !== null && typeof settings === "object" && settings.words === true;
|
|
255
|
-
|
|
347
|
+
const layout = layoutOf(context.size);
|
|
348
|
+
face.dataset.layout = layout;
|
|
256
349
|
const today = new Date();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
// The room the grid has: the face's, its labels' and its lines' taken off (the weekdays' column and the gap after it; the aside, where it shows,
|
|
268
|
-
// its margin and the gap after it: a wide card's squares had run 3px past its edge where its weeks filled the room).
|
|
269
|
-
const width = body.clientWidth - rows.offsetWidth - 6 - (aside.offsetWidth > 0 ? aside.offsetWidth + 6 + 6 : 0);
|
|
270
|
-
const height = body.clientHeight - months.offsetHeight - 4;
|
|
271
|
-
const { cell, gap, weeks, across } = fitOf(width, height);
|
|
272
|
-
// Less to more: under the squares where the room left under them holds it, else at the head's end.
|
|
273
|
-
const shownAside = aside.offsetWidth > 0;
|
|
274
|
-
const under = height - (7 * cell + 6 * gap) >= 22;
|
|
275
|
-
const place = under ? area : head;
|
|
276
|
-
if (legend.parentElement !== place) place.append(legend);
|
|
277
|
-
// The head's quiet note: the line, or, where the figures say the rest beside the squares, their period.
|
|
278
|
-
line.textContent = failure || (shownAside && figures ? figures.period : summary.line);
|
|
279
|
-
const key = JSON.stringify([cell, gap, weeks, across, dayOf(today), byWords, days, failure, language, aside.offsetWidth, under]);
|
|
350
|
+
// The room the squares have: the field's, the legend's taken off (a strip's height says how large they are).
|
|
351
|
+
const room = { width: field.clientWidth || 600, height: field.clientHeight > 0 ? field.clientHeight - legend.offsetHeight : Infinity };
|
|
352
|
+
const { pitch, cell, gap, weeks } = fitOf(layout, room.width, room.height);
|
|
353
|
+
const summary = summaryOf(days ?? [], byWords, weeks, today);
|
|
354
|
+
const exported = context.place === "export" && at;
|
|
355
|
+
const fresh = days !== null && summary.fresh;
|
|
356
|
+
face.toggleAttribute("data-fresh", fresh);
|
|
357
|
+
face.toggleAttribute("data-unread", Boolean(failure));
|
|
358
|
+
const key = JSON.stringify([layout, pitch, weeks, dayOf(today), byWords, days, failure, at]);
|
|
280
359
|
if (key === drawn) return;
|
|
281
360
|
drawn = key;
|
|
361
|
+
hide(); // the squares drawn anew: the one it stood over is gone
|
|
362
|
+
// The head's meta.
|
|
363
|
+
const [bigValue, bigUnit] = bigOf(summary.total, byWords);
|
|
364
|
+
const atWhen = exported ? (() => { const when = new Date(at); return `${words.date(when)} ${two(when.getHours())}:${two(when.getMinutes())}`; })() : "";
|
|
365
|
+
if (failure) meta.textContent = "";
|
|
366
|
+
else if (layout === "strip") {
|
|
367
|
+
const [first, second] = words.stripMeta(weeks >= 52 ? words.year : words.weeks(weeks), byWords ? `${bigValue} ${bigUnit.trim()}` : words.totalEdits(format(summary.total)), summary.active);
|
|
368
|
+
const dot = made("span", "dot", "·");
|
|
369
|
+
meta.replaceChildren(first, dot, second);
|
|
370
|
+
} else if (layout === "compact") meta.textContent = words.weeks(weeks) + (byWords ? ` · ${words.byWords}` : "");
|
|
371
|
+
else meta.textContent = fresh ? words.fresh : exported ? words.upTo(atWhen) : byWords ? words.byWords : "";
|
|
372
|
+
// The figures.
|
|
373
|
+
totalNumber.textContent = failure ? "—" : bigValue;
|
|
374
|
+
totalUnit.textContent = failure ? "" : bigUnit;
|
|
375
|
+
if (fresh && summary.since) {
|
|
376
|
+
period.textContent = words.freshSince(words.date(new Date(`${summary.since}T12:00:00`)));
|
|
377
|
+
average.replaceChildren();
|
|
378
|
+
// Its clauses each whole on a line where one fits (style.css), so that it breaks after the comma.
|
|
379
|
+
const said = made("p", "fresh");
|
|
380
|
+
said.append(...words.freshSaid.map((clause) => made("span", "clause", clause)));
|
|
381
|
+
facts.replaceChildren(said);
|
|
382
|
+
} else {
|
|
383
|
+
period.textContent = layout === "compact" ? "" : `${words.weeks(weeks)}${byWords ? words.added : ""}`;
|
|
384
|
+
const [before, value, afterwards] = words.average(byWords ? format(Math.round(summary.average)) : summary.average.toFixed(1), byWords);
|
|
385
|
+
average.replaceChildren(before, made("b", "strong", value), afterwards);
|
|
386
|
+
const todayName = exported ? words.date(new Date(at)) : words.today;
|
|
387
|
+
facts.replaceChildren(
|
|
388
|
+
kv(words.active, String(summary.active), words.days),
|
|
389
|
+
kv(words.run, String(summary.run), words.days),
|
|
390
|
+
kv(todayName, byWords ? format(summary.today) : String(summary.today), byWords ? words.wordsUnit : words.times),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
if (layout === "compact") average.replaceChildren(words.activeDays(summary.active));
|
|
394
|
+
const both = summary.you + summary.agent;
|
|
395
|
+
const youShare = both > 0 ? Math.round((100 * summary.you) / both) : 0;
|
|
396
|
+
youBar.style.flex = String(summary.you || (both ? 0 : 1));
|
|
397
|
+
agentBar.style.flex = String(summary.agent);
|
|
398
|
+
const label = (/** @type {string} */ kind, /** @type {string} */ name, /** @type {number} */ share) => {
|
|
399
|
+
const piece = made("span", kind);
|
|
400
|
+
piece.append(made("i", "swatch"), `${name} `, made("b", "strong", `${share}%`));
|
|
401
|
+
return piece;
|
|
402
|
+
};
|
|
403
|
+
whoWords.replaceChildren(label("you", words.you, youShare), label("agent", words.agent, both > 0 ? 100 - youShare : 0));
|
|
404
|
+
who.hidden = both === 0 || fresh;
|
|
405
|
+
// The legend's note: since when the record is kept, where days before it are drawn.
|
|
406
|
+
const columns = weeksOf(today, weeks);
|
|
407
|
+
const firstDay = columns[0][0].day;
|
|
408
|
+
const before = days !== null && summary.since !== null && summary.since > firstDay;
|
|
409
|
+
note.replaceChildren(...(before && layout !== "compact" ? [made("s", "dot-key"), layout === "strip" ? words.beforeSince(words.date(new Date(`${summary.since}T12:00:00`))) : words.before] : []));
|
|
410
|
+
overlay.hidden = !failure;
|
|
411
|
+
overlay.textContent = failure;
|
|
412
|
+
// The squares.
|
|
282
413
|
face.style.setProperty("--gallery-heatmap-cell", `${cell}px`);
|
|
283
414
|
face.style.setProperty("--gallery-heatmap-gap", `${gap}px`);
|
|
284
|
-
face.style.setProperty("--gallery-heatmap-across", `${across}px`);
|
|
285
415
|
face.style.setProperty("--gallery-heatmap-weeks", String(weeks));
|
|
286
|
-
face.toggleAttribute("data-small", cell < 6);
|
|
287
|
-
const columns = weeksOf(today, weeks);
|
|
288
|
-
const most = Math.max(0, ...[...summary.values.values()]);
|
|
289
416
|
const byDate = new Map((days ?? []).map((item) => [item.date, item]));
|
|
290
417
|
grid.replaceChildren(...columns.flatMap((column) => column.map(({ date, day, future }) => {
|
|
291
418
|
const square = made("span", "day");
|
|
@@ -293,36 +420,71 @@ DocKit.component({
|
|
|
293
420
|
square.classList.add("gallery-heatmap-future");
|
|
294
421
|
return square;
|
|
295
422
|
}
|
|
423
|
+
square.dataset.date = day;
|
|
424
|
+
if (day === dayOf(today)) square.classList.add("gallery-heatmap-today");
|
|
296
425
|
const recorded = days !== null && summary.since !== null && day >= summary.since;
|
|
297
426
|
if (!recorded) {
|
|
298
427
|
square.classList.add("gallery-heatmap-before");
|
|
299
|
-
square.
|
|
428
|
+
square.setAttribute("aria-label", `${words.weekday(date)} · ${days === null ? failure : words.unrecorded}`);
|
|
300
429
|
return square;
|
|
301
430
|
}
|
|
302
431
|
const entry = byDate.get(day);
|
|
303
|
-
square.dataset.level = String(levelOf(summary.values.get(day) ?? 0,
|
|
304
|
-
square.
|
|
432
|
+
square.dataset.level = String(levelOf(summary.values.get(day) ?? 0, summary.levels));
|
|
433
|
+
square.setAttribute("aria-label", `${words.tip(words.weekday(date), entry?.edits ?? 0, entry?.words ?? 0, byWords)}(${words.split(entry?.reader?.edits ?? 0, entry?.agent?.edits ?? 0)})`);
|
|
305
434
|
return square;
|
|
306
435
|
})));
|
|
307
|
-
// A month's name over the week its first day falls in
|
|
308
|
-
//
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
months.replaceChildren(...
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const label = made("span", "month", words.month(first.date.getMonth()));
|
|
316
|
-
label.style.setProperty("--gallery-heatmap-week", String(index));
|
|
317
|
-
return [label];
|
|
436
|
+
// A month's name over the week its first day falls in (the first week's too, but where the next is
|
|
437
|
+
// fewer than three weeks after it).
|
|
438
|
+
const starts = columns.flatMap((column, index) => (index === 0 || column[0].date.getMonth() !== columns[index - 1][0].date.getMonth() ? [index] : []));
|
|
439
|
+
if (starts.length > 1 && starts[1] < 3) starts.shift();
|
|
440
|
+
months.replaceChildren(...starts.map((index) => {
|
|
441
|
+
const name = made("span", "month", words.month(columns[index][0].date.getMonth()));
|
|
442
|
+
name.style.setProperty("--gallery-heatmap-week", String(index));
|
|
443
|
+
return name;
|
|
318
444
|
}));
|
|
445
|
+
// The squares alone: the months' row is the card's first, and a name that would reach into the grip's corner is left out.
|
|
446
|
+
if (layout === "grid") {
|
|
447
|
+
const end = face.getBoundingClientRect().right - gripRoom.offsetWidth;
|
|
448
|
+
for (const name of [...months.children]) if (name.getBoundingClientRect().right > end) name.remove();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
// A pointer on a square: its day, its count and whose, over it (under it where the window has no room
|
|
452
|
+
// above), its middle at the square's but held 8px inside the window, the arrow at the square.
|
|
453
|
+
const hide = () => {
|
|
454
|
+
if (popover) {
|
|
455
|
+
if (tip.matches(":popover-open")) tip.hidePopover();
|
|
456
|
+
} else tip.hidden = true;
|
|
457
|
+
};
|
|
458
|
+
const show = (/** @type {Event} */ event) => {
|
|
459
|
+
const square = /** @type {HTMLElement | null} */ (/** @type {HTMLElement} */ (event.target).closest?.(".gallery-heatmap-day[data-date]"));
|
|
460
|
+
if (!square || !grid.contains(square)) return hide();
|
|
461
|
+
const [first, second] = (square.getAttribute("aria-label") ?? "").replace(/)$/, "").split("(");
|
|
462
|
+
tip.replaceChildren(first, ...(second ? [made("small", "tip-split", second)] : []));
|
|
463
|
+
if (popover) {
|
|
464
|
+
if (!tip.matches(":popover-open")) tip.showPopover();
|
|
465
|
+
} else tip.hidden = false;
|
|
466
|
+
const box = square.getBoundingClientRect();
|
|
467
|
+
const width = tip.offsetWidth;
|
|
468
|
+
const height = tip.offsetHeight;
|
|
469
|
+
const left = Math.max(8, Math.min(innerWidth - 8 - width, box.left + box.width / 2 - width / 2));
|
|
470
|
+
const below = box.top - 8 - height < 8;
|
|
471
|
+
tip.style.left = `${Math.round(left)}px`;
|
|
472
|
+
tip.style.top = `${Math.round(below ? box.bottom + 8 : box.top - 8 - height)}px`;
|
|
473
|
+
tip.style.setProperty("--gallery-heatmap-arrow", `${Math.round(Math.max(10, Math.min(width - 10, box.left + box.width / 2 - left)))}px`);
|
|
474
|
+
tip.toggleAttribute("data-below", below);
|
|
319
475
|
};
|
|
476
|
+
context.listen(grid, "pointerover", show);
|
|
477
|
+
context.listen(grid, "pointerleave", hide);
|
|
478
|
+
// A scroll (the page's, or a card's) moves the square from under it: gone until the pointer is on one again.
|
|
479
|
+
context.listen(document, "scroll", hide, { capture: true, passive: true });
|
|
480
|
+
context.signal?.addEventListener("abort", hide, { once: true });
|
|
320
481
|
let asking = 0; // the last read asked: an older answer that comes after it is dropped
|
|
321
482
|
const read = () => {
|
|
322
483
|
const ticket = ++asking;
|
|
323
|
-
workspace.query("activity", { days: DAYS }).then(({ data }) => {
|
|
484
|
+
workspace.query("activity", { days: DAYS }).then(({ data, at: when }) => {
|
|
324
485
|
if (ticket !== asking || context.signal.aborted) return;
|
|
325
486
|
days = data;
|
|
487
|
+
at = typeof when === "string" ? when : "";
|
|
326
488
|
failure = "";
|
|
327
489
|
draw();
|
|
328
490
|
}, (/** @type {any} */ error) => {
|
|
@@ -334,8 +496,8 @@ DocKit.component({
|
|
|
334
496
|
};
|
|
335
497
|
draw();
|
|
336
498
|
read();
|
|
337
|
-
// The room the squares have, its height too
|
|
338
|
-
//
|
|
499
|
+
// The room the squares have, its height too: a card given its height after the first draw (a board laid
|
|
500
|
+
// out late) is drawn again as it gets it.
|
|
339
501
|
if (typeof ResizeObserver === "function") {
|
|
340
502
|
const watch = new ResizeObserver(() => draw());
|
|
341
503
|
watch.observe(body);
|