@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,527 @@
|
|
|
1
|
+
// gallery-soft/todos (我的待办, 柔光风格): Gallery 风格's gallery/todos in the soft look, the same logic. The
|
|
2
|
+
// to-dos of the workspace's documents (each item of a to-do list, ul.doc-todo), those not done yet, read
|
|
3
|
+
// through context.workspace.query("todos") (its manifest's needs hold "workspace"), grouped by document, the
|
|
4
|
+
// one edited last first, each group's items in its document's order, each with the headings it stands under.
|
|
5
|
+
// Its box checks the item in its document (context.workspace.setTodo: the server writes that document as the
|
|
6
|
+
// reader's edit, and the home page's undo checks it back), drawn checked at once, put back with a line saying
|
|
7
|
+
// why where the write is refused (the item changed meanwhile, several items alike, one the document's own
|
|
8
|
+
// page cannot check, the document being written, no server); an item checked here stays in the list, struck
|
|
9
|
+
// through, until the page is shown again, so that a slip is undone where it was made, and its group stays
|
|
10
|
+
// where it is (once a box is checked here the groups keep the order they were drawn in, a document new to the
|
|
11
|
+
// card first). A click on an item's words opens its document there, at the item's block where it has an id,
|
|
12
|
+
// else at the document (context.workspace.open); a click on a group's title opens the document. It reads
|
|
13
|
+
// again whenever the workspace says its to-dos changed (the `data` signal, "todos") and when its settings do
|
|
14
|
+
// (`source`). Its two settings, the card's form (the grip at its top right › 设置…), are kept in its data part
|
|
15
|
+
// `settings`, which the board writes and this block only reads: `document` (a document's slug: its to-dos
|
|
16
|
+
// alone) and `done` (the done ones too, checked and struck through). Where the workspace cannot be read (an
|
|
17
|
+
// exported file, where it has no server; a server that does not answer) it says so; where nothing writes it
|
|
18
|
+
// (an exported file: no setTodo) its boxes are only drawn. The words are the page's language.
|
|
19
|
+
//
|
|
20
|
+
// Each group's title has its document's icon before it, as the sidebar draws it, read from the documents
|
|
21
|
+
// edited last (workspace.query("recent"), the 50 the workspace gives; a document none of them is gets a
|
|
22
|
+
// page's picture), drawn from the server's icon route as Gallery's sidebar draws one.
|
|
23
|
+
//
|
|
24
|
+
// Its face, laid out by its card's size (`context.size`, read again on `layout`; data-w, data-h, data-pad): a
|
|
25
|
+
// head of a checked list's chip and 我的待办, at its end how many are done of all (a 4 × 4 card: a small ring
|
|
26
|
+
// and 18 / 27; a 4 × 6 card: 共 27 项; an 8 × 4 card: how many are open and in how many documents); a tall card
|
|
27
|
+
// under it the share done as a ring with its percent in the middle and a key of the two counts, an 8 × 4 card
|
|
28
|
+
// the same down its left side and the groups in two columns beside it; the list of whole rows only (`fit`), a
|
|
29
|
+
// muted line under it saying how many more are below (还有 4 项), counted again as it scrolls or the card
|
|
30
|
+
// changes size. All done: the ring full in green, 待办都完成了 under it. The face says by `data-state` what it
|
|
31
|
+
// shows: items, done (every one done), none or unread.
|
|
32
|
+
|
|
33
|
+
const WORDS = {
|
|
34
|
+
zh: {
|
|
35
|
+
title: "我的待办",
|
|
36
|
+
empty: "所有文档里的待办会出现在这里",
|
|
37
|
+
emptyHow: "在文档里输入 [] 就是一个待办",
|
|
38
|
+
allDone: "待办都完成了",
|
|
39
|
+
allDoneHow: (/** @type {number} */ count, /** @type {number} */ documents) => `${count} 项,分在 ${documents} 篇文档里`,
|
|
40
|
+
noneIn: "这篇文档里没有待办",
|
|
41
|
+
noneInHow: "在这篇文档里输入 [] 就是一个待办",
|
|
42
|
+
offline: "在 Gallery 里打开时显示待办",
|
|
43
|
+
offlineHow: "导出的文件读不到工作区",
|
|
44
|
+
failed: "读不到工作区的待办",
|
|
45
|
+
failedHow: "文档一有改动就会再读",
|
|
46
|
+
open: (/** @type {string} */ title) => `打开「${title}」`,
|
|
47
|
+
path: (/** @type {string[]} */ path) => path.join(" › "),
|
|
48
|
+
check: "标记为完成",
|
|
49
|
+
uncheck: "标记为未完成",
|
|
50
|
+
done: "已完成",
|
|
51
|
+
left: "没做完",
|
|
52
|
+
share: (/** @type {number} */ done, /** @type {number} */ all) => `${all} 项待办完成了 ${done} 项`,
|
|
53
|
+
all: (/** @type {number} */ count) => `共 ${count} 项`,
|
|
54
|
+
openIn: (/** @type {number} */ count, /** @type {number} */ documents) => [`${count} 项没做完`, `${documents} 篇文档`],
|
|
55
|
+
more: (/** @type {number} */ count) => `还有 ${count} 项`,
|
|
56
|
+
refused: {
|
|
57
|
+
changed: "这一项刚被改过,已重新读取",
|
|
58
|
+
ambiguous: "文档里有几项一样的待办,请在文档里勾选",
|
|
59
|
+
locked: "这一项在文档里是只读的,不能勾选",
|
|
60
|
+
busy: "文档正在被修改,请稍后再勾选",
|
|
61
|
+
offline: "在 Gallery 里打开时才能勾选",
|
|
62
|
+
missing: "找不到这篇文档",
|
|
63
|
+
failed: "没能勾选这一项",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
en: {
|
|
67
|
+
title: "My to-dos",
|
|
68
|
+
empty: "The to-dos of every document will show here",
|
|
69
|
+
emptyHow: "Type [] in a document for a to-do",
|
|
70
|
+
allDone: "Every to-do is done",
|
|
71
|
+
allDoneHow: (/** @type {number} */ count, /** @type {number} */ documents) => `${count} in ${documents} ${documents === 1 ? "document" : "documents"}`,
|
|
72
|
+
noneIn: "This document has no to-dos",
|
|
73
|
+
noneInHow: "Type [] in it for a to-do",
|
|
74
|
+
offline: "Shown when opened in Gallery",
|
|
75
|
+
offlineHow: "An exported file can't read the workspace",
|
|
76
|
+
failed: "The workspace's to-dos could not be read",
|
|
77
|
+
failedHow: "It reads again as soon as a document changes",
|
|
78
|
+
open: (/** @type {string} */ title) => `Open “${title}”`,
|
|
79
|
+
path: (/** @type {string[]} */ path) => path.join(" › "),
|
|
80
|
+
check: "Mark as done",
|
|
81
|
+
uncheck: "Mark as not done",
|
|
82
|
+
done: "Done",
|
|
83
|
+
left: "Open",
|
|
84
|
+
share: (/** @type {number} */ done, /** @type {number} */ all) => `${done} of ${all} to-dos done`,
|
|
85
|
+
all: (/** @type {number} */ count) => `${count} in all`,
|
|
86
|
+
openIn: (/** @type {number} */ count, /** @type {number} */ documents) => [`${count} open`, `${documents} ${documents === 1 ? "document" : "documents"}`],
|
|
87
|
+
more: (/** @type {number} */ count) => `${count} more`,
|
|
88
|
+
refused: {
|
|
89
|
+
changed: "This to-do just changed: read again",
|
|
90
|
+
ambiguous: "The document has several to-dos alike: check it in the document",
|
|
91
|
+
locked: "This to-do is read-only in its document: it cannot be checked",
|
|
92
|
+
busy: "The document is being written: try again in a moment",
|
|
93
|
+
offline: "Open it in Gallery to check it",
|
|
94
|
+
missing: "The document is not there",
|
|
95
|
+
failed: "The to-do could not be checked",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** How far a row may run past the list's bottom and still be whole: the empty bottom of its 28px box, under its 16px box and words. */
|
|
101
|
+
const SLACK = 6;
|
|
102
|
+
|
|
103
|
+
/** How long a line saying why a check was refused stays, in ms. */
|
|
104
|
+
const SAID_FOR = 4000;
|
|
105
|
+
|
|
106
|
+
/** The language the page speaks: the UI's where Gallery says it (data-ge-language), else its lang's. */
|
|
107
|
+
function languageOf(/** @type {HTMLElement} */ root) {
|
|
108
|
+
const ui = root.getAttribute("data-ge-language");
|
|
109
|
+
return (ui !== null ? ui === "en" : /^en(?:-|$)/i.test(root.lang)) ? "en" : "zh";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* What the card's settings ask (context.readJSON's, {} where none is set yet): { document, done }, the
|
|
114
|
+
* slug of the one document to read (null: every one) and whether the done ones show too.
|
|
115
|
+
* @param {any} settings
|
|
116
|
+
*/
|
|
117
|
+
function askedOf(settings) {
|
|
118
|
+
const table = settings !== null && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
119
|
+
return { document: typeof table.document === "string" && table.document ? table.document : null, done: table.done === true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @typedef {{ ref: DocTodoRef, text: string, done: boolean, slug: string, title: string, path: string[] }} Todo */
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* An item by its document, its headings and its words: what names it while the card lives, whether it has
|
|
126
|
+
* an id yet or not (its check gives it one).
|
|
127
|
+
* @param {Todo} todo
|
|
128
|
+
*/
|
|
129
|
+
const itemKey = (todo) => JSON.stringify([todo.slug, todo.path, todo.text]);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The to-dos (workspace.query("todos")'s data) as the card shows them, of `asked`: { groups, open,
|
|
133
|
+
* all }, groups [{ slug, title, items }] in the order the documents come (the one edited last first),
|
|
134
|
+
* items those shown (not done, or all with `asked.done`, and the ones checked on this card: `kept`);
|
|
135
|
+
* `open` how many are not done, `all` how many there are of the document asked or of the workspace.
|
|
136
|
+
* `order` (the slugs as drawn before, or null): the groups keep that order, a document not in it before them.
|
|
137
|
+
* @param {Todo[]} todos
|
|
138
|
+
* @param {{ document: string | null, done: boolean }} asked
|
|
139
|
+
* @param {Set<string>} kept
|
|
140
|
+
* @param {string[] | null} [order]
|
|
141
|
+
*/
|
|
142
|
+
function groupsOf(todos, asked, kept, order = null) {
|
|
143
|
+
const mine = todos.filter((todo) => asked.document === null || todo.slug === asked.document);
|
|
144
|
+
/** @type {Map<string, { slug: string, title: string, items: Todo[] }>} */
|
|
145
|
+
const groups = new Map();
|
|
146
|
+
for (const todo of mine) {
|
|
147
|
+
if (todo.done && !asked.done && !kept.has(itemKey(todo))) continue;
|
|
148
|
+
if (!groups.has(todo.slug)) groups.set(todo.slug, { slug: todo.slug, title: todo.title, items: [] });
|
|
149
|
+
groups.get(todo.slug)?.items.push(todo);
|
|
150
|
+
}
|
|
151
|
+
const placed = [...groups.values()];
|
|
152
|
+
if (order) placed.sort((a, b) => order.indexOf(a.slug) - order.indexOf(b.slug)); // stable: those not in it keep the order they came in
|
|
153
|
+
return { groups: placed, open: mine.filter((todo) => !todo.done).length, all: mine.length, documents: new Set(mine.map((todo) => todo.slug)).size };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The line the card says where it shows no item, or null where it shows some: none in the workspace,
|
|
158
|
+
* none in the one document asked, all done.
|
|
159
|
+
* @param {{ groups: unknown[], all: number }} shown
|
|
160
|
+
* @param {{ document: string | null }} asked
|
|
161
|
+
* @param {"zh" | "en"} language
|
|
162
|
+
*/
|
|
163
|
+
function emptyLine(shown, asked, language) {
|
|
164
|
+
if (shown.groups.length > 0) return null;
|
|
165
|
+
const words = WORDS[language];
|
|
166
|
+
if (shown.all > 0) return words.allDone;
|
|
167
|
+
return asked.document === null ? words.empty : words.noneIn;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** What a document's icon is, as Gallery writes its value: a glyph (/icons/…), a picture of its folder (assets/…), an emoji, or none. @param {string | null | undefined} value */
|
|
171
|
+
function iconKindOf(value) {
|
|
172
|
+
if (typeof value !== "string" || !value) return null;
|
|
173
|
+
if (value.startsWith("/icons/")) return "glyph";
|
|
174
|
+
if (value.startsWith("assets/")) return "image";
|
|
175
|
+
return "emoji";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The size the face is laid out by: its card's cells (`context.size`), or `fallback` (the manifest's first
|
|
180
|
+
* size) where no card is; and the frame's padding for it, as the soft cards pad theirs: "xs" on a card one
|
|
181
|
+
* cell high, "s" on a small one (2 × 2 and less), "l" on one 3 cells high or more, else "m".
|
|
182
|
+
* @param {DocComponentSize | null | undefined} size
|
|
183
|
+
* @param {[number, number]} fallback
|
|
184
|
+
*/
|
|
185
|
+
function sizeOf(size, fallback) {
|
|
186
|
+
const w = size?.w ?? fallback[0];
|
|
187
|
+
const h = size?.h ?? fallback[1];
|
|
188
|
+
return { w, h, pad: h === 1 ? "xs" : w <= 2 && h <= 2 ? "s" : h >= 3 ? "l" : "m" };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* An element of the face, of `kind` (its class).
|
|
193
|
+
* @template {keyof HTMLElementTagNameMap} K
|
|
194
|
+
* @param {K} tag
|
|
195
|
+
* @param {string} kind
|
|
196
|
+
* @param {string} [text]
|
|
197
|
+
*/
|
|
198
|
+
function made(tag, kind, text) {
|
|
199
|
+
const node = document.createElement(tag);
|
|
200
|
+
node.className = `gallery-soft-todos-${kind}`;
|
|
201
|
+
if (text !== undefined) node.textContent = text;
|
|
202
|
+
return node;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const CHECKS = '<path d="M4 7l1.8 1.8L9.2 5.4"/><path d="M4 14.5l1.8 1.8 3.4-3.4"/><path d="M12.5 7.5H20M12.5 15H20"/>';
|
|
206
|
+
const line = (/** @type {string} */ paths, /** @type {number} */ width = 1.8) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${width}" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A ring of `share` (0 to 1) done, `size` px across, its line `stroke` px, what `inner` holds in its middle.
|
|
210
|
+
* @param {number} size
|
|
211
|
+
* @param {number} stroke
|
|
212
|
+
* @param {number} share
|
|
213
|
+
* @param {Node[]} inner
|
|
214
|
+
*/
|
|
215
|
+
function ringOf(size, stroke, share, inner = []) {
|
|
216
|
+
const ring = made("span", "ring");
|
|
217
|
+
const radius = (size - stroke) / 2;
|
|
218
|
+
const round = 2 * Math.PI * radius;
|
|
219
|
+
ring.style.width = `${size}px`;
|
|
220
|
+
ring.style.height = `${size}px`;
|
|
221
|
+
ring.innerHTML = `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" aria-hidden="true"><circle class="gallery-soft-todos-track" cx="${size / 2}" cy="${size / 2}" r="${radius}" stroke-width="${stroke}"/><circle class="gallery-soft-todos-arc" cx="${size / 2}" cy="${size / 2}" r="${radius}" stroke-width="${stroke}" stroke-dasharray="${(round * share).toFixed(2)} ${round.toFixed(2)}" transform="rotate(-90 ${size / 2} ${size / 2})"/></svg>`;
|
|
222
|
+
ring.append(...inner);
|
|
223
|
+
return ring;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* How much of `all` is done, as a whole percent, as every soft card says it (the to-dos' ring, the stats'
|
|
228
|
+
* bar): rounded, but 100 only when all are done and 0 only when none is (18 of 27: 67; 199 of 200: 99;
|
|
229
|
+
* 1 of 300: 1).
|
|
230
|
+
* @param {number} done
|
|
231
|
+
* @param {number} all
|
|
232
|
+
*/
|
|
233
|
+
function percentDone(done, all) {
|
|
234
|
+
if (!(all > 0) || !(done > 0)) return 0;
|
|
235
|
+
if (done >= all) return 100;
|
|
236
|
+
return Math.min(99, Math.max(1, Math.round((100 * done) / all)));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** A percent as the ring says it in its middle, its sign small and muted. @param {number} value */
|
|
240
|
+
function percentOf(value) {
|
|
241
|
+
const node = made("span", "percent", String(value));
|
|
242
|
+
node.append(made("small", "percent-sign", "%"));
|
|
243
|
+
return node;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
DocKit.component({
|
|
247
|
+
id: "gallery-soft/todos",
|
|
248
|
+
on: ["source", "data", "layout"],
|
|
249
|
+
mount(host, context) {
|
|
250
|
+
const language = languageOf(document.documentElement);
|
|
251
|
+
const words = WORDS[language];
|
|
252
|
+
const face = made("div", "face");
|
|
253
|
+
const head = made("div", "head");
|
|
254
|
+
const count = made("span", "count");
|
|
255
|
+
const chip = made("span", "chip");
|
|
256
|
+
chip.setAttribute("aria-hidden", "true");
|
|
257
|
+
chip.innerHTML = line(CHECKS);
|
|
258
|
+
head.append(chip, made("span", "title", words.title), count);
|
|
259
|
+
// How many are done of all: a ring of the share done and a key of the two counts (a tall or a wide card's).
|
|
260
|
+
const summary = made("div", "summary");
|
|
261
|
+
summary.setAttribute("role", "img");
|
|
262
|
+
const list = made("div", "list");
|
|
263
|
+
const more = made("p", "more");
|
|
264
|
+
const arrow = made("span", "arrow");
|
|
265
|
+
arrow.innerHTML = line('<path d="M12 5v14M6.5 13.5L12 19l5.5-5.5"/>');
|
|
266
|
+
const moreWords = made("span", "more-words");
|
|
267
|
+
more.append(arrow, moreWords);
|
|
268
|
+
more.hidden = true;
|
|
269
|
+
const note = made("p", "note");
|
|
270
|
+
note.hidden = true;
|
|
271
|
+
note.setAttribute("role", "status");
|
|
272
|
+
const empty = made("div", "empty");
|
|
273
|
+
face.append(head, summary, list, more, note, empty);
|
|
274
|
+
host.append(face);
|
|
275
|
+
const workspace = context.workspace;
|
|
276
|
+
if (!workspace) return void context.fail(new Error("its manifest's needs hold \"workspace\": context.workspace is not there"));
|
|
277
|
+
const open = (/** @type {{ slug: string, block?: string | null }} */ target) => workspace.open(target);
|
|
278
|
+
const setTodo = workspace.setTodo;
|
|
279
|
+
const kept = new Set(); // the items checked on this card, shown until the page is shown again
|
|
280
|
+
/** @type {Todo[] | null} */
|
|
281
|
+
let last = null; // what was read last, as drawn (a check drawn into it at once)
|
|
282
|
+
/** @type {{ document: string | null, done: boolean }} */
|
|
283
|
+
let lastAsked = { document: null, done: false };
|
|
284
|
+
/** @type {string[]} */
|
|
285
|
+
let order = []; // the documents of the groups as drawn
|
|
286
|
+
/** @type {string[] | null} */
|
|
287
|
+
let held = null; // that order as it was when the first box was checked here: the groups keep it from then on
|
|
288
|
+
/** @type {Map<string, string | null>} */
|
|
289
|
+
let icons = new Map(); // each document's icon, of those edited last
|
|
290
|
+
let failure = ""; // why nothing could be read, or ""
|
|
291
|
+
|
|
292
|
+
let noteTimer = 0;
|
|
293
|
+
const tell = (/** @type {string} */ said) => {
|
|
294
|
+
note.textContent = said;
|
|
295
|
+
note.hidden = false;
|
|
296
|
+
clearTimeout(noteTimer);
|
|
297
|
+
noteTimer = context.timeout(() => { note.hidden = true; }, SAID_FOR);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// An item's box checked or cleared: drawn so at once, then written in its document; a refusal puts it
|
|
301
|
+
// back, says why and reads again.
|
|
302
|
+
const check = (/** @type {Todo} */ todo) => {
|
|
303
|
+
if (!setTodo || !last) return;
|
|
304
|
+
const done = !todo.done;
|
|
305
|
+
const drawn = { ...todo, done };
|
|
306
|
+
kept.add(itemKey(todo));
|
|
307
|
+
held ??= order;
|
|
308
|
+
last = last.map((one) => (one === todo ? drawn : one));
|
|
309
|
+
draw();
|
|
310
|
+
setTodo(todo.ref, done).catch((/** @type {any} */ error) => {
|
|
311
|
+
if (context.signal.aborted) return;
|
|
312
|
+
if (last?.includes(drawn)) {
|
|
313
|
+
last = last.map((one) => (one === drawn ? todo : one));
|
|
314
|
+
draw();
|
|
315
|
+
}
|
|
316
|
+
const refused = /** @type {Record<string, string>} */ (words.refused);
|
|
317
|
+
tell(refused[error?.code] ?? refused.failed);
|
|
318
|
+
read();
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// A document's icon: its emoji, its picture from the server's icon route, a glyph masked, or a page's picture.
|
|
323
|
+
const iconOf = (/** @type {string} */ slug) => {
|
|
324
|
+
const value = icons.get(slug) ?? null;
|
|
325
|
+
const kind = iconKindOf(value);
|
|
326
|
+
const icon = made("span", "icon");
|
|
327
|
+
icon.setAttribute("aria-hidden", "true");
|
|
328
|
+
if (!kind || value === null) {
|
|
329
|
+
icon.classList.add("gallery-soft-todos-plain");
|
|
330
|
+
icon.innerHTML = line('<path d="M7 3.5h10a1.5 1.5 0 0 1 1.5 1.5v14a1.5 1.5 0 0 1-1.5 1.5H7A1.5 1.5 0 0 1 5.5 19V5A1.5 1.5 0 0 1 7 3.5z"/><path d="M9 8.5h6M9 12h6M9 15.5h3.5"/>');
|
|
331
|
+
return icon;
|
|
332
|
+
}
|
|
333
|
+
if (kind === "emoji") {
|
|
334
|
+
icon.textContent = value;
|
|
335
|
+
return icon;
|
|
336
|
+
}
|
|
337
|
+
const address = `/__gallery/edit/icon?value=${encodeURIComponent(value)}&slug=${encodeURIComponent(slug)}`;
|
|
338
|
+
if (kind === "glyph") {
|
|
339
|
+
icon.classList.add("gallery-soft-todos-glyph");
|
|
340
|
+
icon.style.setProperty("--gallery-soft-todos-icon", `url("${address}")`);
|
|
341
|
+
return icon;
|
|
342
|
+
}
|
|
343
|
+
const picture = document.createElement("img");
|
|
344
|
+
picture.alt = "";
|
|
345
|
+
picture.src = address;
|
|
346
|
+
picture.decoding = "async";
|
|
347
|
+
icon.append(picture);
|
|
348
|
+
return icon;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// How many items are below what the list shows (one cut off counted), said under it, or nothing.
|
|
352
|
+
const recount = () => {
|
|
353
|
+
const bottom = list.scrollTop + list.clientHeight;
|
|
354
|
+
let below = 0;
|
|
355
|
+
for (const item of /** @type {NodeListOf<HTMLElement>} */ (list.querySelectorAll(".gallery-soft-todos-item"))) if (item.offsetTop + item.offsetHeight > bottom + SLACK) below++;
|
|
356
|
+
const said = below > 0 ? words.more(below) : "";
|
|
357
|
+
if (moreWords.textContent !== said) moreWords.textContent = said;
|
|
358
|
+
more.hidden = below === 0;
|
|
359
|
+
};
|
|
360
|
+
// The list as high as the whole rows that fit the room it has (the line of more under it where they are
|
|
361
|
+
// not all shown): its height the bottom of the last item no row or group's title is cut at.
|
|
362
|
+
const fit = () => {
|
|
363
|
+
list.style.maxHeight = "";
|
|
364
|
+
more.hidden = true;
|
|
365
|
+
if (list.clientHeight === 0 || list.scrollHeight <= list.clientHeight + 1) return void recount();
|
|
366
|
+
moreWords.textContent = words.more(0);
|
|
367
|
+
more.hidden = false;
|
|
368
|
+
const room = list.clientHeight;
|
|
369
|
+
const rows = /** @type {HTMLElement[]} */ ([...list.querySelectorAll(".gallery-soft-todos-item, .gallery-soft-todos-group-head")]);
|
|
370
|
+
const bottoms = rows.filter((row) => row.classList.contains("gallery-soft-todos-item")).map((row) => row.offsetTop + row.offsetHeight).filter((bottom) => bottom <= room + SLACK).sort((a, b) => b - a);
|
|
371
|
+
const whole = bottoms.find((bottom) => rows.every((row) => !(row.offsetTop < bottom - 0.5 && row.offsetTop + row.offsetHeight > bottom + 0.5)));
|
|
372
|
+
if (whole !== undefined && whole < room) list.style.maxHeight = `${whole}px`;
|
|
373
|
+
recount();
|
|
374
|
+
};
|
|
375
|
+
list.addEventListener("scroll", recount, { passive: true });
|
|
376
|
+
const watch = typeof ResizeObserver === "function" ? new ResizeObserver(() => fit()) : null;
|
|
377
|
+
watch?.observe(face);
|
|
378
|
+
context.signal?.addEventListener("abort", () => {
|
|
379
|
+
list.removeEventListener("scroll", recount);
|
|
380
|
+
watch?.disconnect();
|
|
381
|
+
}, { once: true });
|
|
382
|
+
|
|
383
|
+
// The empty face: a soft disc with the list's picture (or the ring full, all done), a line and what to do.
|
|
384
|
+
const showEmpty = (/** @type {string} */ said, /** @type {string} */ how, /** @type {"plain" | "bad" | "done"} */ look) => {
|
|
385
|
+
const disc = look === "done" ? ringOf(88, 8, 1, [percentOf(100)]) : made("span", "disc");
|
|
386
|
+
if (look === "done") disc.classList.add("gallery-soft-todos-full");
|
|
387
|
+
else disc.innerHTML = line(CHECKS);
|
|
388
|
+
disc.classList.toggle("gallery-soft-todos-bad", look === "bad");
|
|
389
|
+
empty.replaceChildren(disc, made("p", "line", said), ...(how ? [made("p", "how", how)] : []));
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// The list drawn anew of what was read, scrolled where it was.
|
|
393
|
+
const draw = () => {
|
|
394
|
+
const { w, h, pad } = sizeOf(context.size, [4, 4]);
|
|
395
|
+
const wide = w >= 6;
|
|
396
|
+
const tall = !wide && h >= 6;
|
|
397
|
+
Object.assign(face.dataset, { w: String(w), h: String(h), pad, layout: wide ? "wide" : tall ? "tall" : "square" });
|
|
398
|
+
if (!last) {
|
|
399
|
+
face.dataset.state = failure ? "unread" : "none";
|
|
400
|
+
count.replaceChildren();
|
|
401
|
+
summary.replaceChildren();
|
|
402
|
+
list.replaceChildren();
|
|
403
|
+
if (failure) showEmpty(failure === "offline" ? words.offline : words.failed, failure === "offline" ? words.offlineHow : words.failedHow, failure === "offline" ? "plain" : "bad");
|
|
404
|
+
else empty.replaceChildren();
|
|
405
|
+
return fit();
|
|
406
|
+
}
|
|
407
|
+
const asked = lastAsked;
|
|
408
|
+
const shown = groupsOf(last, asked, kept, held);
|
|
409
|
+
order = shown.groups.map((group) => group.slug);
|
|
410
|
+
const top = list.scrollTop;
|
|
411
|
+
const done = shown.all - shown.open;
|
|
412
|
+
const share = shown.all > 0 ? done / shown.all : 0;
|
|
413
|
+
const percent = () => [percentOf(percentDone(done, shown.all))];
|
|
414
|
+
face.dataset.state = shown.groups.length > 0 ? "items" : shown.all > 0 ? "done" : "none";
|
|
415
|
+
// The head's end: how many are done of all, as the card's size says it.
|
|
416
|
+
if (shown.all === 0) count.replaceChildren();
|
|
417
|
+
else if (face.dataset.state !== "items" || tall) count.replaceChildren(words.all(shown.all));
|
|
418
|
+
else if (wide) {
|
|
419
|
+
const [left, documents] = words.openIn(shown.open, shown.groups.length);
|
|
420
|
+
count.replaceChildren(left, made("span", "dot"), documents);
|
|
421
|
+
} else count.replaceChildren(ringOf(18, 3, share), made("b", "done-count", String(done)), made("span", "of", `/ ${shown.all}`));
|
|
422
|
+
summary.setAttribute("aria-label", words.share(done, shown.all));
|
|
423
|
+
if (face.dataset.state === "items" && (tall || wide)) {
|
|
424
|
+
const key = made("dl", "key");
|
|
425
|
+
for (const [kind, name, value] of [["done", words.done, done], ["left", words.left, shown.open]]) {
|
|
426
|
+
const row = made("div", "key-row");
|
|
427
|
+
row.dataset.kind = String(kind);
|
|
428
|
+
const term = made("dt", "key-name");
|
|
429
|
+
term.append(made("i", "key-dot"), String(name));
|
|
430
|
+
row.append(term, made("dd", "key-count", String(value)));
|
|
431
|
+
key.append(row);
|
|
432
|
+
}
|
|
433
|
+
summary.replaceChildren(wide ? ringOf(100, 9, share, percent()) : ringOf(64, 7, share, percent()), key);
|
|
434
|
+
} else summary.replaceChildren();
|
|
435
|
+
list.replaceChildren(...shown.groups.map((group) => {
|
|
436
|
+
const section = made("section", "group");
|
|
437
|
+
const title = made("div", "group-head");
|
|
438
|
+
const name = made("button", "doc", group.title);
|
|
439
|
+
name.type = "button";
|
|
440
|
+
name.title = words.open(group.title);
|
|
441
|
+
name.addEventListener("click", () => open({ slug: group.slug }));
|
|
442
|
+
title.append(iconOf(group.slug), name, made("span", "group-count", String(group.items.length)));
|
|
443
|
+
const items = made("ul", "items");
|
|
444
|
+
for (const todo of group.items) {
|
|
445
|
+
const item = made("li", "item");
|
|
446
|
+
if (todo.done) item.classList.add("gallery-soft-todos-done");
|
|
447
|
+
const button = made("button", "todo");
|
|
448
|
+
button.type = "button";
|
|
449
|
+
button.title = words.open(group.title);
|
|
450
|
+
const text = made("span", "text", todo.text);
|
|
451
|
+
if (setTodo) {
|
|
452
|
+
const box = made("button", "check");
|
|
453
|
+
box.type = "button";
|
|
454
|
+
box.setAttribute("role", "checkbox");
|
|
455
|
+
box.setAttribute("aria-checked", String(todo.done));
|
|
456
|
+
box.title = todo.done ? words.uncheck : words.check;
|
|
457
|
+
box.setAttribute("aria-label", `${todo.done ? words.uncheck : words.check}: ${todo.text}`);
|
|
458
|
+
const mark = made("span", "box");
|
|
459
|
+
mark.innerHTML = line('<path d="M5 12.5l4.3 4.3L19 7.3"/>', 2.8);
|
|
460
|
+
box.append(mark);
|
|
461
|
+
box.addEventListener("click", (event) => {
|
|
462
|
+
event.stopPropagation();
|
|
463
|
+
check(todo);
|
|
464
|
+
});
|
|
465
|
+
item.append(box);
|
|
466
|
+
button.append(text);
|
|
467
|
+
} else {
|
|
468
|
+
const mark = made("span", "box");
|
|
469
|
+
mark.innerHTML = line('<path d="M5 12.5l4.3 4.3L19 7.3"/>', 2.8);
|
|
470
|
+
button.append(mark, text);
|
|
471
|
+
}
|
|
472
|
+
// Its headings, but for the document's title the group's title above says (its first heading, as often); a wide card's two columns leave them out.
|
|
473
|
+
const path = todo.path[0] === group.title ? todo.path.slice(1) : todo.path;
|
|
474
|
+
if (path.length > 0 && !wide) button.append(made("span", "path", words.path(path)));
|
|
475
|
+
button.addEventListener("click", () => open({ slug: todo.ref.slug, block: todo.ref.block }));
|
|
476
|
+
item.append(button);
|
|
477
|
+
items.append(item);
|
|
478
|
+
}
|
|
479
|
+
section.append(title, items);
|
|
480
|
+
return section;
|
|
481
|
+
}));
|
|
482
|
+
list.scrollTop = top;
|
|
483
|
+
const said = emptyLine(shown, asked, language);
|
|
484
|
+
if (said === null) empty.replaceChildren();
|
|
485
|
+
else if (shown.all > 0) showEmpty(said, words.allDoneHow(shown.all, shown.documents), "done");
|
|
486
|
+
else showEmpty(said, asked.document === null ? words.emptyHow : words.noneInHow, "plain");
|
|
487
|
+
fit();
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
let asking = 0; // the last read asked: an older answer that comes after it is dropped
|
|
491
|
+
const read = () => {
|
|
492
|
+
const settings = context.readJSON("settings");
|
|
493
|
+
if (settings === undefined) return; // no JSON: the instance has failed, and Gallery shows what it holds
|
|
494
|
+
const asked = askedOf(settings);
|
|
495
|
+
const ticket = ++asking;
|
|
496
|
+
workspace.query("todos", { limit: 1000, ...(asked.document ? { slugs: [asked.document] } : {}) }).then(({ data }) => {
|
|
497
|
+
if (ticket !== asking || context.signal.aborted) return;
|
|
498
|
+
last = data;
|
|
499
|
+
lastAsked = asked;
|
|
500
|
+
failure = "";
|
|
501
|
+
draw();
|
|
502
|
+
}, (/** @type {any} */ error) => {
|
|
503
|
+
if (ticket !== asking || context.signal.aborted) return;
|
|
504
|
+
last = null;
|
|
505
|
+
failure = error?.code === "offline" ? "offline" : "failed";
|
|
506
|
+
draw();
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
// The documents' icons, of those edited last: none where they cannot be read.
|
|
510
|
+
const readIcons = () => {
|
|
511
|
+
workspace.query("recent", { limit: 50 }).then(({ data }) => {
|
|
512
|
+
if (context.signal.aborted) return;
|
|
513
|
+
icons = new Map(data.map((entry) => [entry.slug, entry.icon]));
|
|
514
|
+
if (last) draw();
|
|
515
|
+
}, () => {});
|
|
516
|
+
};
|
|
517
|
+
read();
|
|
518
|
+
readIcons();
|
|
519
|
+
return {
|
|
520
|
+
update(reason, detail) {
|
|
521
|
+
if (reason === "source" || (reason === "data" && detail === "todos")) read();
|
|
522
|
+
else if (reason === "data" && detail === "recent") readIcons();
|
|
523
|
+
else if (reason === "layout") draw();
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
},
|
|
527
|
+
});
|
|
@@ -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="#ecf3fb"/><rect x="34" y="30" width="6" height="2" rx="1" fill="#3e7ac6"/><rect x="50" y="28" width="40" height="6" rx="3" fill="#37352f"/><circle cx="190" cy="31" r="5" fill="none" stroke="#efeff0" stroke-width="2.5"/><path d="M190 26a5 5 0 1 1-4.3 7.5" fill="none" stroke="#3e7ac6" stroke-width="2.5"/><rect x="199" y="28" width="8" height="6" rx="3" fill="#c4c2bd"/><rect x="30" y="46" width="40" height="4" rx="2" fill="#c4c2bd"/><rect x="30" y="55" width="9" height="9" rx="3" fill="none" stroke="#c7c6c9" stroke-width="1.4"/><rect x="46" y="57" width="76" height="6" rx="3" fill="#4a4843"/><rect x="184" y="58" width="20" height="4" rx="2" fill="#c4c2bd"/><rect x="30" y="70" width="9" height="9" rx="3" fill="none" stroke="#c7c6c9" stroke-width="1.4"/><rect x="46" y="72" width="96" height="6" rx="3" fill="#4a4843"/><rect x="184" y="73" width="20" height="4" rx="2" fill="#c4c2bd"/><rect x="30" y="85" width="9" height="9" rx="3" fill="none" stroke="#c7c6c9" stroke-width="1.4"/><rect x="46" y="87" width="62" height="6" rx="3" fill="#4a4843"/><rect x="184" y="88" width="20" height="4" rx="2" fill="#c4c2bd"/></svg>
|