@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.
Files changed (292) hide show
  1. package/THIRD-PARTY-NOTICES.md +2 -2
  2. package/browser/editor/components/align.mjs +58 -0
  3. package/browser/editor/components/code/index.mjs +1 -1
  4. package/browser/editor/components/embed/page.mjs +1 -1
  5. package/browser/editor/components/file/page.mjs +4 -4
  6. package/browser/editor/components/heading/index.mjs +7 -6
  7. package/browser/editor/components/icons.mjs +3 -0
  8. package/browser/editor/components/image/index.mjs +2 -0
  9. package/browser/editor/components/image/page.mjs +172 -90
  10. package/browser/editor/components/installed/index.mjs +6 -2
  11. package/browser/editor/components/installed/page.mjs +80 -13
  12. package/browser/editor/components/media/icons.mjs +13 -8
  13. package/browser/editor/components/media/page.mjs +5 -3
  14. package/browser/editor/components/media/toolbar.mjs +12 -3
  15. package/browser/editor/components/paragraph/index.mjs +4 -3
  16. package/browser/editor/components/registry.mjs +17 -5
  17. package/browser/editor/components/table/index.mjs +4 -2
  18. package/browser/editor/page/after/index.mjs +288 -0
  19. package/browser/editor/page/align.mjs +91 -0
  20. package/browser/editor/page/beside.mjs +5 -6
  21. package/browser/editor/page/block-menu/index.mjs +6 -3
  22. package/browser/editor/page/blocks.mjs +3 -1
  23. package/browser/editor/page/board/hint.mjs +64 -27
  24. package/browser/editor/page/board/index.mjs +160 -71
  25. package/browser/editor/page/board/motion.mjs +177 -0
  26. package/browser/editor/page/board/settings.mjs +4 -3
  27. package/browser/editor/page/clipboard/index.mjs +10 -3
  28. package/browser/editor/page/clipboard/own/blocks.mjs +3 -4
  29. package/browser/editor/page/clipboard/own/describe.mjs +6 -2
  30. package/browser/editor/page/commit.mjs +2 -2
  31. package/browser/editor/page/core.mjs +3 -1
  32. package/browser/editor/page/custom-page.mjs +15 -11
  33. package/browser/editor/page/editor.mjs +31 -14
  34. package/browser/editor/page/history.mjs +19 -4
  35. package/browser/editor/page/insert/index.mjs +20 -22
  36. package/browser/editor/page/menu.mjs +15 -7
  37. package/browser/editor/page/page-objects.mjs +3 -7
  38. package/browser/editor/page/present-links.mjs +5 -4
  39. package/browser/editor/page/present-shell.mjs +63 -0
  40. package/browser/editor/page/selection/index.mjs +10 -2
  41. package/browser/editor/page/table/binding.mjs +87 -0
  42. package/browser/editor/page/table/icons.mjs +0 -2
  43. package/browser/editor/page/table/index.mjs +10 -14
  44. package/browser/editor/page/table/views.mjs +5 -52
  45. package/browser/editor/page/table/widths.mjs +82 -10
  46. package/browser/editor/page/typing/index.mjs +44 -12
  47. package/browser/editor/page/typing/leave.mjs +19 -5
  48. package/browser/shared/block-packs.mjs +23 -8
  49. package/browser/shared/component-catalog.mjs +15 -8
  50. package/browser/shared/component-source.mjs +3 -1
  51. package/browser/shared/components/heading.mjs +2 -1
  52. package/browser/shared/components/installed.mjs +1 -1
  53. package/browser/shared/components/page.mjs +2 -0
  54. package/browser/shared/components/paragraph.mjs +8 -1
  55. package/browser/shared/components/table.mjs +1 -0
  56. package/browser/shared/emoji/line-icons.mjs +37 -0
  57. package/browser/shared/http.mjs +9 -0
  58. package/browser/shared/i18n/en.mjs +40 -17
  59. package/browser/shared/outline.mjs +30 -2
  60. package/browser/shared/pack-manifest.mjs +12 -1
  61. package/browser/shared/pages.mjs +169 -57
  62. package/browser/shared/pinyin.mjs +92 -0
  63. package/browser/shared/present-step.mjs +17 -0
  64. package/browser/shared/sidebar-head.mjs +70 -0
  65. package/browser/shared/web-links.mjs +5 -0
  66. package/dist/chunk-2OHANJ6Q.mjs +2 -0
  67. package/dist/{chunk-QOUEFUCH.mjs → chunk-3MBRL2PM.mjs} +1 -1
  68. package/dist/{chunk-NWR2LT5B.mjs → chunk-4OKWZY5E.mjs} +1 -1
  69. package/dist/{chunk-RMECZA2K.mjs → chunk-5K44HLTZ.mjs} +1 -1
  70. package/dist/{chunk-NFMCP2HI.mjs → chunk-5XA2ZBAE.mjs} +2 -2
  71. package/dist/{chunk-OUUP3WKT.mjs → chunk-67N2QJYT.mjs} +1 -1
  72. package/dist/chunk-6KBYRA2C.mjs +2 -0
  73. package/dist/{chunk-SX4LJYT6.mjs → chunk-6N3PX4XX.mjs} +1 -1
  74. package/dist/{chunk-RFDZXFJF.mjs → chunk-7JDX4E65.mjs} +1 -1
  75. package/dist/chunk-7M2BQQGZ.mjs +2 -0
  76. package/dist/{chunk-ZUNVAXLH.mjs → chunk-7OG26PGF.mjs} +1 -1
  77. package/dist/{chunk-B4P2CM2J.mjs → chunk-AHN3NFKJ.mjs} +1 -1
  78. package/dist/chunk-ALZ6ZENZ.mjs +3 -0
  79. package/dist/{chunk-UGWYKHBL.mjs → chunk-AR5D57ZD.mjs} +2 -2
  80. package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
  81. package/dist/{chunk-HLYV7RIH.mjs → chunk-CMA72MAG.mjs} +1 -1
  82. package/dist/{chunk-D4CKRD3V.mjs → chunk-DAZL73VK.mjs} +2 -2
  83. package/dist/{chunk-6VTU6KEO.mjs → chunk-DJISHPUL.mjs} +1 -1
  84. package/dist/{chunk-BPL64HGA.mjs → chunk-EAIFITAV.mjs} +1 -1
  85. package/dist/chunk-EP25HDAJ.mjs +2 -0
  86. package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
  87. package/dist/{chunk-3KJHLHUO.mjs → chunk-F6HVDZR4.mjs} +1 -1
  88. package/dist/{chunk-3NJACR4W.mjs → chunk-FDMAXMQA.mjs} +1 -1
  89. package/dist/chunk-FETSREUN.mjs +5 -0
  90. package/dist/{chunk-RKPWLGZ3.mjs → chunk-GHOYKMQ3.mjs} +1 -1
  91. package/dist/chunk-HDWTZ763.mjs +12 -0
  92. package/dist/{chunk-VTKNEYT3.mjs → chunk-HPOI4ZX7.mjs} +1 -1
  93. package/dist/{chunk-OW5NWPOJ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
  94. package/dist/{chunk-V6RIYQZZ.mjs → chunk-ITOH5EGO.mjs} +1 -1
  95. package/dist/{chunk-ZWPMCK7P.mjs → chunk-IV64FFI4.mjs} +1 -1
  96. package/dist/chunk-J43Z6LSS.mjs +2 -0
  97. package/dist/chunk-J5PUIV3Y.mjs +4 -0
  98. package/dist/{chunk-KTKHRC6H.mjs → chunk-JA7G53PC.mjs} +1 -1
  99. package/dist/{chunk-NWFBE2YA.mjs → chunk-JLTZF4LA.mjs} +1 -1
  100. package/dist/{chunk-GY3FAUVM.mjs → chunk-JZT7I2OJ.mjs} +1 -1
  101. package/dist/{chunk-QKXMPUXC.mjs → chunk-K5XATEOO.mjs} +1 -1
  102. package/dist/chunk-L2SUIS5S.mjs +2 -0
  103. package/dist/{chunk-TEZAWDSQ.mjs → chunk-LM4XFQHZ.mjs} +1 -1
  104. package/dist/{chunk-5ZJA7CXT.mjs → chunk-MN7BZL4R.mjs} +1 -1
  105. package/dist/chunk-MTYYHM2G.mjs +15 -0
  106. package/dist/{chunk-HMYTM3GT.mjs → chunk-MXUBKDRO.mjs} +1 -1
  107. package/dist/{chunk-MHW7ZKOU.mjs → chunk-OEXWRJME.mjs} +1 -1
  108. package/dist/{chunk-CRJEG7MT.mjs → chunk-OFHXGWOX.mjs} +1 -1
  109. package/dist/chunk-ORID3AE7.mjs +166 -0
  110. package/dist/{chunk-32ZUEFZ7.mjs → chunk-PGDE24N5.mjs} +1 -1
  111. package/dist/{chunk-UNOYVIYR.mjs → chunk-POTA2FV4.mjs} +1 -1
  112. package/dist/{chunk-N7AZZLGT.mjs → chunk-QE5RG76S.mjs} +18 -17
  113. package/dist/{chunk-N76HWBA3.mjs → chunk-R2ZDLJYT.mjs} +1 -1
  114. package/dist/chunk-SWUTTZCN.mjs +2 -0
  115. package/dist/{chunk-POQSE4C2.mjs → chunk-TR7TWGHB.mjs} +1 -1
  116. package/dist/chunk-U5XL7D6A.mjs +3 -0
  117. package/dist/{chunk-PVER7LSC.mjs → chunk-U7NZI6N4.mjs} +2 -2
  118. package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
  119. package/dist/chunk-UJQJKNGK.mjs +2 -0
  120. package/dist/chunk-UYVCQK3H.mjs +22 -0
  121. package/dist/{chunk-FJYN7RTH.mjs → chunk-VFX2KNHP.mjs} +1 -1
  122. package/dist/{chunk-UBBRTJ2R.mjs → chunk-VS7JIGJW.mjs} +1 -1
  123. package/dist/{chunk-NVRNLK25.mjs → chunk-W7665ZEC.mjs} +2 -2
  124. package/dist/{chunk-NG375ILQ.mjs → chunk-WQDQWCX5.mjs} +1 -1
  125. package/dist/chunk-YLPSMDIS.mjs +5 -0
  126. package/dist/{chunk-VXACTLO3.mjs → chunk-YNAWBAYN.mjs} +1 -1
  127. package/dist/{chunk-ZB4H7ZN4.mjs → chunk-YROX2O22.mjs} +1 -1
  128. package/dist/{chunk-YQGT4AB2.mjs → chunk-ZDLPUOAG.mjs} +1 -1
  129. package/dist/chunk-ZQS6H473.mjs +16 -0
  130. package/dist/{chunk-OJKMINIU.mjs → chunk-ZWWC4KLF.mjs} +15 -15
  131. package/dist/cli.mjs +1 -1
  132. package/dist/server.mjs +1 -1
  133. package/dist/worker.mjs +1 -1
  134. package/kit/board.css +5 -5
  135. package/kit/code.js +15 -15
  136. package/kit/report.css +120 -41
  137. package/kit/runtime.js +45 -7
  138. package/kit/tables.js +2 -0
  139. package/kit/ui.js +2 -0
  140. package/package.json +1 -1
  141. package/packs/gallery/chart-view/kit.js +3 -3
  142. package/packs/gallery/chart-view/writing.md +1 -1
  143. package/packs/gallery/countdown/component.json +1 -1
  144. package/packs/gallery/countdown/kit.js +89 -29
  145. package/packs/gallery/countdown/preview.svg +1 -1
  146. package/packs/gallery/countdown/style.css +88 -100
  147. package/packs/gallery/countdown/writing.md +2 -2
  148. package/packs/gallery/habit/component.json +3 -3
  149. package/packs/gallery/habit/kit.js +114 -75
  150. package/packs/gallery/habit/preview.svg +1 -1
  151. package/packs/gallery/habit/style.css +133 -205
  152. package/packs/gallery/habit/writing.md +2 -2
  153. package/packs/gallery/heatmap/component.json +2 -2
  154. package/packs/gallery/heatmap/kit.js +335 -173
  155. package/packs/gallery/heatmap/preview.svg +1 -1
  156. package/packs/gallery/heatmap/style.css +158 -170
  157. package/packs/gallery/heatmap/writing.md +2 -2
  158. package/packs/gallery/kanban/kit.js +3 -3
  159. package/packs/gallery/kanban/writing.md +1 -1
  160. package/packs/gallery/metric/kit.js +3 -3
  161. package/packs/gallery/metric/writing.md +1 -1
  162. package/packs/gallery/note/component.json +3 -3
  163. package/packs/gallery/note/kit.js +40 -13
  164. package/packs/gallery/note/preview.svg +1 -1
  165. package/packs/gallery/note/style.css +51 -30
  166. package/packs/gallery/note/writing.md +2 -2
  167. package/packs/gallery/recent/component.json +2 -2
  168. package/packs/gallery/recent/kit.js +141 -86
  169. package/packs/gallery/recent/preview.svg +1 -1
  170. package/packs/gallery/recent/style.css +53 -126
  171. package/packs/gallery/recent/writing.md +2 -2
  172. package/packs/gallery/static/component.json +2 -2
  173. package/packs/gallery/static/preview.svg +1 -1
  174. package/packs/gallery/static/style.css +99 -66
  175. package/packs/gallery/static/writing.md +2 -2
  176. package/packs/gallery/stats/component.json +3 -3
  177. package/packs/gallery/stats/kit.js +253 -72
  178. package/packs/gallery/stats/preview.svg +1 -1
  179. package/packs/gallery/stats/style.css +64 -142
  180. package/packs/gallery/stats/writing.md +2 -2
  181. package/packs/gallery/today/component.json +2 -2
  182. package/packs/gallery/today/kit.js +62 -27
  183. package/packs/gallery/today/preview.svg +1 -1
  184. package/packs/gallery/today/style.css +101 -72
  185. package/packs/gallery/today/writing.md +2 -2
  186. package/packs/gallery/todos/component.json +1 -1
  187. package/packs/gallery/todos/kit.js +218 -71
  188. package/packs/gallery/todos/preview.svg +1 -1
  189. package/packs/gallery/todos/style.css +170 -223
  190. package/packs/gallery/todos/writing.md +1 -1
  191. package/packs/gallery/weather/component.json +2 -2
  192. package/packs/gallery/weather/kit.js +165 -90
  193. package/packs/gallery/weather/preview.svg +1 -1
  194. package/packs/gallery/weather/style.css +100 -218
  195. package/packs/gallery/weather/writing.md +1 -1
  196. package/packs/gallery-soft/countdown/component.json +18 -0
  197. package/packs/gallery-soft/countdown/example.html +3 -0
  198. package/packs/gallery-soft/countdown/kit.js +206 -0
  199. package/packs/gallery-soft/countdown/preview.svg +1 -0
  200. package/packs/gallery-soft/countdown/style.css +211 -0
  201. package/packs/gallery-soft/countdown/template.html +3 -0
  202. package/packs/gallery-soft/countdown/writing.md +24 -0
  203. package/packs/gallery-soft/habit/component.json +14 -0
  204. package/packs/gallery-soft/habit/example.html +3 -0
  205. package/packs/gallery-soft/habit/kit.js +366 -0
  206. package/packs/gallery-soft/habit/preview.svg +1 -0
  207. package/packs/gallery-soft/habit/style.css +251 -0
  208. package/packs/gallery-soft/habit/template.html +3 -0
  209. package/packs/gallery-soft/habit/writing.md +23 -0
  210. package/packs/gallery-soft/heatmap/component.json +18 -0
  211. package/packs/gallery-soft/heatmap/example.html +3 -0
  212. package/packs/gallery-soft/heatmap/kit.js +498 -0
  213. package/packs/gallery-soft/heatmap/preview.svg +1 -0
  214. package/packs/gallery-soft/heatmap/style.css +181 -0
  215. package/packs/gallery-soft/heatmap/template.html +3 -0
  216. package/packs/gallery-soft/heatmap/writing.md +23 -0
  217. package/packs/gallery-soft/note/component.json +14 -0
  218. package/packs/gallery-soft/note/example.html +3 -0
  219. package/packs/gallery-soft/note/kit.js +162 -0
  220. package/packs/gallery-soft/note/preview.svg +1 -0
  221. package/packs/gallery-soft/note/style.css +148 -0
  222. package/packs/gallery-soft/note/template.html +3 -0
  223. package/packs/gallery-soft/note/writing.md +20 -0
  224. package/packs/gallery-soft/recent/component.json +13 -0
  225. package/packs/gallery-soft/recent/example.html +1 -0
  226. package/packs/gallery-soft/recent/kit.js +335 -0
  227. package/packs/gallery-soft/recent/preview.svg +1 -0
  228. package/packs/gallery-soft/recent/style.css +160 -0
  229. package/packs/gallery-soft/recent/template.html +1 -0
  230. package/packs/gallery-soft/recent/writing.md +17 -0
  231. package/packs/gallery-soft/static/component.json +13 -0
  232. package/packs/gallery-soft/static/example.html +12 -0
  233. package/packs/gallery-soft/static/preview.svg +1 -0
  234. package/packs/gallery-soft/static/style.css +186 -0
  235. package/packs/gallery-soft/static/template.html +1 -0
  236. package/packs/gallery-soft/static/writing.md +20 -0
  237. package/packs/gallery-soft/stats/component.json +13 -0
  238. package/packs/gallery-soft/stats/example.html +1 -0
  239. package/packs/gallery-soft/stats/kit.js +341 -0
  240. package/packs/gallery-soft/stats/preview.svg +1 -0
  241. package/packs/gallery-soft/stats/style.css +119 -0
  242. package/packs/gallery-soft/stats/template.html +1 -0
  243. package/packs/gallery-soft/stats/writing.md +13 -0
  244. package/packs/gallery-soft/today/component.json +12 -0
  245. package/packs/gallery-soft/today/example.html +1 -0
  246. package/packs/gallery-soft/today/kit.js +222 -0
  247. package/packs/gallery-soft/today/preview.svg +1 -0
  248. package/packs/gallery-soft/today/style.css +178 -0
  249. package/packs/gallery-soft/today/template.html +1 -0
  250. package/packs/gallery-soft/today/writing.md +16 -0
  251. package/packs/gallery-soft/todos/component.json +19 -0
  252. package/packs/gallery-soft/todos/example.html +3 -0
  253. package/packs/gallery-soft/todos/kit.js +527 -0
  254. package/packs/gallery-soft/todos/preview.svg +1 -0
  255. package/packs/gallery-soft/todos/style.css +212 -0
  256. package/packs/gallery-soft/todos/template.html +3 -0
  257. package/packs/gallery-soft/todos/writing.md +32 -0
  258. package/packs/gallery-soft/weather/component.json +19 -0
  259. package/packs/gallery-soft/weather/example.html +3 -0
  260. package/packs/gallery-soft/weather/kit.js +411 -0
  261. package/packs/gallery-soft/weather/preview.svg +1 -0
  262. package/packs/gallery-soft/weather/style.css +185 -0
  263. package/packs/gallery-soft/weather/template.html +3 -0
  264. package/packs/gallery-soft/weather/writing.md +28 -0
  265. package/release.json +9 -9
  266. package/skill/SKILL.md +12 -12
  267. package/skill/references/building-blocks.md +4 -4
  268. package/skill/references/pages-and-data.md +13 -9
  269. package/skill/references/writing-html.md +2 -0
  270. package/web/assets/index-BM_K-Auu.css +1 -0
  271. package/web/assets/index-DRslOBiv.js +15 -0
  272. package/web/index.html +2 -2
  273. package/dist/chunk-2NRBY6AJ.mjs +0 -2
  274. package/dist/chunk-35NQ4PLQ.mjs +0 -2
  275. package/dist/chunk-4AV456RQ.mjs +0 -2
  276. package/dist/chunk-5XYHDBJ5.mjs +0 -2
  277. package/dist/chunk-6A53MZYZ.mjs +0 -2
  278. package/dist/chunk-6EXH3ORZ.mjs +0 -166
  279. package/dist/chunk-BVEFGE4B.mjs +0 -2
  280. package/dist/chunk-D4EURAX4.mjs +0 -4
  281. package/dist/chunk-DQCE7TJY.mjs +0 -5
  282. package/dist/chunk-FWNRPMCJ.mjs +0 -12
  283. package/dist/chunk-FWW4CSAZ.mjs +0 -2
  284. package/dist/chunk-JVHHHGEA.mjs +0 -3
  285. package/dist/chunk-PZDLJJTJ.mjs +0 -3
  286. package/dist/chunk-SXHBYZST.mjs +0 -16
  287. package/dist/chunk-UFNYFFKO.mjs +0 -20
  288. package/dist/chunk-UKSHDZZ6.mjs +0 -2
  289. package/dist/chunk-VJPFVNNW.mjs +0 -15
  290. package/dist/chunk-VS3ZMVGX.mjs +0 -5
  291. package/web/assets/index-BK2e_07w.js +0 -13
  292. package/web/assets/index-CWFI83mF.css +0 -1
@@ -10,37 +10,51 @@
10
10
  // wrote is the one edited last, which the facts would put first: once a box is checked here the groups keep
11
11
  // the order they were drawn in, a document new to the card first). A click on an item's words opens its
12
12
  // document there, at the item's block where it has an id, else at the document (context.workspace.open:
13
- // the workbench goes there); a click on a group's title opens the document. It reads again whenever the workspace says its to-dos changed (the `data` signal, "todos":
14
- // a to-do added, checked or taken out in any document, a document renamed or gone) and when its settings
15
- // do (`source`). Its two settings, the card's form (the grip at its top right › 设置…), are kept in its data part `settings`,
16
- // which the board writes and this block only reads: `document` (a document's slug: its to-dos alone) and
17
- // `done` (the done ones too, checked and struck through). Where the workspace cannot be read (an exported
18
- // file, where it has no server; a server that does not answer) it says so in a line; where nothing writes
19
- // it (an exported file: no setTodo) its boxes are only drawn. The words are the page's language (the UI's
20
- // where Gallery says it, else the page's lang). Its head is the cards' (the kit's card tokens): the chip,
21
- // a checked list, before its title, how many to-dos there are its quiet note at the end (共 6 项); under it,
22
- // where there are any, how many are done of all (of the document asked, or of every one): a ring of the
23
- // share done, its percent in the middle, and a key of the two counts. An item's headings are said after it
24
- // but for the document's own title (its first heading, as often), which the group's title above says. The
25
- // face says by `data-state` what it shows: items, every one done (the ring then its picture in the middle
26
- // of the room, 待办都完成了 under it, no key) or none.
13
+ // the workbench goes there); a click on a group's title opens the document. It reads again whenever the
14
+ // workspace says its to-dos changed (the `data` signal, "todos": a to-do added, checked or taken out in any
15
+ // document, a document renamed or gone) and when its settings do (`source`). Its two settings, the card's
16
+ // form (the grip at its top right › 设置…), are kept in its data part `settings`, which the board writes and
17
+ // this block only reads: `document` (a document's slug: its to-dos alone) and `done` (the done ones too,
18
+ // checked and struck through). Where the workspace cannot be read (an exported file, where it has no
19
+ // server; a server that does not answer) it says so; where nothing writes it (an exported file: no
20
+ // setTodo) its boxes are only drawn. The words are the page's language (the UI's where Gallery says it,
21
+ // else the page's lang).
22
+ //
23
+ // Its face (the pack's design B, in the kit's card tokens), laid out by the card's cells (`data-layout`, by
24
+ // context.size, read again on `layout`): a head of the chip (a checked list), 我的待办 and its quiet meta;
25
+ // how many are done of all (of the document asked, or of every one); and the list, grouped under each
26
+ // document's icon and title (small, gray, its open count after it), each item a box, its words and, under
27
+ // them (beside them on a wide card), the headings it stands under, faint, but for the document's own
28
+ // title (its first heading, as often), which the group's says; the list fades at its foot while there is
29
+ // more under it. The share done is drawn by the room: on a 4 × 4 card ("default") a line (已完成 19 / 27,
30
+ // the percent at its end) over a row of ticks, one an item (a bar past 48); on a 4 × 6 ("tall") a ring
31
+ // of the share, its percent in the middle, beside the open count large (8 项没完成) and 已完成 19 / 27; on
32
+ // an 8 × 4 ("wide") a ring at the left over a key (已完成, 未完成, 来自文档). The head's meta says how many are
33
+ // open (8 项; 来自 4 篇文档 on a tall card). The face says by `data-state` what it shows: items, every one
34
+ // done (a full green ring with a tick, 待办都完成了), none, or the workspace not read.
27
35
 
28
36
  const WORDS = {
29
37
  zh: {
30
38
  title: "我的待办",
31
- empty: "所有文档里的待办会出现在这里",
39
+ empty: ["还没有待办", "所有文档里的待办会出现在这里", "在文档里输入 [] 就能新建一项"],
40
+ noneIn: ["这篇文档里没有待办", "在文档里输入 [] 就能新建一项"],
32
41
  allDone: "待办都完成了",
33
- noneIn: "这篇文档里没有待办",
34
- offline: "在 Gallery 里打开时,这里列出所有文档里的待办",
35
- failed: "读不到工作区的待办",
42
+ allDoneSaid: (/** @type {number} */ all) => `${all} 项,全都勾掉了`,
43
+ offline: ["在 Gallery 里打开时显示", "现在读不到工作区"],
44
+ failed: ["读不到工作区的待办", "过一会儿会再读"],
36
45
  open: (/** @type {string} */ title) => `打开「${title}」`,
37
46
  path: (/** @type {string[]} */ path) => path.join(" › "),
38
47
  check: "标记为完成",
39
48
  uncheck: "标记为未完成",
40
49
  done: "已完成",
41
50
  left: "未完成",
51
+ from: "来自文档",
52
+ progress: (/** @type {number} */ done, /** @type {number} */ all) => ["已完成 ", String(done), ` / ${all}`],
53
+ openUnit: "项没完成",
42
54
  share: (/** @type {number} */ done, /** @type {number} */ all) => `${all} 项待办完成了 ${done} 项`,
55
+ openCount: (/** @type {number} */ count) => `${count} 项`,
43
56
  all: (/** @type {number} */ count) => `共 ${count} 项`,
57
+ documents: (/** @type {number} */ count) => `来自 ${count} 篇文档`,
44
58
  refused: {
45
59
  changed: "这一项刚被改过,已重新读取",
46
60
  ambiguous: "文档里有几项一样的待办,请在文档里勾选",
@@ -53,19 +67,25 @@ const WORDS = {
53
67
  },
54
68
  en: {
55
69
  title: "My to-dos",
56
- empty: "The to-dos of every document will show here",
70
+ empty: ["No to-dos yet", "The to-dos of every document show here", "Type [] in a document to make one"],
71
+ noneIn: ["This document has no to-dos", "Type [] in it to make one"],
57
72
  allDone: "Every to-do is done",
58
- noneIn: "This document has no to-dos",
59
- offline: "Opened in Gallery, this lists the to-dos of every document",
60
- failed: "The workspace's to-dos could not be read",
73
+ allDoneSaid: (/** @type {number} */ all) => `All ${all} checked off`,
74
+ offline: ["Shown when opened in Gallery", "The workspace cannot be read now"],
75
+ failed: ["The workspace's to-dos could not be read", "It reads them again in a while"],
61
76
  open: (/** @type {string} */ title) => `Open “${title}”`,
62
77
  path: (/** @type {string[]} */ path) => path.join(" › "),
63
78
  check: "Mark as done",
64
79
  uncheck: "Mark as not done",
65
80
  done: "Done",
66
81
  left: "Open",
82
+ from: "Documents",
83
+ progress: (/** @type {number} */ done, /** @type {number} */ all) => ["", String(done), ` of ${all} done`],
84
+ openUnit: "open",
67
85
  share: (/** @type {number} */ done, /** @type {number} */ all) => `${done} of ${all} to-dos done`,
86
+ openCount: (/** @type {number} */ count) => `${count} open`,
68
87
  all: (/** @type {number} */ count) => `${count} in all`,
88
+ documents: (/** @type {number} */ count) => `from ${count} ${count === 1 ? "document" : "documents"}`,
69
89
  refused: {
70
90
  changed: "This to-do just changed: read again",
71
91
  ambiguous: "The document has several to-dos alike: check it in the document",
@@ -80,6 +100,8 @@ const WORDS = {
80
100
 
81
101
  /** How long a line saying why a check was refused stays, in ms. */
82
102
  const SAID_FOR = 4000;
103
+ /** The most items the ticks draw one each; past them, a bar. */
104
+ const MOST_TICKS = 48;
83
105
 
84
106
  /** The language the page speaks: the UI's where Gallery says it (data-ge-language), else its lang's. */
85
107
  function languageOf(/** @type {HTMLElement} */ root) {
@@ -97,7 +119,7 @@ function askedOf(settings) {
97
119
  return { document: typeof table.document === "string" && table.document ? table.document : null, done: table.done === true };
98
120
  }
99
121
 
100
- /** @typedef {{ ref: DocTodoRef, text: string, done: boolean, slug: string, title: string, path: string[] }} Todo */
122
+ /** @typedef {{ ref: DocTodoRef, text: string, done: boolean, slug: string, title: string, icon?: string | null, path: string[] }} Todo */
101
123
 
102
124
  /**
103
125
  * An item by its document, its headings and its words: what names it while the card lives, whether it has
@@ -108,10 +130,11 @@ const itemKey = (todo) => JSON.stringify([todo.slug, todo.path, todo.text]);
108
130
 
109
131
  /**
110
132
  * The to-dos (workspace.query("todos")'s data) as the card shows them, of `asked`: { groups, open,
111
- * all }, groups [{ slug, title, items }] in the order the documents come (the one edited last first),
112
- * items those shown (not done, or all with `asked.done`, and the ones checked on this card: `kept`);
113
- * `open` how many are not done, `all` how many there are of the document asked or of the workspace.
114
- * `order` (the slugs as drawn before, or null): the groups keep that order, a document not in it before them.
133
+ * all, documents }, groups [{ slug, title, icon, items }] in the order the documents come (the one edited
134
+ * last first), items those shown (not done, or all with `asked.done`, and the ones checked on this card:
135
+ * `kept`); `open` how many are not done, `all` how many there are of the document asked or of the
136
+ * workspace, `documents` how many documents hold one open. `order` (the slugs as drawn before, or null):
137
+ * the groups keep that order, a document not in it before them.
115
138
  * @param {Todo[]} todos
116
139
  * @param {{ document: string | null, done: boolean }} asked
117
140
  * @param {Set<string>} kept
@@ -119,21 +142,21 @@ const itemKey = (todo) => JSON.stringify([todo.slug, todo.path, todo.text]);
119
142
  */
120
143
  function groupsOf(todos, asked, kept, order = null) {
121
144
  const mine = todos.filter((todo) => asked.document === null || todo.slug === asked.document);
122
- /** @type {Map<string, { slug: string, title: string, items: Todo[] }>} */
145
+ /** @type {Map<string, { slug: string, title: string, icon: string | null, items: Todo[] }>} */
123
146
  const groups = new Map();
124
147
  for (const todo of mine) {
125
148
  if (todo.done && !asked.done && !kept.has(itemKey(todo))) continue;
126
- if (!groups.has(todo.slug)) groups.set(todo.slug, { slug: todo.slug, title: todo.title, items: [] });
149
+ if (!groups.has(todo.slug)) groups.set(todo.slug, { slug: todo.slug, title: todo.title, icon: todo.icon ?? null, items: [] });
127
150
  groups.get(todo.slug)?.items.push(todo);
128
151
  }
129
152
  const placed = [...groups.values()];
130
153
  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
131
- return { groups: placed, open: mine.filter((todo) => !todo.done).length, all: mine.length };
154
+ return { groups: placed, open: mine.filter((todo) => !todo.done).length, all: mine.length, documents: new Set(mine.filter((todo) => !todo.done).map((todo) => todo.slug)).size };
132
155
  }
133
156
 
134
157
  /**
135
- * The line the card says where it shows no item, or null where it shows some: none in the workspace,
136
- * none in the one document asked, all done.
158
+ * What the card says where it shows no item, or null where it shows some: none in the workspace, none in
159
+ * the one document asked, all done.
137
160
  * @param {{ groups: unknown[], all: number }} shown
138
161
  * @param {{ document: string | null }} asked
139
162
  * @param {"zh" | "en"} language
@@ -142,7 +165,15 @@ function emptyLine(shown, asked, language) {
142
165
  if (shown.groups.length > 0) return null;
143
166
  const words = WORDS[language];
144
167
  if (shown.all > 0) return words.allDone;
145
- return asked.document === null ? words.empty : words.noneIn;
168
+ return asked.document === null ? words.empty[1] : words.noneIn[0];
169
+ }
170
+
171
+ /** 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 */
172
+ function iconKindOf(value) {
173
+ if (typeof value !== "string" || !value) return null;
174
+ if (value.startsWith("/icons/")) return "glyph";
175
+ if (value.startsWith("assets/")) return "image";
176
+ return "emoji";
146
177
  }
147
178
 
148
179
  /**
@@ -159,9 +190,34 @@ function made(tag, kind, text) {
159
190
  return node;
160
191
  }
161
192
 
193
+ // A document's icon, as the sidebar draws it: its picture from the server's icon route, a glyph masked
194
+ // in the muted color, or none (style.css draws a page).
195
+ function iconFor(/** @type {string} */ slug, /** @type {string | null} */ value) {
196
+ const kind = iconKindOf(value);
197
+ const icon = made("span", "icon");
198
+ icon.setAttribute("aria-hidden", "true");
199
+ if (!kind || value === null) return icon;
200
+ const address = `/__gallery/edit/icon?value=${encodeURIComponent(value)}&slug=${encodeURIComponent(slug)}`;
201
+ if (kind === "glyph") {
202
+ icon.classList.add("gallery-todos-glyph");
203
+ icon.style.setProperty("--gallery-todos-icon", `url("${address}")`);
204
+ return icon;
205
+ }
206
+ const picture = document.createElement("img");
207
+ picture.alt = "";
208
+ picture.src = address;
209
+ picture.decoding = "async";
210
+ icon.classList.add("gallery-todos-picture");
211
+ icon.append(picture);
212
+ return icon;
213
+ }
214
+
215
+ const CHIP = '<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="4" width="6.5" height="6.5" rx="1.8"/><path d="M5.2 7.3l1.3 1.3 2-2.1"/><rect x="3.5" y="13.5" width="6.5" height="6.5" rx="1.8"/><path d="M13.5 7.2h7M13.5 16.8h7"/></svg>';
216
+ const TICK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5.5 12.5l4 4 9-9"/></svg>';
217
+
162
218
  DocKit.component({
163
219
  id: "gallery/todos",
164
- on: ["source", "data"],
220
+ on: ["source", "data", "layout"],
165
221
  mount(host, context) {
166
222
  const language = languageOf(document.documentElement);
167
223
  const words = WORDS[language];
@@ -170,34 +226,58 @@ DocKit.component({
170
226
  const count = made("span", "count");
171
227
  const chip = made("span", "chip");
172
228
  chip.setAttribute("aria-hidden", "true");
173
- chip.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 5.5L5 7l2.5-2.5M3.5 11.5L5 13l2.5-2.5M3.5 17.5L5 19l2.5-2.5M11 6h9M11 12h9M11 18h9"/></svg>';
229
+ chip.innerHTML = CHIP;
174
230
  head.append(chip, made("span", "title", words.title), count);
175
- // How many are done of all: a ring of the share done (the good green once all are) and a key of the two counts.
231
+ // How many are done of all: a line and its ticks, a ring and its percent, the open count large, a key.
176
232
  const summary = made("div", "summary");
177
233
  summary.hidden = true;
178
234
  summary.setAttribute("role", "img");
235
+ const progress = made("p", "progress");
236
+ const progressWords = made("span", "progress-words");
237
+ const sharePercent = made("span", "share");
238
+ progress.append(progressWords, sharePercent);
239
+ const ticks = made("span", "ticks");
240
+ ticks.setAttribute("aria-hidden", "true");
179
241
  const ring = made("span", "ring");
180
- ring.innerHTML = '<svg viewBox="0 0 100 100" aria-hidden="true"><circle class="gallery-todos-track" cx="50" cy="50" r="42"/><circle class="gallery-todos-arc" cx="50" cy="50" r="42" pathLength="100" transform="rotate(-90 50 50)"/></svg>';
181
- const percent = made("span", "percent");
182
- ring.append(percent);
242
+ ring.innerHTML = '<svg aria-hidden="true"><circle class="gallery-todos-track"/><circle class="gallery-todos-arc" pathLength="100"/></svg><span class="gallery-todos-percent"></span>';
243
+ const percent = /** @type {HTMLElement} */ (ring.querySelector(".gallery-todos-percent"));
244
+ const big = made("div", "big");
245
+ const bigLine = made("p", "big-line");
246
+ const openNumber = made("span", "open");
247
+ bigLine.append(openNumber, made("span", "open-unit", words.openUnit));
248
+ const bigDone = made("p", "big-done");
249
+ big.append(bigLine, bigDone);
183
250
  const key = made("dl", "key");
184
- const doneCount = made("dd", "key-count");
185
- const leftCount = made("dd", "key-count");
186
- const doneRow = made("div", "key-row");
187
- doneRow.dataset.kind = "done";
188
- doneRow.append(made("dt", "key-name", words.done), doneCount);
189
- const leftRow = made("div", "key-row");
190
- leftRow.dataset.kind = "left";
191
- leftRow.append(made("dt", "key-name", words.left), leftCount);
192
- key.append(doneRow, leftRow);
193
- summary.append(ring, key);
251
+ const keyRow = (/** @type {string} */ kind, /** @type {string} */ name) => {
252
+ const row = made("div", "key-row");
253
+ row.dataset.kind = kind;
254
+ const value = made("dd", "key-count");
255
+ row.append(made("dt", "key-name", name), value);
256
+ key.append(row);
257
+ return value;
258
+ };
259
+ const doneCount = keyRow("done", words.done);
260
+ const leftCount = keyRow("left", words.left);
261
+ const fromCount = keyRow("from", words.from);
262
+ summary.append(progress, ticks, ring, big, key);
194
263
  const list = made("div", "list");
264
+ // Where it shows no item: the cards' empty face (a soft square with the list's picture, or the full ring).
265
+ const empty = made("div", "empty");
266
+ const emptyPicture = made("span", "empty-picture");
267
+ emptyPicture.setAttribute("aria-hidden", "true");
195
268
  const line = made("p", "line");
269
+ const hint = made("p", "hint");
270
+ empty.append(emptyPicture, line, hint);
271
+ empty.hidden = true;
196
272
  const note = made("p", "note");
197
273
  note.hidden = true;
198
274
  note.setAttribute("role", "status");
199
- face.append(head, summary, list, line, note);
275
+ face.append(head, summary, list, empty, note);
200
276
  host.append(face);
277
+ // The list fades at its foot while there is more under it.
278
+ const fade = () => list.toggleAttribute("data-more", list.scrollHeight - list.scrollTop - list.clientHeight > 1);
279
+ list.addEventListener("scroll", fade, { passive: true });
280
+ context.signal?.addEventListener("abort", () => list.removeEventListener("scroll", fade), { once: true });
201
281
  const workspace = context.workspace;
202
282
  if (!workspace) return void context.fail(new Error("its manifest's needs hold \"workspace\": context.workspace is not there"));
203
283
  const open = (/** @type {{ slug: string, block?: string | null }} */ target) => workspace.open(target);
@@ -212,13 +292,37 @@ DocKit.component({
212
292
  /** @type {string[] | null} */
213
293
  let held = null; // that order as it was when the first box was checked here: the groups keep it from then on
214
294
 
215
- const say = (/** @type {string | null} */ words) => {
216
- line.hidden = words === null;
217
- line.textContent = words ?? "";
295
+ const layoutOf = () => {
296
+ const size = context.size;
297
+ return !size ? "default" : size.w >= 8 ? "wide" : size.h >= 6 ? "tall" : "default";
298
+ };
299
+ // A ring of `size` px, its stroke `width` px, the share done (`share`, 0 to 1).
300
+ const drawRing = (/** @type {number} */ size, /** @type {number} */ width, /** @type {number} */ share) => {
301
+ const svg = /** @type {SVGSVGElement} */ (ring.querySelector("svg"));
302
+ svg.setAttribute("viewBox", `0 0 ${size} ${size}`);
303
+ svg.setAttribute("width", String(size));
304
+ svg.setAttribute("height", String(size));
305
+ for (const circle of svg.querySelectorAll("circle")) {
306
+ circle.setAttribute("cx", String(size / 2));
307
+ circle.setAttribute("cy", String(size / 2));
308
+ circle.setAttribute("r", String((size - width) / 2));
309
+ circle.setAttribute("stroke-width", String(width));
310
+ }
311
+ svg.querySelector(".gallery-todos-arc")?.setAttribute("stroke-dasharray", `${Math.max(0, Math.min(1, share)) * 100} 100`);
312
+ };
313
+ // The empty face: its words, and its picture (the list's, or the full green ring with a tick).
314
+ const sayEmpty = (/** @type {string[] | null} */ said, /** @type {"list" | "done"} */ picture = "list") => {
315
+ empty.hidden = said === null;
316
+ list.hidden = said !== null;
317
+ if (said === null) return;
318
+ empty.dataset.picture = picture;
319
+ emptyPicture.innerHTML = picture === "done" ? `<svg viewBox="0 0 72 72"><circle class="gallery-todos-done-ring" cx="36" cy="36" r="32.5"/></svg>${TICK}` : CHIP;
320
+ line.textContent = said[0];
321
+ hint.replaceChildren(...said.slice(1).flatMap((piece, index) => (index === 0 ? [piece] : [document.createElement("br"), piece])));
218
322
  };
219
323
  let noteTimer = 0;
220
- const tell = (/** @type {string} */ words) => {
221
- note.textContent = words;
324
+ const tell = (/** @type {string} */ said) => {
325
+ note.textContent = said;
222
326
  note.hidden = false;
223
327
  clearTimeout(noteTimer);
224
328
  noteTimer = context.timeout(() => { note.hidden = true; }, SAID_FOR);
@@ -248,29 +352,61 @@ DocKit.component({
248
352
 
249
353
  // The list drawn anew of what was read, scrolled where it was.
250
354
  const draw = () => {
355
+ const layout = layoutOf();
356
+ face.dataset.layout = layout;
251
357
  if (!last) return;
252
358
  const asked = lastAsked;
253
359
  const shown = groupsOf(last, asked, kept, held);
254
360
  order = shown.groups.map((group) => group.slug);
255
361
  const top = list.scrollTop;
256
- count.textContent = shown.all > 0 ? words.all(shown.all) : "";
257
- // The summary of all the to-dos asked (of one document, or of every one), none where there are none.
258
362
  const done = shown.all - shown.open;
259
- summary.hidden = shown.all === 0;
260
- summary.toggleAttribute("data-all", shown.all > 0 && done === shown.all);
261
- summary.style.setProperty("--gallery-todos-share", String(shown.all > 0 ? done / shown.all : 0));
363
+ const allDone = shown.groups.length === 0 && shown.all > 0;
364
+ count.textContent = shown.all === 0 ? "" : allDone ? words.all(shown.all) : layout === "tall" ? words.documents(shown.documents) : words.openCount(shown.open);
365
+ // The summary of all the to-dos asked (of one document, or of every one), none where there are none.
366
+ summary.hidden = shown.all === 0 || allDone;
262
367
  summary.setAttribute("aria-label", words.share(done, shown.all));
263
- if (shown.all > 0) percent.replaceChildren(String(Math.floor((100 * done) / shown.all)), made("span", "percent-sign", "%"));
264
- else percent.replaceChildren();
368
+ const share = shown.all > 0 ? done / shown.all : 0;
369
+ const whole = shown.all > 0 ? String(Math.floor(100 * share)) : "";
370
+ const [before, middle, after] = words.progress(done, shown.all);
371
+ const bold = document.createElement("b");
372
+ bold.textContent = middle;
373
+ progressWords.replaceChildren(before, bold, after);
374
+ sharePercent.textContent = shown.all > 0 ? `${whole}%` : "";
375
+ const value = made("span", "percent-value", whole);
376
+ value.append(made("span", "percent-sign", "%"));
377
+ percent.replaceChildren(value);
378
+ drawRing(layout === "wide" ? 96 : 64, layout === "wide" ? 9 : 7, share);
379
+ openNumber.textContent = String(shown.open);
380
+ const [doneBefore, doneMiddle, doneAfter] = words.progress(done, shown.all);
381
+ bigDone.textContent = `${doneBefore}${doneMiddle}${doneAfter}`;
265
382
  doneCount.textContent = String(done);
266
383
  leftCount.textContent = String(shown.open);
384
+ fromCount.textContent = String(shown.documents);
385
+ // A tick an item (done first), or one bar past MOST_TICKS.
386
+ if (shown.all > MOST_TICKS) {
387
+ const on = made("i", "tick");
388
+ on.classList.add("gallery-todos-on");
389
+ on.style.flex = String(done);
390
+ const off = made("i", "tick");
391
+ off.style.flex = String(shown.open);
392
+ ticks.replaceChildren(on, off);
393
+ } else {
394
+ ticks.replaceChildren(...Array.from({ length: shown.all }, (_, index) => {
395
+ const tick = made("i", "tick");
396
+ tick.classList.toggle("gallery-todos-on", index < done);
397
+ return tick;
398
+ }));
399
+ }
267
400
  face.dataset.state = shown.groups.length > 0 ? "items" : shown.all > 0 ? "done" : "none";
268
401
  list.replaceChildren(...shown.groups.map((group) => {
269
402
  const section = made("section", "group");
270
- const title = made("button", "doc", group.title);
271
- title.type = "button";
272
- title.title = words.open(group.title);
273
- title.addEventListener("click", () => open({ slug: group.slug }));
403
+ const title = made("div", "group-head");
404
+ const doc = made("button", "doc");
405
+ doc.type = "button";
406
+ doc.title = words.open(group.title);
407
+ doc.append(iconFor(group.slug, group.icon), group.title);
408
+ doc.addEventListener("click", () => open({ slug: group.slug }));
409
+ title.append(doc, made("span", "group-count", String(group.items.filter((todo) => !todo.done).length)));
274
410
  const items = made("ul", "items");
275
411
  for (const todo of group.items) {
276
412
  const item = made("li", "item");
@@ -289,14 +425,21 @@ DocKit.component({
289
425
  box.setAttribute("aria-checked", String(todo.done));
290
426
  box.title = todo.done ? words.uncheck : words.check;
291
427
  box.setAttribute("aria-label", `${todo.done ? words.uncheck : words.check}: ${todo.text}`);
292
- box.append(made("span", "box"));
428
+ const square = made("span", "box");
429
+ if (todo.done) square.innerHTML = TICK;
430
+ box.append(square);
293
431
  box.addEventListener("click", (event) => {
294
432
  event.stopPropagation();
295
433
  check(todo);
296
434
  });
297
435
  item.append(box);
298
436
  button.append(text);
299
- } else button.append(made("span", "box"), text);
437
+ } else {
438
+ const square = made("span", "box");
439
+ if (todo.done) square.innerHTML = TICK;
440
+ item.append(square);
441
+ button.append(text);
442
+ }
300
443
  // Its headings, but for the document's title the group's title above says (its first heading, as often).
301
444
  const path = todo.path[0] === group.title ? todo.path.slice(1) : todo.path;
302
445
  if (path.length > 0) button.append(made("span", "path", words.path(path)));
@@ -308,7 +451,10 @@ DocKit.component({
308
451
  return section;
309
452
  }));
310
453
  list.scrollTop = top;
311
- say(emptyLine(shown, asked, language));
454
+ if (shown.groups.length > 0) sayEmpty(null);
455
+ else if (shown.all > 0) sayEmpty([words.allDone, words.allDoneSaid(shown.all)], "done");
456
+ else sayEmpty(asked.document === null ? words.empty : words.noneIn);
457
+ fade();
312
458
  };
313
459
 
314
460
  let asking = 0; // the last read asked: an older answer that comes after it is dropped
@@ -327,15 +473,16 @@ DocKit.component({
327
473
  last = null;
328
474
  count.textContent = "";
329
475
  summary.hidden = true;
330
- face.dataset.state = "none";
476
+ face.dataset.state = "unread";
331
477
  list.replaceChildren();
332
- say(error?.code === "offline" ? words.offline : words.failed);
478
+ sayEmpty(error?.code === "offline" ? words.offline : words.failed);
333
479
  });
334
480
  };
335
481
  read();
336
482
  return {
337
483
  update(reason, detail) {
338
484
  if (reason === "source" || (reason === "data" && detail === "todos")) read();
485
+ else if (reason === "layout") draw();
339
486
  },
340
487
  };
341
488
  },
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 120"><rect width="240" height="120" rx="8" fill="#f3f3f1"/><rect x="16.5" y="12.5" width="207" height="95" rx="12" fill="#ffffff" stroke="#e7e6e3"/><rect x="30" y="24" width="14" height="14" rx="4.5" fill="#ebf2fa"/><g transform="translate(32 26) scale(.4167)" fill="none" stroke="#2e7cd1" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 5.5L5 7l2.5-2.5M3.5 11.5L5 13l2.5-2.5M3.5 17.5L5 19l2.5-2.5M11 6h9M11 12h9M11 18h9"/></g><rect x="50" y="28" width="40" height="6" rx="3" fill="#3b3a37"/><rect x="186" y="28.5" width="24" height="5" rx="2.5" fill="#c4c2bd"/><circle cx="40" cy="55" r="8.5" fill="none" stroke="#ecebe8" stroke-width="3.2"/><circle cx="40" cy="55" r="8.5" fill="none" stroke="#2e7cd1" stroke-width="3.2" stroke-linecap="round" stroke-dasharray="32.0 53.4" transform="rotate(-90 40 55)"/><circle cx="59" cy="50.5" r="2" fill="#2e7cd1"/><rect x="64" y="48.5" width="20" height="4" rx="2" fill="#c4c2bd"/><rect x="98" y="48.5" width="7" height="4" rx="2" fill="#3b3a37"/><circle cx="59" cy="59.5" r="2" fill="#ecebe8"/><rect x="64" y="57.5" width="20" height="4" rx="2" fill="#c4c2bd"/><rect x="98" y="57.5" width="7" height="4" rx="2" fill="#3b3a37"/><rect x="30" y="70" width="180" height="1" fill="#efeeec"/><rect x="30" y="76" width="10" height="10" rx="3" fill="#2e7cd1"/><path d="M32.12 81.18l1.98 1.98 3.96-4.14" fill="none" stroke="#fff" stroke-width="1.44" stroke-linecap="round" stroke-linejoin="round"/><rect x="47" y="78.5" width="92" height="5" rx="2.5" fill="#c4c2bd"/><rect x="144" y="78.5" width="16" height="5" rx="2.5" fill="#e9e8e5"/><rect x="30.75" y="91.75" width="8.5" height="8.5" rx="2.6" fill="none" stroke="#a9a7a2" stroke-width="1.3"/><rect x="47" y="93.5" width="70" height="5" rx="2.5" fill="#3b3a37"/><rect x="122" y="93.5" width="16" height="5" rx="2.5" fill="#e9e8e5"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 120"><rect width="240" height="120" rx="8" fill="#f3f3f1"/><rect x="60.5" y="6.5" width="119" height="107" rx="8" fill="#ffffff" stroke="#e6e6e3"/><rect x="70" y="14" width="10" height="10" rx="3" fill="#efefed"/><rect x="84" y="16.5" width="30" height="5" rx="2.5" fill="#37352f"/><rect x="118" y="16.5" width="12" height="5" rx="2.5" fill="#d4d3cf"/><rect x="70" y="32" width="34" height="4" rx="2" fill="#a6a5a1"/><rect x="70" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="77.2" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="84.4" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="91.6" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="98.8" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="106" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="113.2" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="120.4" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="127.6" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="134.8" y="40" width="6" height="3" rx="1.5" fill="#4084cf"/><rect x="142" y="40" width="6" height="3" rx="1.5" fill="#e7e7e5"/><rect x="149.2" y="40" width="6" height="3" rx="1.5" fill="#e7e7e5"/><rect x="156.4" y="40" width="6" height="3" rx="1.5" fill="#e7e7e5"/><rect x="163.60000000000002" y="40" width="6" height="3" rx="1.5" fill="#e7e7e5"/><rect x="70" y="52" width="50" height="4" rx="2" fill="#a6a5a1"/><rect x="70.5" y="62.5" width="7" height="7" rx="2" fill="none" stroke="#bdbcb8"/><rect x="82" y="63" width="56" height="5" rx="2.5" fill="#37352f"/><rect x="82" y="71" width="30" height="3" rx="1.5" fill="#d4d3cf"/><rect x="70.5" y="78.5" width="7" height="7" rx="2" fill="none" stroke="#bdbcb8"/><rect x="82" y="79" width="44" height="5" rx="2.5" fill="#37352f"/><rect x="82" y="87" width="22" height="3" rx="1.5" fill="#d4d3cf"/><rect x="70.5" y="94.5" width="7" height="7" rx="2" fill="#4084cf" stroke="#4084cf"/><rect x="82" y="95" width="50" height="5" rx="2.5" fill="#d4d3cf"/><rect x="82" y="103" width="26" height="3" rx="1.5" fill="#d4d3cf"/></svg>