@heycys/gallery 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/THIRD-PARTY-NOTICES.md +1 -1
  2. package/browser/editor/components/columns/page.mjs +4 -4
  3. package/browser/editor/components/embed/index.mjs +2 -2
  4. package/browser/editor/components/flowchart/add.mjs +7 -6
  5. package/browser/editor/components/flowchart/page.mjs +1 -1
  6. package/browser/editor/components/icons.mjs +8 -0
  7. package/browser/editor/components/image/crop.mjs +6 -9
  8. package/browser/editor/components/installed/index.mjs +11 -4
  9. package/browser/editor/components/installed/page.mjs +27 -16
  10. package/browser/editor/components/installed/served.mjs +6 -5
  11. package/browser/editor/components/instances.mjs +18 -0
  12. package/browser/editor/components/list/page.mjs +3 -6
  13. package/browser/editor/components/registry.mjs +13 -4
  14. package/browser/editor/components/table/index.mjs +59 -2
  15. package/browser/editor/components/table/page.mjs +1 -1
  16. package/browser/editor/page/annotate/card.mjs +3 -6
  17. package/browser/editor/page/autosave.mjs +310 -37
  18. package/browser/editor/page/board/client.mjs +57 -0
  19. package/browser/editor/page/board/hint.mjs +145 -0
  20. package/browser/editor/page/board/history.mjs +119 -0
  21. package/browser/editor/page/board/index.mjs +970 -0
  22. package/browser/editor/page/board/settings.mjs +209 -0
  23. package/browser/editor/page/caret.mjs +1 -1
  24. package/browser/editor/page/changes/bar.mjs +6 -14
  25. package/browser/editor/page/changes/index.mjs +2 -2
  26. package/browser/editor/page/clipboard/import/html.mjs +29 -6
  27. package/browser/editor/page/clipboard/import/inline.mjs +32 -0
  28. package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
  29. package/browser/editor/page/clipboard/import/read.mjs +6 -1
  30. package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
  31. package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
  32. package/browser/editor/page/clipboard/index.mjs +124 -13
  33. package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
  34. package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
  35. package/browser/editor/page/clipboard/own/selection.mjs +1 -1
  36. package/browser/editor/page/columns/index.mjs +48 -46
  37. package/browser/editor/page/commit.mjs +80 -22
  38. package/browser/editor/page/conflicts.mjs +7 -7
  39. package/browser/editor/page/core.mjs +31 -15
  40. package/browser/editor/page/custom-page.mjs +106 -0
  41. package/browser/editor/page/editor.mjs +51 -14
  42. package/browser/editor/page/emoji-picker/styles.mjs +7 -12
  43. package/browser/editor/page/emoji-picker/upload.mjs +2 -2
  44. package/browser/editor/page/handle/index.mjs +5 -8
  45. package/browser/editor/page/history.mjs +24 -4
  46. package/browser/editor/page/links/index.mjs +5 -3
  47. package/browser/editor/page/links/pages.mjs +27 -9
  48. package/browser/editor/page/look.mjs +54 -3
  49. package/browser/editor/page/margin.mjs +18 -3
  50. package/browser/editor/page/media-menu/index.mjs +6 -7
  51. package/browser/editor/page/menu.mjs +18 -7
  52. package/browser/editor/page/notices.mjs +2 -2
  53. package/browser/editor/page/page-objects.mjs +13 -2
  54. package/browser/editor/page/panel.mjs +4 -9
  55. package/browser/editor/page/present-links.mjs +11 -2
  56. package/browser/editor/page/resize/index.mjs +42 -29
  57. package/browser/editor/page/served.mjs +14 -0
  58. package/browser/editor/page/session.mjs +48 -4
  59. package/browser/editor/page/slash-menu/index.mjs +3 -3
  60. package/browser/editor/page/table/cells.mjs +102 -46
  61. package/browser/editor/page/table/icons.mjs +2 -0
  62. package/browser/editor/page/table/index.mjs +29 -10
  63. package/browser/editor/page/table/pictures.mjs +299 -0
  64. package/browser/editor/page/table/views.mjs +150 -0
  65. package/browser/editor/page/text-select/index.mjs +26 -12
  66. package/browser/editor/page/todo-writes.mjs +75 -0
  67. package/browser/editor/page/typing/index.mjs +53 -3
  68. package/browser/editor/page/ui.mjs +13 -0
  69. package/browser/shared/block-packs.mjs +404 -0
  70. package/browser/shared/board-places.mjs +224 -0
  71. package/browser/shared/card-size.mjs +59 -0
  72. package/browser/shared/colors.mjs +3 -2
  73. package/browser/shared/component-catalog.mjs +323 -46
  74. package/browser/shared/component-source.mjs +37 -0
  75. package/browser/shared/components/index.mjs +12 -3
  76. package/browser/shared/components/installed.mjs +35 -3
  77. package/browser/shared/components/page.mjs +18 -8
  78. package/browser/shared/components/state.mjs +5 -1
  79. package/browser/shared/document-look.mjs +10 -5
  80. package/browser/shared/drafts.mjs +432 -0
  81. package/browser/shared/extension-sidebar.mjs +126 -0
  82. package/browser/shared/extensions.mjs +79 -0
  83. package/browser/shared/file-table.mjs +101 -0
  84. package/browser/shared/i18n/en.mjs +307 -27
  85. package/browser/shared/json-layout.mjs +47 -0
  86. package/browser/shared/net-cache.mjs +133 -0
  87. package/browser/shared/net-keys.mjs +32 -0
  88. package/browser/shared/net-policy.mjs +177 -0
  89. package/browser/shared/outline-shape.mjs +128 -0
  90. package/browser/shared/outline.mjs +331 -67
  91. package/browser/shared/pack-manifest.mjs +77 -0
  92. package/browser/shared/page-address.mjs +6 -3
  93. package/browser/shared/page-cards.mjs +308 -0
  94. package/browser/shared/page-links.mjs +69 -6
  95. package/browser/shared/pages.mjs +388 -0
  96. package/browser/shared/paths.mjs +6 -0
  97. package/browser/shared/preview-server.mjs +3 -7
  98. package/browser/shared/session.mjs +8 -1
  99. package/browser/shared/shipped-blocks.mjs +299 -0
  100. package/browser/shared/table-chart.mjs +33 -1
  101. package/browser/shared/todos.mjs +83 -0
  102. package/browser/shared/user-directory.mjs +24 -0
  103. package/browser/shared/versions.mjs +14 -0
  104. package/browser/shared/web-links.mjs +134 -0
  105. package/browser/shared/workspace-columns.mjs +120 -0
  106. package/browser/shared/workspace-name.mjs +154 -0
  107. package/browser/shared/workspace-query.mjs +125 -0
  108. package/browser/shared/workspace-registry.mjs +272 -0
  109. package/browser/shared/workspace.mjs +171 -20
  110. package/dist/chunk-2NRBY6AJ.mjs +2 -0
  111. package/dist/chunk-32ZUEFZ7.mjs +2 -0
  112. package/dist/chunk-35NQ4PLQ.mjs +2 -0
  113. package/dist/chunk-3KJHLHUO.mjs +2 -0
  114. package/dist/chunk-3NJACR4W.mjs +2 -0
  115. package/dist/chunk-4AV456RQ.mjs +2 -0
  116. package/dist/chunk-4OZWXZE5.mjs +5 -0
  117. package/dist/chunk-5XYHDBJ5.mjs +2 -0
  118. package/dist/chunk-5ZJA7CXT.mjs +2 -0
  119. package/dist/chunk-6A53MZYZ.mjs +2 -0
  120. package/dist/chunk-6EXH3ORZ.mjs +166 -0
  121. package/dist/chunk-6EZ7FNIK.mjs +3 -0
  122. package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
  123. package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
  124. package/dist/chunk-B4P2CM2J.mjs +3 -0
  125. package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
  126. package/dist/chunk-BVEFGE4B.mjs +2 -0
  127. package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
  128. package/dist/chunk-CRJEG7MT.mjs +2 -0
  129. package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
  130. package/dist/chunk-D4EURAX4.mjs +4 -0
  131. package/dist/chunk-DQCE7TJY.mjs +5 -0
  132. package/dist/chunk-DR73Y2UJ.mjs +3 -0
  133. package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
  134. package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
  135. package/dist/chunk-EURLTOTZ.mjs +4 -0
  136. package/dist/chunk-FJYN7RTH.mjs +4 -0
  137. package/dist/chunk-FWNRPMCJ.mjs +12 -0
  138. package/dist/chunk-FWW4CSAZ.mjs +2 -0
  139. package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
  140. package/dist/chunk-GY3FAUVM.mjs +2 -0
  141. package/dist/chunk-HLYV7RIH.mjs +2 -0
  142. package/dist/chunk-HMYTM3GT.mjs +15 -0
  143. package/dist/chunk-IA4PSIMD.mjs +2 -0
  144. package/dist/chunk-JEUK3ZO7.mjs +2 -0
  145. package/dist/chunk-KTKHRC6H.mjs +2 -0
  146. package/dist/chunk-L7UP52JO.mjs +2 -0
  147. package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
  148. package/dist/chunk-N76HWBA3.mjs +10 -0
  149. package/dist/chunk-N7AZZLGT.mjs +548 -0
  150. package/dist/chunk-NAMN3RAF.mjs +2 -0
  151. package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
  152. package/dist/chunk-NG375ILQ.mjs +2 -0
  153. package/dist/chunk-NVRNLK25.mjs +6 -0
  154. package/dist/chunk-NWFBE2YA.mjs +2 -0
  155. package/dist/chunk-NWR2LT5B.mjs +2 -0
  156. package/dist/chunk-OJKMINIU.mjs +20 -0
  157. package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
  158. package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
  159. package/dist/chunk-PAREWQGB.mjs +4 -0
  160. package/dist/chunk-POQSE4C2.mjs +2 -0
  161. package/dist/chunk-PUOQ65NC.mjs +2 -0
  162. package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
  163. package/dist/chunk-PZDLJJTJ.mjs +3 -0
  164. package/dist/chunk-Q47PLKIJ.mjs +15 -0
  165. package/dist/chunk-Q4UEPUWF.mjs +2 -0
  166. package/dist/chunk-QKXMPUXC.mjs +2 -0
  167. package/dist/chunk-QOUEFUCH.mjs +16 -0
  168. package/dist/chunk-RFDZXFJF.mjs +13 -0
  169. package/dist/chunk-RKPWLGZ3.mjs +4 -0
  170. package/dist/chunk-RMECZA2K.mjs +2 -0
  171. package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
  172. package/dist/chunk-SX4LJYT6.mjs +8 -0
  173. package/dist/chunk-SXHBYZST.mjs +16 -0
  174. package/dist/chunk-TANZRINB.mjs +2 -0
  175. package/dist/chunk-TEZAWDSQ.mjs +2 -0
  176. package/dist/chunk-UBBRTJ2R.mjs +24 -0
  177. package/dist/chunk-UFNYFFKO.mjs +20 -0
  178. package/dist/chunk-UGWYKHBL.mjs +170 -0
  179. package/dist/chunk-UKSHDZZ6.mjs +2 -0
  180. package/dist/chunk-UNOYVIYR.mjs +2 -0
  181. package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
  182. package/dist/chunk-VJPFVNNW.mjs +15 -0
  183. package/dist/chunk-VS3ZMVGX.mjs +5 -0
  184. package/dist/chunk-VTKNEYT3.mjs +6 -0
  185. package/dist/chunk-VXACTLO3.mjs +3 -0
  186. package/dist/chunk-YI43RG4P.mjs +2 -0
  187. package/dist/chunk-YQGT4AB2.mjs +6 -0
  188. package/dist/chunk-ZB4H7ZN4.mjs +2 -0
  189. package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
  190. package/dist/chunk-ZTHICSSF.mjs +2 -0
  191. package/dist/chunk-ZUNVAXLH.mjs +2 -0
  192. package/dist/chunk-ZWPMCK7P.mjs +2 -0
  193. package/dist/cli.mjs +1 -1
  194. package/dist/server.mjs +1 -1
  195. package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
  196. package/dist/worker.mjs +1 -1
  197. package/kit/board.css +95 -0
  198. package/kit/board.js +250 -0
  199. package/kit/charts.js +5 -71
  200. package/kit/code.js +14 -14
  201. package/kit/report.css +255 -17
  202. package/kit/runtime.js +1600 -125
  203. package/kit/state.js +10 -1
  204. package/kit/tables.js +128 -43
  205. package/kit/ui.js +77 -35
  206. package/package.json +2 -1
  207. package/packs/gallery/card/component.json +14 -0
  208. package/packs/gallery/card/example.html +10 -0
  209. package/packs/gallery/card/preview.svg +1 -0
  210. package/packs/gallery/card/style.css +6 -0
  211. package/packs/gallery/card/template.html +6 -0
  212. package/packs/gallery/card/writing.md +14 -0
  213. package/packs/gallery/chart-card/component.json +15 -0
  214. package/packs/gallery/chart-card/example.html +14 -0
  215. package/packs/gallery/chart-card/kit.js +130 -0
  216. package/packs/gallery/chart-card/preview.svg +1 -0
  217. package/packs/gallery/chart-card/style.css +42 -0
  218. package/packs/gallery/chart-card/template.html +13 -0
  219. package/packs/gallery/chart-card/writing.md +20 -0
  220. package/packs/gallery/chart-view/component.json +20 -0
  221. package/packs/gallery/chart-view/example.html +13 -0
  222. package/packs/gallery/chart-view/kit.js +355 -0
  223. package/packs/gallery/chart-view/preview.svg +1 -0
  224. package/packs/gallery/chart-view/style.css +94 -0
  225. package/packs/gallery/chart-view/template.html +3 -0
  226. package/packs/gallery/chart-view/writing.md +22 -0
  227. package/packs/gallery/countdown/component.json +18 -0
  228. package/packs/gallery/countdown/example.html +3 -0
  229. package/packs/gallery/countdown/kit.js +156 -0
  230. package/packs/gallery/countdown/preview.svg +1 -0
  231. package/packs/gallery/countdown/style.css +130 -0
  232. package/packs/gallery/countdown/template.html +3 -0
  233. package/packs/gallery/countdown/writing.md +22 -0
  234. package/packs/gallery/habit/component.json +14 -0
  235. package/packs/gallery/habit/example.html +3 -0
  236. package/packs/gallery/habit/kit.js +295 -0
  237. package/packs/gallery/habit/preview.svg +1 -0
  238. package/packs/gallery/habit/style.css +246 -0
  239. package/packs/gallery/habit/template.html +3 -0
  240. package/packs/gallery/habit/writing.md +21 -0
  241. package/packs/gallery/heatmap/component.json +18 -0
  242. package/packs/gallery/heatmap/example.html +3 -0
  243. package/packs/gallery/heatmap/kit.js +351 -0
  244. package/packs/gallery/heatmap/preview.svg +1 -0
  245. package/packs/gallery/heatmap/style.css +201 -0
  246. package/packs/gallery/heatmap/template.html +3 -0
  247. package/packs/gallery/heatmap/writing.md +21 -0
  248. package/packs/gallery/kanban/component.json +18 -0
  249. package/packs/gallery/kanban/example.html +12 -0
  250. package/packs/gallery/kanban/kit.js +540 -0
  251. package/packs/gallery/kanban/preview.svg +1 -0
  252. package/packs/gallery/kanban/style.css +212 -0
  253. package/packs/gallery/kanban/template.html +3 -0
  254. package/packs/gallery/kanban/writing.md +20 -0
  255. package/packs/gallery/metric/component.json +22 -0
  256. package/packs/gallery/metric/example.html +15 -0
  257. package/packs/gallery/metric/kit.js +248 -0
  258. package/packs/gallery/metric/preview.svg +1 -0
  259. package/packs/gallery/metric/style.css +92 -0
  260. package/packs/gallery/metric/template.html +3 -0
  261. package/packs/gallery/metric/writing.md +23 -0
  262. package/packs/gallery/note/component.json +14 -0
  263. package/packs/gallery/note/example.html +3 -0
  264. package/packs/gallery/note/kit.js +102 -0
  265. package/packs/gallery/note/preview.svg +1 -0
  266. package/packs/gallery/note/style.css +76 -0
  267. package/packs/gallery/note/template.html +3 -0
  268. package/packs/gallery/note/writing.md +18 -0
  269. package/packs/gallery/pack.json +8 -0
  270. package/packs/gallery/recent/component.json +13 -0
  271. package/packs/gallery/recent/example.html +1 -0
  272. package/packs/gallery/recent/kit.js +238 -0
  273. package/packs/gallery/recent/preview.svg +1 -0
  274. package/packs/gallery/recent/style.css +158 -0
  275. package/packs/gallery/recent/template.html +1 -0
  276. package/packs/gallery/recent/writing.md +15 -0
  277. package/packs/gallery/static/component.json +13 -0
  278. package/packs/gallery/static/example.html +11 -0
  279. package/packs/gallery/static/preview.svg +1 -0
  280. package/packs/gallery/static/style.css +138 -0
  281. package/packs/gallery/static/template.html +1 -0
  282. package/packs/gallery/static/writing.md +18 -0
  283. package/packs/gallery/stats/component.json +13 -0
  284. package/packs/gallery/stats/example.html +1 -0
  285. package/packs/gallery/stats/kit.js +166 -0
  286. package/packs/gallery/stats/preview.svg +1 -0
  287. package/packs/gallery/stats/style.css +151 -0
  288. package/packs/gallery/stats/template.html +1 -0
  289. package/packs/gallery/stats/writing.md +11 -0
  290. package/packs/gallery/today/component.json +12 -0
  291. package/packs/gallery/today/example.html +1 -0
  292. package/packs/gallery/today/kit.js +185 -0
  293. package/packs/gallery/today/preview.svg +1 -0
  294. package/packs/gallery/today/style.css +120 -0
  295. package/packs/gallery/today/template.html +1 -0
  296. package/packs/gallery/today/writing.md +14 -0
  297. package/packs/gallery/todos/component.json +19 -0
  298. package/packs/gallery/todos/example.html +3 -0
  299. package/packs/gallery/todos/kit.js +342 -0
  300. package/packs/gallery/todos/preview.svg +1 -0
  301. package/packs/gallery/todos/style.css +267 -0
  302. package/packs/gallery/todos/template.html +3 -0
  303. package/packs/gallery/todos/writing.md +30 -0
  304. package/packs/gallery/weather/component.json +19 -0
  305. package/packs/gallery/weather/example.html +3 -0
  306. package/packs/gallery/weather/kit.js +312 -0
  307. package/packs/gallery/weather/preview.svg +1 -0
  308. package/packs/gallery/weather/style.css +258 -0
  309. package/packs/gallery/weather/template.html +3 -0
  310. package/packs/gallery/weather/writing.md +26 -0
  311. package/packs/gallery-soft/pack.json +8 -0
  312. package/release.json +12 -10
  313. package/sdk/doc-kit.d.ts +396 -5
  314. package/sdk/doc-state.d.ts +2 -0
  315. package/skill/SKILL.md +56 -23
  316. package/skill/references/block-script.md +140 -0
  317. package/skill/references/building-blocks.md +257 -0
  318. package/skill/references/extensions.md +59 -0
  319. package/skill/references/pages-and-data.md +221 -0
  320. package/skill/references/writing-html.md +6 -4
  321. package/skill/sdk/doc-kit.d.ts +396 -5
  322. package/skill/sdk/doc-state.d.ts +2 -0
  323. package/web/assets/index-BK2e_07w.js +13 -0
  324. package/web/assets/index-CWFI83mF.css +1 -0
  325. package/web/index.html +2 -2
  326. package/dist/chunk-3HIRPVLU.mjs +0 -2
  327. package/dist/chunk-5MHMDLZD.mjs +0 -4
  328. package/dist/chunk-64K7IPLO.mjs +0 -255
  329. package/dist/chunk-6QXGZM36.mjs +0 -8
  330. package/dist/chunk-6RGJOMJP.mjs +0 -16
  331. package/dist/chunk-AJ73XJF3.mjs +0 -2
  332. package/dist/chunk-AUZGSZJH.mjs +0 -13
  333. package/dist/chunk-BFE45AKL.mjs +0 -2
  334. package/dist/chunk-BXO2ZY5P.mjs +0 -2
  335. package/dist/chunk-CEH4MRCC.mjs +0 -2
  336. package/dist/chunk-CY2FNNAB.mjs +0 -16
  337. package/dist/chunk-DFUDV4LX.mjs +0 -2
  338. package/dist/chunk-DKXG4XDG.mjs +0 -2
  339. package/dist/chunk-EZQ34M3F.mjs +0 -124
  340. package/dist/chunk-EZY3WC6Z.mjs +0 -15
  341. package/dist/chunk-FUKQNOQS.mjs +0 -9
  342. package/dist/chunk-JPOSLGOC.mjs +0 -6
  343. package/dist/chunk-L3QKH4OK.mjs +0 -26
  344. package/dist/chunk-LAE5NQ4B.mjs +0 -2
  345. package/dist/chunk-LEF6DRCK.mjs +0 -59
  346. package/dist/chunk-NO5NJCHO.mjs +0 -2
  347. package/dist/chunk-NQPVAU63.mjs +0 -2
  348. package/dist/chunk-NSKUTQ5J.mjs +0 -5
  349. package/dist/chunk-O35FIXRB.mjs +0 -2
  350. package/dist/chunk-P2CAZPDP.mjs +0 -3
  351. package/dist/chunk-PL6GHLGD.mjs +0 -15
  352. package/dist/chunk-QJIR2BLP.mjs +0 -6
  353. package/dist/chunk-QUPLVPB2.mjs +0 -7
  354. package/dist/chunk-R7BNENLY.mjs +0 -4
  355. package/dist/chunk-S6O4UHLC.mjs +0 -2
  356. package/dist/chunk-SGJWQKEI.mjs +0 -2
  357. package/dist/chunk-TX6JH7TR.mjs +0 -2
  358. package/dist/chunk-TYK6VNAB.mjs +0 -10
  359. package/dist/chunk-UALRNVSE.mjs +0 -4
  360. package/dist/chunk-V3HXVQEM.mjs +0 -2
  361. package/dist/chunk-V5VXUJKG.mjs +0 -2
  362. package/dist/chunk-VBXMADMP.mjs +0 -2
  363. package/dist/chunk-VNV55RUA.mjs +0 -2
  364. package/dist/chunk-VPD2PS35.mjs +0 -2
  365. package/dist/chunk-W4VKN3GF.mjs +0 -7
  366. package/dist/chunk-W5BXXPDJ.mjs +0 -3
  367. package/dist/chunk-XUNHRCOG.mjs +0 -6
  368. package/skill/references/building-components.md +0 -367
  369. package/web/assets/index-Bh74DfsV.css +0 -1
  370. package/web/assets/index-UW0RyFFt.js +0 -13
@@ -0,0 +1,299 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { beforeCatalogRead } from "./component-catalog.mjs";
5
+ import { packsDirectory } from "./paths.mjs";
6
+ import { PACKAGE_MANIFEST, layoutOf } from "./workspace.mjs";
7
+
8
+ // Gallery's own custom blocks (自带块): the blocks of Gallery's own block packs (./block-packs.mjs), a
9
+ // folder each in the package (src/packs/<pack>/<name>/ in the source checkout, packs/<pack>/<name>/ in
10
+ // the release), each a block of the scope <pack> as any workspace's is, its manifest's id <pack>/<name>:
11
+ // the pack gallery's (Gallery 风格) and gallery-soft's (柔光风格). A workspace's first start installs each
12
+ // pack as any shared pack is installed (./block-packs.mjs installOfficialPacks); gallery block add <name>
13
+ // (gallery's, or <pack>/<name>) copies one unchanged into a workspace, components/<pack>/<name>/, and records its
14
+ // version and a fingerprint of what it copied in .gallery/blocks.json; adding it again updates a copy
15
+ // that is as it was put there, and never one whose fingerprint has changed since, which is someone's
16
+ // work (gallery block new <id> --recipe <name> starts a block of one's own from a shipped one instead).
17
+ // The scopes of Gallery's packs are kept for these: gallery block new refuses them. A custom page's
18
+ // service (phase 2) adds one the same way when a card asks for it.
19
+ //
20
+ // A workspace's copies follow Gallery (the user, 2026-11-09: a home page made with 1.0.0's cards kept
21
+ // their plain look after 1.1.0 brought the cards' design): a copy that is as it was added and behind
22
+ // Gallery's is updated in place, as gallery block add would update it, without the command
23
+ // (followShipped): as a server starts (src/server/index.mjs, production.mjs), whenever its catalog
24
+ // reads the workspace's blocks (readCatalog, by followOnRead's hook) and when the page service reads
25
+ // them (its library, an add), once a process for each block and version, told once in the server's
26
+ // log. A copy changed since it was added is someone's work and stays as it is: the log says so once,
27
+ // and 设置 › 组件列表 and gallery block list say that Gallery has a newer one and how to take it.
28
+
29
+ /** The scope of Gallery's own blocks that a bare name names (gallery block add card): the pack gallery's. */
30
+ export const SHIPPED_SCOPE = "gallery";
31
+
32
+ // What a system writes into a folder it shows, which nobody wrote: macOS's Finder (.DS_Store, and ._
33
+ // files beside each on a disk of another format), Windows' Explorer (Thumbs.db, desktop.ini). No
34
+ // fingerprint counts them, no add takes them out, and a recipe copies none.
35
+ const SYSTEM_FILE = /^(?:\.DS_Store|\._.*|Thumbs\.db|ehthumbs\.db|desktop\.ini)$/i;
36
+
37
+ /** Whether a file called `name` (its last part) is one a system wrote into a folder it showed. */
38
+ export const isSystemFile = (name) => SYSTEM_FILE.test(name);
39
+
40
+ /** The files of a folder, by their paths in it with "/", sorted, what a system wrote there left out. */
41
+ export function filesIn(folder) {
42
+ return fs.readdirSync(folder, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile() && !SYSTEM_FILE.test(entry.name))
43
+ .map((entry) => path.relative(folder, path.join(entry.parentPath, entry.name)).split(path.sep).join("/")).sort();
44
+ }
45
+
46
+ /**
47
+ * The names of a block's files in the order they are written: its manifest last, what makes a folder a
48
+ * block to whoever watches (a running server's watcher, the catalog), so that a block that appears is
49
+ * whole, and one a write stopped partway through is a folder with no manifest, which loads as nothing.
50
+ */
51
+ export const writingOrder = (names) => [...names.filter((name) => name !== PACKAGE_MANIFEST), ...(names.includes(PACKAGE_MANIFEST) ? [PACKAGE_MANIFEST] : [])];
52
+
53
+ /** What a folder holds, as a hash of each file's path and bytes (what a system wrote there aside), or null when there is no such folder. */
54
+ export function fingerprintOf(folder) {
55
+ if (!fs.existsSync(folder)) return null;
56
+ return fingerprintOfFiles(filesIn(folder).map((name) => ({ name, data: fs.readFileSync(path.join(folder, name)) })));
57
+ }
58
+
59
+ /** What fingerprintOf says of a folder that holds `files` alone ([{ name, data }], each by its path in it with "/"). */
60
+ export function fingerprintOfFiles(files) {
61
+ const hash = createHash("sha256");
62
+ const named = files.filter(({ name }) => !SYSTEM_FILE.test(name.split("/").at(-1))).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
63
+ for (const { name, data } of named) hash.update(`${name}\u0000`).update(data).update("\n");
64
+ return hash.digest("hex").slice(0, 16);
65
+ }
66
+
67
+ // The folders right in `directory`, by name, or none where it is not there.
68
+ const foldersOf = (directory) => {
69
+ try {
70
+ return fs.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
71
+ } catch {
72
+ return [];
73
+ }
74
+ };
75
+
76
+ /** The ids of Gallery's own packs in `directory` (the package's): each folder of it, by name. */
77
+ export const shippedPackIds = (directory = packsDirectory) => foldersOf(directory);
78
+
79
+ /**
80
+ * Gallery's own blocks in `directory` (the package's packs): [{ name, id, pack, folder, version, title:
81
+ * { zh, en }, description: { zh, en }, places }], by pack and name; a folder whose manifest does not read
82
+ * as its pack's block of its name is none.
83
+ */
84
+ export function shippedBlocks(directory = packsDirectory) {
85
+ return foldersOf(directory).flatMap((pack) => foldersOf(path.join(directory, pack)).flatMap((name) => {
86
+ const folder = path.join(directory, pack, name);
87
+ try {
88
+ const manifest = JSON.parse(fs.readFileSync(path.join(folder, PACKAGE_MANIFEST), "utf8"));
89
+ if (manifest.id !== `${pack}/${name}`) return [];
90
+ return [{ name, id: manifest.id, pack, folder, version: manifest.version, title: manifest.name, description: manifest.description, places: manifest.places ?? ["document"] }];
91
+ } catch {
92
+ return [];
93
+ }
94
+ }));
95
+ }
96
+
97
+ /** The shipped block `written` names (card, gallery/card, gallery-soft/todos: a bare name is the pack gallery's), or null. */
98
+ export function shippedBlock(written, directory = packsDirectory) {
99
+ const id = written.includes("/") ? written : `${SHIPPED_SCOPE}/${written}`;
100
+ return shippedBlocks(directory).find((block) => block.id === id) ?? null;
101
+ }
102
+
103
+ /** Whether `id` is of a scope of Gallery's own packs (gallery/…, gallery-soft/…). */
104
+ export const isShippedId = (id, directory = packsDirectory) => shippedPackIds(directory).includes(String(id).split("/")[0]);
105
+
106
+ /**
107
+ * .gallery/blocks.json: { "<id>": { version, fingerprint } } of each block added or installed (one of
108
+ * Gallery's own, or of a pack), and `packs`, { "<pack id>": { version, fingerprint, blocks, official? } } of
109
+ * each pack installed (./block-packs.mjs); {} for none or a file that does not read.
110
+ */
111
+ export function recordOf(root) {
112
+ try {
113
+ const record = JSON.parse(fs.readFileSync(layoutOf(root).blocks, "utf8"));
114
+ return record && typeof record === "object" && !Array.isArray(record) ? record : {};
115
+ } catch {
116
+ return {};
117
+ }
118
+ }
119
+
120
+ /** .gallery/blocks.json as `record` says. */
121
+ export function writeRecord(root, record) {
122
+ const file = layoutOf(root).blocks;
123
+ fs.mkdirSync(path.dirname(file), { recursive: true });
124
+ fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`);
125
+ }
126
+
127
+ /** Where a block of the workspace at `root` is. */
128
+ const folderIn = (root, id) => path.join(layoutOf(root).components, ...id.split("/"));
129
+
130
+ /**
131
+ * What the workspace at `root` holds of the shipped `block`: { added (a folder of its id is there),
132
+ * version (what its manifest says, or null), changed (its files are not what was added: changed since,
133
+ * or put there by some other hand), behind (it is as added, and Gallery's is another now) }.
134
+ */
135
+ export function shippedState(root, block) {
136
+ const folder = folderIn(root, block.id);
137
+ const now = fingerprintOf(folder);
138
+ if (now === null) return { added: false, version: null, changed: false, behind: false };
139
+ let version = null;
140
+ try {
141
+ version = JSON.parse(fs.readFileSync(path.join(folder, PACKAGE_MANIFEST), "utf8")).version ?? null;
142
+ } catch {}
143
+ const kept = recordOf(root)[block.id];
144
+ const shipped = fingerprintOf(block.folder);
145
+ const changed = now !== shipped && now !== kept?.fingerprint;
146
+ return { added: true, version, changed, behind: !changed && now !== shipped };
147
+ }
148
+
149
+ /**
150
+ * Copies the shipped `block` into the workspace at `root`, unchanged: { did: "added", "updated" (from a
151
+ * copy as it was added, `from` its version) or "same" (nothing to do), folder }. A copy whose files
152
+ * changed since it was added (or that no add put there) is left as it is: an error with code "changed".
153
+ * The manifest is written last, what makes a folder a block to a running server's watcher, so that a
154
+ * block that appears is whole; files of the old copy that the new one has not go first.
155
+ */
156
+ export function addShipped(root, block) {
157
+ const folder = folderIn(root, block.id);
158
+ const state = shippedState(root, block);
159
+ if (state.changed) throw Object.assign(new Error(`${block.id} was changed after it was added`), { code: "changed", folder });
160
+ const shipped = fingerprintOf(block.folder);
161
+ const record = recordOf(root);
162
+ if (state.added && !state.behind) {
163
+ if (record[block.id]?.fingerprint !== shipped) writeRecord(root, { ...record, [block.id]: { version: block.version, fingerprint: shipped } });
164
+ return { did: "same", folder };
165
+ }
166
+ const files = filesIn(block.folder);
167
+ if (state.added) for (const old of filesIn(folder)) if (!files.includes(old)) fs.rmSync(path.join(folder, old));
168
+ for (const file of writingOrder(files)) {
169
+ fs.mkdirSync(path.dirname(path.join(folder, file)), { recursive: true });
170
+ fs.copyFileSync(path.join(block.folder, file), path.join(folder, file));
171
+ }
172
+ writeRecord(root, { ...record, [block.id]: { version: block.version, fingerprint: shipped } });
173
+ return { did: state.added ? "updated" : "added", from: state.version, folder };
174
+ }
175
+
176
+ // A version's numbers (1.10.0: [1, 10, 0]), or null for one that is not three of them.
177
+ const numbersOf = (version) => (/^\d+\.\d+\.\d+$/.test(String(version ?? "")) ? version.split(".").map(Number) : null);
178
+
179
+ /** Whether version `a` comes after `b` (both x.y.z); `b` none or not of that form: whether they differ. */
180
+ export function versionAfter(a, b) {
181
+ const [x, y] = [numbersOf(a), numbersOf(b)];
182
+ if (!x || !y) return a !== b;
183
+ const at = x.findIndex((number, index) => number !== y[index]);
184
+ return at !== -1 && x[at] > y[at];
185
+ }
186
+
187
+ /**
188
+ * The version of the shipped `block` that the workspace at `root` does not take because its copy was
189
+ * changed since it was added (or no add put it there), when Gallery's comes after the copy's; else null.
190
+ */
191
+ export function shippedNewer(root, block) {
192
+ const state = shippedState(root, block);
193
+ return state.changed && versionAfter(block.version, state.version) ? block.version : null;
194
+ }
195
+
196
+ const looked = new Map(); // a workspace's root and a shipped block's folder -> the version of it looked at
197
+ let told = () => {}; // where followShipped tells what it did when it is not given a log: the server's (followOnRead)
198
+
199
+ // What followShipped did, as lines of the log.
200
+ function followedLines(root, { updated, kept, failed }) {
201
+ const lines = [];
202
+ if (updated.length) lines.push(`Gallery's own blocks in ${root}, as they were added, updated to this Gallery's: ${updated.map(({ id, from, to }) => `${id} ${from ?? "?"} -> ${to}`).join(", ")}`);
203
+ for (const { id, version, shipped } of kept) lines.push(`${id} in ${root} was changed after it was added, so it stays at ${version ?? "its version"}: Gallery's is ${shipped} (设置 › 组件列表 says how to take it)`);
204
+ for (const { id, to, why } of failed) lines.push(`${id} in ${root} could not be updated to Gallery's ${to}: ${why}`);
205
+ return lines;
206
+ }
207
+
208
+ /**
209
+ * Bring the workspace at `root`'s copies of Gallery's own blocks (of `ids`, by default each one) up to
210
+ * Gallery's (in `directory`): one that is as it was added and behind is updated as addShipped updates
211
+ * it (the manifest written last); one changed since it was added is kept as it is. Each block and
212
+ * version is looked at once a process, a later call for it doing nothing, so that what reads the blocks
213
+ * again and again (a server's catalog, its page service) pays once. `log`: told once of what was done,
214
+ * a line each (by default the one followOnRead set: none in a command). Never throws: { updated: [{ id,
215
+ * from, to }], kept: [{ id, version, shipped }] (changed, Gallery's newer), failed: [{ id, to, why }] }.
216
+ */
217
+ export function followShipped(root, { ids = null, directory = packsDirectory, log = null } = {}) {
218
+ const done = { updated: [], kept: [], failed: [] };
219
+ const key = path.resolve(root);
220
+ for (const block of shippedBlocks(directory)) {
221
+ if (ids && !ids.includes(block.id)) continue;
222
+ const at = `${key}\u0000${path.resolve(block.folder)}`;
223
+ if (looked.get(at) === block.version) continue;
224
+ looked.set(at, block.version);
225
+ try {
226
+ const state = shippedState(root, block);
227
+ if (state.changed && versionAfter(block.version, state.version)) done.kept.push({ id: block.id, version: state.version, shipped: block.version });
228
+ if (!state.behind) continue;
229
+ if (addShipped(root, block).did === "updated") done.updated.push({ id: block.id, from: state.version, to: block.version });
230
+ } catch (error) {
231
+ done.failed.push({ id: block.id, to: block.version, why: error?.message ?? String(error) });
232
+ }
233
+ }
234
+ for (const line of followedLines(key, done)) (log ?? told)(line);
235
+ return done;
236
+ }
237
+
238
+ const following = new Set(); // the workspaces whose server runs in this process (followOnRead), by their resolved root
239
+ const keyOf = (root) => (process.platform === "win32" ? path.resolve(root).toLowerCase() : path.resolve(root));
240
+
241
+ /**
242
+ * From now on in this process, the catalog brings the copies of Gallery's own blocks of the workspace at
243
+ * `root` up to Gallery's before it reads them (followShipped, its ids those of the scopes of Gallery's
244
+ * packs it finds), and what is done of it is told to `log` (a line): a server's, set as it starts for
245
+ * its own workspace (another's blocks are its own server's to follow, and a test's process reads the
246
+ * repository's reports/ too, which nothing of it is to write).
247
+ */
248
+ export function followOnRead(root, log) {
249
+ following.add(keyOf(root));
250
+ told = log;
251
+ beforeCatalogRead((read, ids) => {
252
+ if (following.has(keyOf(read))) followShipped(read, { ids });
253
+ }, shippedPackIds());
254
+ }
255
+
256
+ // The words of a shipped block's files that name it, and what a recipe's copy names instead: its id,
257
+ // and the id with "-" for "/" (a class, a chart type).
258
+ const namesOf = (id) => [id, id.replace("/", "-")];
259
+ const escaped = (words) => words.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
260
+
261
+ /**
262
+ * The shipped block whose preview.svg the file `bytes` is, byte for byte, as Gallery ships it or as a
263
+ * recipe of it for the block `id` has it (every word that named it naming `id`), or null: a block made
264
+ * by --recipe keeps its recipe's picture until someone draws its own, which gallery block check notes.
265
+ */
266
+ export function shippedPreviewOf(bytes, id, directory = packsDirectory) {
267
+ for (const block of shippedBlocks(directory)) {
268
+ if (block.id === id) continue; // a shipped block's own picture is its own
269
+ let shipped;
270
+ try {
271
+ shipped = fs.readFileSync(path.join(block.folder, "preview.svg"));
272
+ } catch {
273
+ continue;
274
+ }
275
+ if (shipped.equals(bytes) || Buffer.from(recipeFiles(block, id)["preview.svg"] ?? "").equals(bytes)) return block;
276
+ }
277
+ return null;
278
+ }
279
+
280
+ /**
281
+ * The files of a new block `id` made from the shipped `block` (gallery block new <id> --recipe <name>),
282
+ * by name, the manifest last: each as Gallery ships it, every word that names the shipped block naming
283
+ * the new one, all in one pass, so that a new name holding an old one (local/gallery-card, made of
284
+ * gallery/card) is not renamed again; its version 0.1.0.
285
+ */
286
+ export function recipeFiles(block, id) {
287
+ const [from, to] = [namesOf(block.id), namesOf(id)];
288
+ const names = new RegExp(from.map(escaped).join("|"), "g");
289
+ const files = {};
290
+ for (const file of filesIn(block.folder)) {
291
+ const bytes = fs.readFileSync(path.join(block.folder, file));
292
+ if (/\.(?:json|html|md|css|js|svg)$/.test(file)) {
293
+ let text = bytes.toString("utf8").replace(names, (name) => to[from.indexOf(name)]);
294
+ if (file === PACKAGE_MANIFEST) text = text.replace(/"version":\s*"[^"]*"/, '"version": "0.1.0"');
295
+ files[file] = text;
296
+ } else files[file] = bytes;
297
+ }
298
+ return Object.fromEntries(writingOrder(Object.keys(files)).map((name) => [name, files[name]]));
299
+ }
@@ -13,11 +13,21 @@ import { isMissingValue, numberReading } from "./number-reading.mjs";
13
13
  // copy or delete either block (a copied pair takes a key of its own:
14
14
  // src/editor/page/clipboard/own/chart-keys.mjs, and the commit makes sure of it against the file:
15
15
  // src/editor/source/chart-keys.mjs).
16
+ //
17
+ // A view of a table (plan 阶段 4: a task board, a count, a chart card of a custom block's) is the same
18
+ // link: an instance of a custom block whose manifest's needs hold "tables" names the table by that key
19
+ // (VIEW_FROM), reads its rows as a chart does (the kit's runtime/table-reading.js, context.table()) and
20
+ // changes them only by asking the page editor (an intent: docs/editor.md, "Table views"). A key is one
21
+ // table's, which any number of charts and views read: TABLE_READERS are the attributes that read one.
16
22
 
17
23
  /** On a chart's host: the key of the table it draws. */
18
24
  export const CHART_DATA = "data-doc-chart-data";
19
25
  /** On a table's frame (.doc-table-wrap): the key a chart reads it by. */
20
26
  export const CHART_SOURCE = "data-doc-chart-source";
27
+ /** On an instance of a custom block whose needs hold "tables": the key of the table it is a view of (context.table()). */
28
+ export const VIEW_FROM = "data-doc-from";
29
+ /** The attributes that name a table by its key, a chart's host's and a view's. */
30
+ export const TABLE_READERS = [CHART_DATA, VIEW_FROM];
21
31
  /** A key: a letter, then letters, digits, "_" or "-", 32 at most. */
22
32
  export const CHART_KEY = /^[A-Za-z][\w-]{0,31}$/;
23
33
 
@@ -38,6 +48,28 @@ const FORMULA = /\\\(([\s\S]*?)\\\)|\\\[([\s\S]*?)\\\]|\$\$([\s\S]*?)\$\$/g;
38
48
  */
39
49
  export const cellWords = (text) => text.replace(FORMULA, (_, inline, display, dollars) => inline ?? display ?? dollars).replaceAll(ZWSP, "").replace(/\s+/g, " ").trim();
40
50
 
51
+ // A formula KaTeX drew on a page: its TeX is its annotation (its drawing's text is its glyphs, twice over).
52
+ const TEX = 'annotation[encoding="application/x-tex"]';
53
+
54
+ /**
55
+ * A cell's text on a page as the file writes it, as near as the page tells: a formula KaTeX drew as its
56
+ * TeX, the editor's chip of an empty one as nothing (the file's \(\)). Its words are then cellWords'.
57
+ * For the kit, which reads a table on a page (runtime/table-reading.js: a chart's, a view's and a package's rows).
58
+ * @param {Node} node
59
+ * @returns {string}
60
+ */
61
+ export function cellText(node) {
62
+ if (node.nodeType === 3) return /** @type {Text} */ (node).data;
63
+ if (node.nodeType !== 1) return "";
64
+ const element = /** @type {Element} */ (node);
65
+ if (element.hasAttribute("data-ge-empty")) return "";
66
+ const tex = element.classList.contains("katex") ? element.querySelector(TEX) : null;
67
+ if (tex) return tex.textContent ?? "";
68
+ let text = "";
69
+ for (const child of element.childNodes) text += cellText(child);
70
+ return text;
71
+ }
72
+
41
73
  // A header's data-sort-type that says its column holds no numbers: its values are its words.
42
74
  const WORD_TYPES = new Set(["text", "date"]);
43
75
 
@@ -66,7 +98,7 @@ export function cellValue(cell, type = null) {
66
98
  * row of header cells alone; null when it has neither), its header cells' data-sort-type (null where
67
99
  * none), its rows of data in the file's order (never a <tfoot>'s), each cell { words, sort } (its
68
100
  * words, cellWords', and its data-sort-value, or null), and whether a cell of the header or the rows
69
- * spans rows or columns (charts/from-table.js tableCells reads it from the page,
101
+ * spans rows or columns (runtime/table-reading.js readTable reads it from the page,
70
102
  * chart-declarations.mjs tableCells from the file).
71
103
  *
72
104
  * `data` is what d3.csvParse gives: an object for each row of data, in their order, its values by
@@ -0,0 +1,83 @@
1
+ import { parse } from "parse5";
2
+ import { TODO } from "./components/list.mjs";
3
+ import { sha256 } from "./files.mjs";
4
+ import { attr, BLOCK_LEVEL, elementsOf, hasClass } from "./html-tree.mjs";
5
+
6
+ // A document's to-dos as its pages show them (the items of its to-do lists, ul.doc-todo): what the
7
+ // workspace's facts list (src/workbench/index.mjs, workspace.query("todos")) and what a check of one
8
+ // from another page finds it by (src/editor/todos.mjs, workspace.setTodo: the plan's 5.1), the one
9
+ // reading for both, so that a ref the facts gave names the item the write looks for. Pure but for the
10
+ // parser and the hash: no DOM.
11
+
12
+ const SKIPPED = new Set(["script", "style", "template", "noscript", "svg", "math"]);
13
+ const HEADING = /^h([1-6])$/;
14
+ const tidy = (text) => text.replace(/[\s​]+/g, " ").trim();
15
+
16
+ // The words an element holds, as a reader reads them: its text, a line break a space, nothing of a script,
17
+ // a style or a drawing; `stop(child)`: a child left out with what it holds.
18
+ function wordsOf(node, stop = () => false) {
19
+ let text = "";
20
+ const visit = (current) => {
21
+ for (const child of current.childNodes ?? []) {
22
+ if (child.nodeName === "#text") text += child.value;
23
+ else if (child.tagName === "br") text += " ";
24
+ else if (child.tagName && !SKIPPED.has(child.tagName) && !stop(child)) visit(child);
25
+ }
26
+ };
27
+ visit(node);
28
+ return tidy(text);
29
+ }
30
+
31
+ // A to-do's own words: what the item holds before its lists and the blocks it holds (a paragraph, a
32
+ // picture), or, where it holds no words of its own (an item written <li><p>…</p></li>), its first block's.
33
+ function itemWords(item) {
34
+ const own = wordsOf(item, (child) => BLOCK_LEVEL.has(child.tagName));
35
+ if (own) return own;
36
+ const first = elementsOf(item).find((child) => BLOCK_LEVEL.has(child.tagName) && child.tagName !== "ul" && child.tagName !== "ol");
37
+ return first ? wordsOf(first, (child) => child.tagName === "ul" || child.tagName === "ol") : "";
38
+ }
39
+
40
+ /**
41
+ * The to-dos of a document's `text` (its page's HTML, as its pages see it), in their order: [{ text,
42
+ * done, block, path, nth }], `path` the headings above it (their words, the outermost first: an h2 under
43
+ * an h1 of the body, the document's title never one), `nth` which of the to-dos of the same words under
44
+ * the same path it is (0 the first). With `at`, each says where its <li> starts in `text` too (`at`, the
45
+ * offset of its start tag), which the editor's units name it by (source/units.mjs elStart).
46
+ */
47
+ export function todosOf(text, { at = false } = {}) {
48
+ const root = parse(String(text), { sourceCodeLocationInfo: at });
49
+ const headings = []; // [{ level, words }]
50
+ const todos = [];
51
+ const seen = new Map();
52
+ const visit = (node) => {
53
+ for (const child of elementsOf(node)) {
54
+ if (SKIPPED.has(child.tagName)) continue;
55
+ const heading = HEADING.exec(child.tagName);
56
+ if (heading && !hasClass(child, "doc-title")) {
57
+ const level = Number(heading[1]);
58
+ while (headings.length && headings.at(-1).level >= level) headings.pop();
59
+ const words = wordsOf(child);
60
+ if (words) headings.push({ level, words });
61
+ continue;
62
+ }
63
+ if (child.tagName === "li" && child.parentNode?.tagName === "ul" && hasClass(child.parentNode, TODO)) {
64
+ const path = headings.map(({ words }) => words);
65
+ const words = itemWords(child);
66
+ const key = JSON.stringify([words, path]);
67
+ const nth = seen.get(key) ?? 0;
68
+ seen.set(key, nth + 1);
69
+ todos.push({ text: words, done: attr(child, "data-checked") !== undefined, block: attr(child, "data-block-id") ?? null, path, nth, ...(at ? { at: child.sourceCodeLocation?.startOffset ?? -1 } : {}) });
70
+ }
71
+ visit(child);
72
+ }
73
+ };
74
+ visit(root);
75
+ return todos;
76
+ }
77
+
78
+ /**
79
+ * The version of a document's file the facts read its to-dos from (the hash of its text, as a to-do's
80
+ * ref carries it: workspace.query("todos")), which a check of one finds it by its place in (`nth`) while
81
+ * the file is still that one.
82
+ */
83
+ export const fileVersion = (source) => sha256(source).slice(0, 16);
@@ -0,0 +1,24 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+
4
+ // The user's own folders for Gallery, outside every workspace: "cache", what may be thrown away (the
5
+ // update check's answer, src/cli/updates.mjs), and "data", what Gallery keeps of the machine (the
6
+ // registry of the workspaces used on it, ./workspace-registry.mjs). One function, so that every system
7
+ // finds them alike. Node's built-ins only: gallery --version reads it, and stays as fast as Node's start.
8
+
9
+ /**
10
+ * The folder of `kind` ("cache" or "data"): GALLERY_CACHE_DIR or GALLERY_DATA_DIR when set (the tests
11
+ * name folders of their own); else on Windows %LOCALAPPDATA%\gallery for both; on macOS
12
+ * ~/Library/Caches/gallery and ~/Library/Application Support/gallery; elsewhere $XDG_CACHE_HOME/gallery
13
+ * (~/.cache/gallery) and $XDG_STATE_HOME/gallery (~/.local/state/gallery). Only a path: nothing is made.
14
+ */
15
+ export function userDirectory(kind, { env = process.env, platform = process.platform, home = os.homedir() } = {}) {
16
+ const cache = kind === "cache";
17
+ const own = cache ? env.GALLERY_CACHE_DIR : env.GALLERY_DATA_DIR;
18
+ if (own) return path.resolve(own);
19
+ if (platform === "win32") return path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "gallery");
20
+ if (platform === "darwin") return path.join(home, "Library", cache ? "Caches" : "Application Support", "gallery");
21
+ return cache
22
+ ? path.join(env.XDG_CACHE_HOME || path.join(home, ".cache"), "gallery")
23
+ : path.join(env.XDG_STATE_HOME || path.join(home, ".local", "state"), "gallery");
24
+ }
@@ -0,0 +1,14 @@
1
+ // Gallery's versions compared, with nothing imported: the preview server's choice of a server to use
2
+ // or replace (./preview-server.mjs) and gallery --version's notes (src/cli/updates.mjs, which loads
3
+ // nothing it can do without) read it.
4
+
5
+ /** -1, 0 or 1 as version `a` is older than, the same as or newer than `b` (numbers by dots: 0.1.0 < 0.1.10 < 0.2.0). */
6
+ export function compareVersions(a, b) {
7
+ const parts = (version) => String(version ?? "0").split(/[.+-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
8
+ const [x, y] = [parts(a), parts(b)];
9
+ for (let index = 0; index < 3; index++) if ((x[index] ?? 0) !== (y[index] ?? 0)) return (x[index] ?? 0) < (y[index] ?? 0) ? -1 : 1;
10
+ return 0;
11
+ }
12
+
13
+ /** A released version as npm writes one, numbers alone (0.1.2): what a registry's answer must be to be believed. */
14
+ export const RELEASED = /^\d{1,6}\.\d{1,6}\.\d{1,6}$/;
@@ -0,0 +1,134 @@
1
+ import { iconKind } from "./emoji/icon-value.mjs";
2
+
3
+ // A web link of the sidebar (docs/workbench.md "Web links"): a folder at the workspace's root, named by
4
+ // a slug from the pool documents and custom pages share, holding link.json alone:
5
+ // { "url": "https://open-meteo.com/en/docs", "title": "天气接口文档", "icon": "🌤️", "siteIcon": "data:image/png;base64,…" }
6
+ // `url` an http or https address of 2048 characters at most, `title` 1 to 200 characters, `icon` an
7
+ // emoji or an icon of the 图标 tab, the user's (no picture: a link keeps no files), `siteIcon` the site's
8
+ // own icon, fetched once as the link is added (src/editor/unfurl.mjs siteIcon) and kept in the file as a
9
+ // data: address (PNG, ICO, SVG, JPEG, WebP or GIF, 16 KB at most: SITE_ICON_BYTES), shown when the
10
+ // user chose none (the grid globe when neither is there). The workbench shows it in its
11
+ // content area in a sandboxed frame (features/link/LinkView.tsx), never as a document: no session, no
12
+ // export, no check, no search of its text. A folder holding doc.html (or report.html) is a document
13
+ // whatever else it holds. What is read and checked here is the same on the server, in the gallery
14
+ // command and in the workbench; reading and writing the file is ./workspace.mjs's (Node's).
15
+
16
+ /** A link's file in its folder. */
17
+ export const LINK_FILE = "link.json";
18
+ export const LINK_LIMITS = { url: 2048, title: 200 };
19
+ /** The largest site icon a link keeps, in bytes (its data: address holds them in base64). */
20
+ export const SITE_ICON_BYTES = 16 * 1024;
21
+ /** The media types of a site icon a link keeps. */
22
+ export const SITE_ICON_TYPES = ["image/png", "image/x-icon", "image/vnd.microsoft.icon", "image/svg+xml", "image/jpeg", "image/webp", "image/gif"];
23
+
24
+ /** Whether `value` is a site icon a link keeps: a base64 data: address of one of SITE_ICON_TYPES, SITE_ICON_BYTES at most. */
25
+ export function isSiteIcon(value) {
26
+ if (typeof value !== "string") return false;
27
+ const match = /^data:([a-z+./-]+);base64,([A-Za-z0-9+/]+={0,2})$/.exec(value);
28
+ if (!match || !SITE_ICON_TYPES.includes(match[1])) return false;
29
+ const bytes = (match[2].length * 3) / 4 - (match[2].endsWith("==") ? 2 : match[2].endsWith("=") ? 1 : 0);
30
+ return bytes > 0 && bytes <= SITE_ICON_BYTES;
31
+ }
32
+
33
+ /**
34
+ * The sandbox of a web page's frame, the embed block's (Notion's): scripts, its own origin (a page of
35
+ * another site, which can then not reach the workbench's), forms, popups that leave the sandbox, a
36
+ * top navigation only by the reader's own click.
37
+ */
38
+ export const WEB_SANDBOX = "allow-scripts allow-popups allow-top-navigation-by-user-activation allow-forms allow-same-origin allow-storage-access-by-user-activation allow-popups-to-escape-sandbox";
39
+
40
+ const tidy = (value) => (typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "");
41
+
42
+ /**
43
+ * Address `value` as a link takes it: { url } (the URL's own spelling, a scheme written in) or
44
+ * { refused, message } — "url" (no address), "scheme" (not http or https: javascript:, file:, data:…),
45
+ * "long" (over 2048 characters). An address written without a scheme (example.com/a) is taken as https.
46
+ */
47
+ export function linkAddress(value) {
48
+ const written = tidy(value);
49
+ if (!written) return { refused: "url", message: "网址不能是空的" };
50
+ const withScheme = /^[a-z][a-z0-9+.-]*:/i.test(written) ? written : `https://${written}`;
51
+ let url;
52
+ try {
53
+ url = new URL(withScheme);
54
+ } catch {
55
+ return { refused: "url", message: "这不是一个网址" };
56
+ }
57
+ if (url.protocol !== "http:" && url.protocol !== "https:") return { refused: "scheme", message: "只能链接 http 或 https 的网址" };
58
+ if (!url.hostname) return { refused: "url", message: "这不是一个网址" };
59
+ if (url.href.length > LINK_LIMITS.url) return { refused: "long", message: "网址太长了(最多 2048 个字符)" };
60
+ return { url: url.href };
61
+ }
62
+
63
+ /** Whether `icon` is one a link takes: an emoji or an icon of the 图标 tab. */
64
+ export const linkIcon = (icon) => ["emoji", "glyph"].includes(iconKind(icon));
65
+
66
+ /**
67
+ * A link's file as it is to be written: { value: { url, title, icon?, siteIcon? } } or { refused, message }
68
+ * (linkAddress's, "title": empty or over 200 characters, "icon": no emoji nor icon of the 图标 tab). A site
69
+ * icon that is none (isSiteIcon) is left out, never refused: the add never fails for it.
70
+ */
71
+ export function linkValue({ url, title, icon = null, siteIcon = null }) {
72
+ const address = linkAddress(url);
73
+ if (address.refused) return address;
74
+ const name = tidy(title);
75
+ if (!name || name.length > LINK_LIMITS.title) return { refused: "title", message: "名称不能是空的,也不能超过 200 个字" };
76
+ if (icon !== null && icon !== undefined && icon !== "" && !linkIcon(icon)) return { refused: "icon", message: "图标只能是表情或图标页签里的图标" };
77
+ return { value: { url: address.url, title: name, ...(icon ? { icon } : {}), ...(isSiteIcon(siteIcon) ? { siteIcon } : {}) } };
78
+ }
79
+
80
+ /** What link.json's text says, as a link: { url, title, icon?, siteIcon? }, or null for a file that is no link (one written by hand wrong). */
81
+ export function readLinkText(text) {
82
+ let value;
83
+ try {
84
+ value = JSON.parse(String(text).replace(/^/, ""));
85
+ } catch {
86
+ return null;
87
+ }
88
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
89
+ const made = linkValue({ url: value.url, title: value.title, icon: linkIcon(value.icon) ? value.icon : null, siteIcon: value.siteIcon });
90
+ return made.value ?? null;
91
+ }
92
+
93
+ /**
94
+ * Whether address `url` is this Gallery's own (`port` its server's): the workbench's host by any of
95
+ * its names (127.0.0.1, localhost, ::1) and port. A link there is refused: framed with the sandbox's
96
+ * allow-same-origin and scripts, the workbench's own page could reach the workbench.
97
+ */
98
+ export function ownAddress(url, port) {
99
+ let parsed;
100
+ try {
101
+ parsed = new URL(url);
102
+ } catch {
103
+ return false;
104
+ }
105
+ const host = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
106
+ const given = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
107
+ return ["127.0.0.1", "localhost", "::1", "0.0.0.0"].includes(host) && String(port) === given;
108
+ }
109
+
110
+ /**
111
+ * A slug made of words: their Latin letters and digits, lowercase, kebab-case, 60 characters at most
112
+ * (cut where a word ends); "" when none of them is a word of three letters (a title in Chinese, or one
113
+ * whose Latin is a version alone: "… v2.2 …"). gallery import's, and a link's.
114
+ */
115
+ export const kebab = (text) => {
116
+ const words = String(text ?? "").normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
117
+ if (!/[a-z]{3}/.test(words)) return "";
118
+ return words.length <= 60 ? words : words.slice(0, 61).replace(/-[^-]*$/, "") || words.slice(0, 60);
119
+ };
120
+
121
+ /**
122
+ * The stem of a new link's slug (a number added after it when it is taken: the workspace's freeSlug):
123
+ * its title's Latin words, else its site's name (open-meteo of open-meteo.com, www passed over), else "link".
124
+ */
125
+ export function linkStem(title, url) {
126
+ const words = kebab(title);
127
+ if (words) return words;
128
+ let host = "";
129
+ try {
130
+ host = new URL(url).hostname.replace(/^www\./, "");
131
+ } catch {}
132
+ const site = kebab(host.split(".")[0] ?? "");
133
+ return site || "link";
134
+ }