@heycys/gallery 0.1.1 → 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 (473) 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/columns/page.mjs +4 -4
  5. package/browser/editor/components/embed/index.mjs +2 -2
  6. package/browser/editor/components/embed/page.mjs +1 -1
  7. package/browser/editor/components/file/page.mjs +4 -4
  8. package/browser/editor/components/flowchart/add.mjs +7 -6
  9. package/browser/editor/components/flowchart/page.mjs +1 -1
  10. package/browser/editor/components/heading/index.mjs +7 -6
  11. package/browser/editor/components/icons.mjs +11 -0
  12. package/browser/editor/components/image/crop.mjs +6 -9
  13. package/browser/editor/components/image/index.mjs +2 -0
  14. package/browser/editor/components/image/page.mjs +172 -90
  15. package/browser/editor/components/installed/index.mjs +15 -4
  16. package/browser/editor/components/installed/page.mjs +103 -25
  17. package/browser/editor/components/installed/served.mjs +6 -5
  18. package/browser/editor/components/instances.mjs +18 -0
  19. package/browser/editor/components/list/page.mjs +3 -6
  20. package/browser/editor/components/media/icons.mjs +13 -8
  21. package/browser/editor/components/media/page.mjs +5 -3
  22. package/browser/editor/components/media/toolbar.mjs +12 -3
  23. package/browser/editor/components/paragraph/index.mjs +4 -3
  24. package/browser/editor/components/registry.mjs +30 -9
  25. package/browser/editor/components/table/index.mjs +61 -2
  26. package/browser/editor/components/table/page.mjs +1 -1
  27. package/browser/editor/page/after/index.mjs +288 -0
  28. package/browser/editor/page/align.mjs +91 -0
  29. package/browser/editor/page/annotate/card.mjs +3 -6
  30. package/browser/editor/page/autosave.mjs +310 -37
  31. package/browser/editor/page/beside.mjs +5 -6
  32. package/browser/editor/page/block-menu/index.mjs +6 -3
  33. package/browser/editor/page/blocks.mjs +3 -1
  34. package/browser/editor/page/board/client.mjs +57 -0
  35. package/browser/editor/page/board/hint.mjs +182 -0
  36. package/browser/editor/page/board/history.mjs +119 -0
  37. package/browser/editor/page/board/index.mjs +1059 -0
  38. package/browser/editor/page/board/motion.mjs +177 -0
  39. package/browser/editor/page/board/settings.mjs +210 -0
  40. package/browser/editor/page/caret.mjs +1 -1
  41. package/browser/editor/page/changes/bar.mjs +6 -14
  42. package/browser/editor/page/changes/index.mjs +2 -2
  43. package/browser/editor/page/clipboard/import/html.mjs +29 -6
  44. package/browser/editor/page/clipboard/import/inline.mjs +32 -0
  45. package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
  46. package/browser/editor/page/clipboard/import/read.mjs +6 -1
  47. package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
  48. package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
  49. package/browser/editor/page/clipboard/index.mjs +133 -15
  50. package/browser/editor/page/clipboard/own/blocks.mjs +13 -14
  51. package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
  52. package/browser/editor/page/clipboard/own/describe.mjs +6 -2
  53. package/browser/editor/page/clipboard/own/selection.mjs +1 -1
  54. package/browser/editor/page/columns/index.mjs +48 -46
  55. package/browser/editor/page/commit.mjs +80 -22
  56. package/browser/editor/page/conflicts.mjs +7 -7
  57. package/browser/editor/page/core.mjs +34 -16
  58. package/browser/editor/page/custom-page.mjs +110 -0
  59. package/browser/editor/page/editor.mjs +71 -17
  60. package/browser/editor/page/emoji-picker/styles.mjs +7 -12
  61. package/browser/editor/page/emoji-picker/upload.mjs +2 -2
  62. package/browser/editor/page/handle/index.mjs +5 -8
  63. package/browser/editor/page/history.mjs +41 -6
  64. package/browser/editor/page/insert/index.mjs +20 -22
  65. package/browser/editor/page/links/index.mjs +5 -3
  66. package/browser/editor/page/links/pages.mjs +27 -9
  67. package/browser/editor/page/look.mjs +54 -3
  68. package/browser/editor/page/margin.mjs +18 -3
  69. package/browser/editor/page/media-menu/index.mjs +6 -7
  70. package/browser/editor/page/menu.mjs +33 -14
  71. package/browser/editor/page/notices.mjs +2 -2
  72. package/browser/editor/page/page-objects.mjs +9 -2
  73. package/browser/editor/page/panel.mjs +4 -9
  74. package/browser/editor/page/present-links.mjs +15 -5
  75. package/browser/editor/page/present-shell.mjs +63 -0
  76. package/browser/editor/page/resize/index.mjs +42 -29
  77. package/browser/editor/page/selection/index.mjs +10 -2
  78. package/browser/editor/page/served.mjs +14 -0
  79. package/browser/editor/page/session.mjs +48 -4
  80. package/browser/editor/page/slash-menu/index.mjs +3 -3
  81. package/browser/editor/page/table/binding.mjs +87 -0
  82. package/browser/editor/page/table/cells.mjs +102 -46
  83. package/browser/editor/page/table/index.mjs +24 -9
  84. package/browser/editor/page/table/pictures.mjs +299 -0
  85. package/browser/editor/page/table/views.mjs +103 -0
  86. package/browser/editor/page/table/widths.mjs +82 -10
  87. package/browser/editor/page/text-select/index.mjs +26 -12
  88. package/browser/editor/page/todo-writes.mjs +75 -0
  89. package/browser/editor/page/typing/index.mjs +97 -15
  90. package/browser/editor/page/typing/leave.mjs +19 -5
  91. package/browser/editor/page/ui.mjs +13 -0
  92. package/browser/shared/block-packs.mjs +419 -0
  93. package/browser/shared/board-places.mjs +224 -0
  94. package/browser/shared/card-size.mjs +59 -0
  95. package/browser/shared/colors.mjs +3 -2
  96. package/browser/shared/component-catalog.mjs +330 -46
  97. package/browser/shared/component-source.mjs +39 -0
  98. package/browser/shared/components/heading.mjs +2 -1
  99. package/browser/shared/components/index.mjs +12 -3
  100. package/browser/shared/components/installed.mjs +35 -3
  101. package/browser/shared/components/page.mjs +20 -8
  102. package/browser/shared/components/paragraph.mjs +8 -1
  103. package/browser/shared/components/state.mjs +5 -1
  104. package/browser/shared/components/table.mjs +1 -0
  105. package/browser/shared/document-look.mjs +10 -5
  106. package/browser/shared/drafts.mjs +432 -0
  107. package/browser/shared/emoji/line-icons.mjs +37 -0
  108. package/browser/shared/extension-sidebar.mjs +126 -0
  109. package/browser/shared/extensions.mjs +79 -0
  110. package/browser/shared/file-table.mjs +101 -0
  111. package/browser/shared/http.mjs +9 -0
  112. package/browser/shared/i18n/en.mjs +330 -27
  113. package/browser/shared/json-layout.mjs +47 -0
  114. package/browser/shared/net-cache.mjs +133 -0
  115. package/browser/shared/net-keys.mjs +32 -0
  116. package/browser/shared/net-policy.mjs +177 -0
  117. package/browser/shared/outline-shape.mjs +128 -0
  118. package/browser/shared/outline.mjs +359 -67
  119. package/browser/shared/pack-manifest.mjs +88 -0
  120. package/browser/shared/page-address.mjs +6 -3
  121. package/browser/shared/page-cards.mjs +308 -0
  122. package/browser/shared/page-links.mjs +69 -6
  123. package/browser/shared/pages.mjs +500 -0
  124. package/browser/shared/paths.mjs +6 -0
  125. package/browser/shared/pinyin.mjs +92 -0
  126. package/browser/shared/present-step.mjs +17 -0
  127. package/browser/shared/preview-server.mjs +3 -7
  128. package/browser/shared/session.mjs +8 -1
  129. package/browser/shared/shipped-blocks.mjs +299 -0
  130. package/browser/shared/sidebar-head.mjs +70 -0
  131. package/browser/shared/table-chart.mjs +33 -1
  132. package/browser/shared/todos.mjs +83 -0
  133. package/browser/shared/user-directory.mjs +24 -0
  134. package/browser/shared/versions.mjs +14 -0
  135. package/browser/shared/web-links.mjs +139 -0
  136. package/browser/shared/workspace-columns.mjs +120 -0
  137. package/browser/shared/workspace-name.mjs +154 -0
  138. package/browser/shared/workspace-query.mjs +125 -0
  139. package/browser/shared/workspace-registry.mjs +272 -0
  140. package/browser/shared/workspace.mjs +171 -20
  141. package/dist/chunk-2OHANJ6Q.mjs +2 -0
  142. package/dist/chunk-3MBRL2PM.mjs +16 -0
  143. package/dist/chunk-4OKWZY5E.mjs +2 -0
  144. package/dist/chunk-4OZWXZE5.mjs +5 -0
  145. package/dist/chunk-5K44HLTZ.mjs +2 -0
  146. package/dist/{chunk-5ZKJ3H4P.mjs → chunk-5XA2ZBAE.mjs} +3 -3
  147. package/dist/{chunk-4FS7Z7WR.mjs → chunk-67N2QJYT.mjs} +1 -1
  148. package/dist/chunk-6KBYRA2C.mjs +2 -0
  149. package/dist/chunk-6N3PX4XX.mjs +8 -0
  150. package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
  151. package/dist/chunk-7JDX4E65.mjs +13 -0
  152. package/dist/chunk-7M2BQQGZ.mjs +2 -0
  153. package/dist/chunk-7OG26PGF.mjs +2 -0
  154. package/dist/chunk-AHN3NFKJ.mjs +3 -0
  155. package/dist/chunk-ALZ6ZENZ.mjs +3 -0
  156. package/dist/chunk-AR5D57ZD.mjs +170 -0
  157. package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
  158. package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
  159. package/dist/chunk-CMA72MAG.mjs +2 -0
  160. package/dist/{chunk-LCTHTZIL.mjs → chunk-DAZL73VK.mjs} +91 -91
  161. package/dist/{chunk-Y6RLRPG3.mjs → chunk-DJISHPUL.mjs} +1 -1
  162. package/dist/chunk-DR73Y2UJ.mjs +3 -0
  163. package/dist/{chunk-FDLXYYG4.mjs → chunk-EAIFITAV.mjs} +1 -1
  164. package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
  165. package/dist/chunk-EP25HDAJ.mjs +2 -0
  166. package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
  167. package/dist/chunk-EURLTOTZ.mjs +4 -0
  168. package/dist/chunk-F4ZA6JRZ.mjs +3 -0
  169. package/dist/chunk-F6HVDZR4.mjs +2 -0
  170. package/dist/chunk-FDMAXMQA.mjs +2 -0
  171. package/dist/chunk-FETSREUN.mjs +5 -0
  172. package/dist/chunk-GHOYKMQ3.mjs +4 -0
  173. package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
  174. package/dist/chunk-HDWTZ763.mjs +12 -0
  175. package/dist/chunk-HPOI4ZX7.mjs +6 -0
  176. package/dist/{chunk-BGGDRNYQ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
  177. package/dist/chunk-IA4PSIMD.mjs +2 -0
  178. package/dist/{chunk-E7LWZRXY.mjs → chunk-ITOH5EGO.mjs} +1 -1
  179. package/dist/chunk-IV64FFI4.mjs +2 -0
  180. package/dist/chunk-J43Z6LSS.mjs +2 -0
  181. package/dist/chunk-J5PUIV3Y.mjs +4 -0
  182. package/dist/chunk-JA7G53PC.mjs +2 -0
  183. package/dist/chunk-JEUK3ZO7.mjs +2 -0
  184. package/dist/chunk-JLTZF4LA.mjs +2 -0
  185. package/dist/chunk-JZT7I2OJ.mjs +2 -0
  186. package/dist/chunk-K5XATEOO.mjs +2 -0
  187. package/dist/chunk-L2SUIS5S.mjs +2 -0
  188. package/dist/chunk-L7UP52JO.mjs +2 -0
  189. package/dist/chunk-LM4XFQHZ.mjs +2 -0
  190. package/dist/chunk-MN7BZL4R.mjs +2 -0
  191. package/dist/chunk-MTYYHM2G.mjs +15 -0
  192. package/dist/chunk-MXUBKDRO.mjs +15 -0
  193. package/dist/chunk-NAMN3RAF.mjs +2 -0
  194. package/dist/{chunk-SYGN7GLU.mjs → chunk-OEXWRJME.mjs} +1 -1
  195. package/dist/chunk-OFHXGWOX.mjs +2 -0
  196. package/dist/chunk-ORID3AE7.mjs +166 -0
  197. package/dist/chunk-PAREWQGB.mjs +4 -0
  198. package/dist/chunk-PGDE24N5.mjs +2 -0
  199. package/dist/chunk-POTA2FV4.mjs +2 -0
  200. package/dist/chunk-PUOQ65NC.mjs +2 -0
  201. package/dist/chunk-Q4UEPUWF.mjs +2 -0
  202. package/dist/chunk-QE5RG76S.mjs +549 -0
  203. package/dist/chunk-R2ZDLJYT.mjs +10 -0
  204. package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
  205. package/dist/chunk-SWUTTZCN.mjs +2 -0
  206. package/dist/chunk-TANZRINB.mjs +2 -0
  207. package/dist/chunk-TR7TWGHB.mjs +2 -0
  208. package/dist/chunk-U5XL7D6A.mjs +3 -0
  209. package/dist/{chunk-XNROSH2T.mjs → chunk-U7NZI6N4.mjs} +2 -2
  210. package/dist/chunk-UGWMMEJ5.mjs +15 -0
  211. package/dist/chunk-UJQJKNGK.mjs +2 -0
  212. package/dist/chunk-UYVCQK3H.mjs +22 -0
  213. package/dist/chunk-VFX2KNHP.mjs +4 -0
  214. package/dist/chunk-VS7JIGJW.mjs +24 -0
  215. package/dist/chunk-W7665ZEC.mjs +6 -0
  216. package/dist/chunk-WQDQWCX5.mjs +2 -0
  217. package/dist/chunk-YI43RG4P.mjs +2 -0
  218. package/dist/chunk-YLPSMDIS.mjs +5 -0
  219. package/dist/chunk-YNAWBAYN.mjs +3 -0
  220. package/dist/chunk-YROX2O22.mjs +2 -0
  221. package/dist/chunk-ZDLPUOAG.mjs +6 -0
  222. package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
  223. package/dist/chunk-ZQS6H473.mjs +16 -0
  224. package/dist/chunk-ZTHICSSF.mjs +2 -0
  225. package/dist/chunk-ZWWC4KLF.mjs +20 -0
  226. package/dist/cli.mjs +1 -1
  227. package/dist/server.mjs +1 -1
  228. package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
  229. package/dist/worker.mjs +1 -1
  230. package/kit/board.css +95 -0
  231. package/kit/board.js +250 -0
  232. package/kit/charts.js +5 -71
  233. package/kit/code.js +17 -17
  234. package/kit/report.css +335 -18
  235. package/kit/runtime.js +1638 -125
  236. package/kit/state.js +10 -1
  237. package/kit/tables.js +130 -43
  238. package/kit/ui.js +79 -35
  239. package/package.json +2 -1
  240. package/packs/gallery/card/component.json +14 -0
  241. package/packs/gallery/card/example.html +10 -0
  242. package/packs/gallery/card/preview.svg +1 -0
  243. package/packs/gallery/card/style.css +6 -0
  244. package/packs/gallery/card/template.html +6 -0
  245. package/packs/gallery/card/writing.md +14 -0
  246. package/packs/gallery/chart-card/component.json +15 -0
  247. package/packs/gallery/chart-card/example.html +14 -0
  248. package/packs/gallery/chart-card/kit.js +130 -0
  249. package/packs/gallery/chart-card/preview.svg +1 -0
  250. package/packs/gallery/chart-card/style.css +42 -0
  251. package/packs/gallery/chart-card/template.html +13 -0
  252. package/packs/gallery/chart-card/writing.md +20 -0
  253. package/packs/gallery/chart-view/component.json +20 -0
  254. package/packs/gallery/chart-view/example.html +13 -0
  255. package/packs/gallery/chart-view/kit.js +355 -0
  256. package/packs/gallery/chart-view/preview.svg +1 -0
  257. package/packs/gallery/chart-view/style.css +94 -0
  258. package/packs/gallery/chart-view/template.html +3 -0
  259. package/packs/gallery/chart-view/writing.md +22 -0
  260. package/packs/gallery/countdown/component.json +18 -0
  261. package/packs/gallery/countdown/example.html +3 -0
  262. package/packs/gallery/countdown/kit.js +216 -0
  263. package/packs/gallery/countdown/preview.svg +1 -0
  264. package/packs/gallery/countdown/style.css +118 -0
  265. package/packs/gallery/countdown/template.html +3 -0
  266. package/packs/gallery/countdown/writing.md +22 -0
  267. package/packs/gallery/habit/component.json +14 -0
  268. package/packs/gallery/habit/example.html +3 -0
  269. package/packs/gallery/habit/kit.js +334 -0
  270. package/packs/gallery/habit/preview.svg +1 -0
  271. package/packs/gallery/habit/style.css +174 -0
  272. package/packs/gallery/habit/template.html +3 -0
  273. package/packs/gallery/habit/writing.md +21 -0
  274. package/packs/gallery/heatmap/component.json +18 -0
  275. package/packs/gallery/heatmap/example.html +3 -0
  276. package/packs/gallery/heatmap/kit.js +513 -0
  277. package/packs/gallery/heatmap/preview.svg +1 -0
  278. package/packs/gallery/heatmap/style.css +189 -0
  279. package/packs/gallery/heatmap/template.html +3 -0
  280. package/packs/gallery/heatmap/writing.md +21 -0
  281. package/packs/gallery/kanban/component.json +18 -0
  282. package/packs/gallery/kanban/example.html +12 -0
  283. package/packs/gallery/kanban/kit.js +540 -0
  284. package/packs/gallery/kanban/preview.svg +1 -0
  285. package/packs/gallery/kanban/style.css +212 -0
  286. package/packs/gallery/kanban/template.html +3 -0
  287. package/packs/gallery/kanban/writing.md +20 -0
  288. package/packs/gallery/metric/component.json +22 -0
  289. package/packs/gallery/metric/example.html +15 -0
  290. package/packs/gallery/metric/kit.js +248 -0
  291. package/packs/gallery/metric/preview.svg +1 -0
  292. package/packs/gallery/metric/style.css +92 -0
  293. package/packs/gallery/metric/template.html +3 -0
  294. package/packs/gallery/metric/writing.md +23 -0
  295. package/packs/gallery/note/component.json +14 -0
  296. package/packs/gallery/note/example.html +3 -0
  297. package/packs/gallery/note/kit.js +129 -0
  298. package/packs/gallery/note/preview.svg +1 -0
  299. package/packs/gallery/note/style.css +97 -0
  300. package/packs/gallery/note/template.html +3 -0
  301. package/packs/gallery/note/writing.md +18 -0
  302. package/packs/gallery/pack.json +8 -0
  303. package/packs/gallery/recent/component.json +13 -0
  304. package/packs/gallery/recent/example.html +1 -0
  305. package/packs/gallery/recent/kit.js +293 -0
  306. package/packs/gallery/recent/preview.svg +1 -0
  307. package/packs/gallery/recent/style.css +85 -0
  308. package/packs/gallery/recent/template.html +1 -0
  309. package/packs/gallery/recent/writing.md +15 -0
  310. package/packs/gallery/static/component.json +13 -0
  311. package/packs/gallery/static/example.html +11 -0
  312. package/packs/gallery/static/preview.svg +1 -0
  313. package/packs/gallery/static/style.css +171 -0
  314. package/packs/gallery/static/template.html +1 -0
  315. package/packs/gallery/static/writing.md +18 -0
  316. package/packs/gallery/stats/component.json +13 -0
  317. package/packs/gallery/stats/example.html +1 -0
  318. package/packs/gallery/stats/kit.js +347 -0
  319. package/packs/gallery/stats/preview.svg +1 -0
  320. package/packs/gallery/stats/style.css +73 -0
  321. package/packs/gallery/stats/template.html +1 -0
  322. package/packs/gallery/stats/writing.md +11 -0
  323. package/packs/gallery/today/component.json +12 -0
  324. package/packs/gallery/today/example.html +1 -0
  325. package/packs/gallery/today/kit.js +220 -0
  326. package/packs/gallery/today/preview.svg +1 -0
  327. package/packs/gallery/today/style.css +149 -0
  328. package/packs/gallery/today/template.html +1 -0
  329. package/packs/gallery/today/writing.md +14 -0
  330. package/packs/gallery/todos/component.json +19 -0
  331. package/packs/gallery/todos/example.html +3 -0
  332. package/packs/gallery/todos/kit.js +489 -0
  333. package/packs/gallery/todos/preview.svg +1 -0
  334. package/packs/gallery/todos/style.css +214 -0
  335. package/packs/gallery/todos/template.html +3 -0
  336. package/packs/gallery/todos/writing.md +30 -0
  337. package/packs/gallery/weather/component.json +19 -0
  338. package/packs/gallery/weather/example.html +3 -0
  339. package/packs/gallery/weather/kit.js +387 -0
  340. package/packs/gallery/weather/preview.svg +1 -0
  341. package/packs/gallery/weather/style.css +140 -0
  342. package/packs/gallery/weather/template.html +3 -0
  343. package/packs/gallery/weather/writing.md +26 -0
  344. package/packs/gallery-soft/countdown/component.json +18 -0
  345. package/packs/gallery-soft/countdown/example.html +3 -0
  346. package/packs/gallery-soft/countdown/kit.js +206 -0
  347. package/packs/gallery-soft/countdown/preview.svg +1 -0
  348. package/packs/gallery-soft/countdown/style.css +211 -0
  349. package/packs/gallery-soft/countdown/template.html +3 -0
  350. package/packs/gallery-soft/countdown/writing.md +24 -0
  351. package/packs/gallery-soft/habit/component.json +14 -0
  352. package/packs/gallery-soft/habit/example.html +3 -0
  353. package/packs/gallery-soft/habit/kit.js +366 -0
  354. package/packs/gallery-soft/habit/preview.svg +1 -0
  355. package/packs/gallery-soft/habit/style.css +251 -0
  356. package/packs/gallery-soft/habit/template.html +3 -0
  357. package/packs/gallery-soft/habit/writing.md +23 -0
  358. package/packs/gallery-soft/heatmap/component.json +18 -0
  359. package/packs/gallery-soft/heatmap/example.html +3 -0
  360. package/packs/gallery-soft/heatmap/kit.js +498 -0
  361. package/packs/gallery-soft/heatmap/preview.svg +1 -0
  362. package/packs/gallery-soft/heatmap/style.css +181 -0
  363. package/packs/gallery-soft/heatmap/template.html +3 -0
  364. package/packs/gallery-soft/heatmap/writing.md +23 -0
  365. package/packs/gallery-soft/note/component.json +14 -0
  366. package/packs/gallery-soft/note/example.html +3 -0
  367. package/packs/gallery-soft/note/kit.js +162 -0
  368. package/packs/gallery-soft/note/preview.svg +1 -0
  369. package/packs/gallery-soft/note/style.css +148 -0
  370. package/packs/gallery-soft/note/template.html +3 -0
  371. package/packs/gallery-soft/note/writing.md +20 -0
  372. package/packs/gallery-soft/pack.json +8 -0
  373. package/packs/gallery-soft/recent/component.json +13 -0
  374. package/packs/gallery-soft/recent/example.html +1 -0
  375. package/packs/gallery-soft/recent/kit.js +335 -0
  376. package/packs/gallery-soft/recent/preview.svg +1 -0
  377. package/packs/gallery-soft/recent/style.css +160 -0
  378. package/packs/gallery-soft/recent/template.html +1 -0
  379. package/packs/gallery-soft/recent/writing.md +17 -0
  380. package/packs/gallery-soft/static/component.json +13 -0
  381. package/packs/gallery-soft/static/example.html +12 -0
  382. package/packs/gallery-soft/static/preview.svg +1 -0
  383. package/packs/gallery-soft/static/style.css +186 -0
  384. package/packs/gallery-soft/static/template.html +1 -0
  385. package/packs/gallery-soft/static/writing.md +20 -0
  386. package/packs/gallery-soft/stats/component.json +13 -0
  387. package/packs/gallery-soft/stats/example.html +1 -0
  388. package/packs/gallery-soft/stats/kit.js +341 -0
  389. package/packs/gallery-soft/stats/preview.svg +1 -0
  390. package/packs/gallery-soft/stats/style.css +119 -0
  391. package/packs/gallery-soft/stats/template.html +1 -0
  392. package/packs/gallery-soft/stats/writing.md +13 -0
  393. package/packs/gallery-soft/today/component.json +12 -0
  394. package/packs/gallery-soft/today/example.html +1 -0
  395. package/packs/gallery-soft/today/kit.js +222 -0
  396. package/packs/gallery-soft/today/preview.svg +1 -0
  397. package/packs/gallery-soft/today/style.css +178 -0
  398. package/packs/gallery-soft/today/template.html +1 -0
  399. package/packs/gallery-soft/today/writing.md +16 -0
  400. package/packs/gallery-soft/todos/component.json +19 -0
  401. package/packs/gallery-soft/todos/example.html +3 -0
  402. package/packs/gallery-soft/todos/kit.js +527 -0
  403. package/packs/gallery-soft/todos/preview.svg +1 -0
  404. package/packs/gallery-soft/todos/style.css +212 -0
  405. package/packs/gallery-soft/todos/template.html +3 -0
  406. package/packs/gallery-soft/todos/writing.md +32 -0
  407. package/packs/gallery-soft/weather/component.json +19 -0
  408. package/packs/gallery-soft/weather/example.html +3 -0
  409. package/packs/gallery-soft/weather/kit.js +411 -0
  410. package/packs/gallery-soft/weather/preview.svg +1 -0
  411. package/packs/gallery-soft/weather/style.css +185 -0
  412. package/packs/gallery-soft/weather/template.html +3 -0
  413. package/packs/gallery-soft/weather/writing.md +28 -0
  414. package/release.json +12 -10
  415. package/sdk/doc-kit.d.ts +396 -5
  416. package/sdk/doc-state.d.ts +2 -0
  417. package/skill/SKILL.md +56 -23
  418. package/skill/references/block-script.md +140 -0
  419. package/skill/references/building-blocks.md +257 -0
  420. package/skill/references/extensions.md +59 -0
  421. package/skill/references/pages-and-data.md +225 -0
  422. package/skill/references/writing-html.md +8 -4
  423. package/skill/sdk/doc-kit.d.ts +396 -5
  424. package/skill/sdk/doc-state.d.ts +2 -0
  425. package/web/assets/index-BM_K-Auu.css +1 -0
  426. package/web/assets/index-DRslOBiv.js +15 -0
  427. package/web/index.html +2 -2
  428. package/dist/chunk-3HIRPVLU.mjs +0 -2
  429. package/dist/chunk-5MHMDLZD.mjs +0 -4
  430. package/dist/chunk-64K7IPLO.mjs +0 -255
  431. package/dist/chunk-6QXGZM36.mjs +0 -8
  432. package/dist/chunk-6RGJOMJP.mjs +0 -16
  433. package/dist/chunk-AJ73XJF3.mjs +0 -2
  434. package/dist/chunk-AUZGSZJH.mjs +0 -13
  435. package/dist/chunk-BFE45AKL.mjs +0 -2
  436. package/dist/chunk-BXO2ZY5P.mjs +0 -2
  437. package/dist/chunk-CEH4MRCC.mjs +0 -2
  438. package/dist/chunk-CY2FNNAB.mjs +0 -16
  439. package/dist/chunk-DFUDV4LX.mjs +0 -2
  440. package/dist/chunk-DKXG4XDG.mjs +0 -2
  441. package/dist/chunk-EZQ34M3F.mjs +0 -124
  442. package/dist/chunk-EZY3WC6Z.mjs +0 -15
  443. package/dist/chunk-FUKQNOQS.mjs +0 -9
  444. package/dist/chunk-JPOSLGOC.mjs +0 -6
  445. package/dist/chunk-JVHHHGEA.mjs +0 -3
  446. package/dist/chunk-L3QKH4OK.mjs +0 -26
  447. package/dist/chunk-LAE5NQ4B.mjs +0 -2
  448. package/dist/chunk-LEF6DRCK.mjs +0 -59
  449. package/dist/chunk-NO5NJCHO.mjs +0 -2
  450. package/dist/chunk-NQPVAU63.mjs +0 -2
  451. package/dist/chunk-NSKUTQ5J.mjs +0 -5
  452. package/dist/chunk-O35FIXRB.mjs +0 -2
  453. package/dist/chunk-P2CAZPDP.mjs +0 -3
  454. package/dist/chunk-PL6GHLGD.mjs +0 -15
  455. package/dist/chunk-QJIR2BLP.mjs +0 -6
  456. package/dist/chunk-QUPLVPB2.mjs +0 -7
  457. package/dist/chunk-R7BNENLY.mjs +0 -4
  458. package/dist/chunk-S6O4UHLC.mjs +0 -2
  459. package/dist/chunk-SGJWQKEI.mjs +0 -2
  460. package/dist/chunk-TX6JH7TR.mjs +0 -2
  461. package/dist/chunk-TYK6VNAB.mjs +0 -10
  462. package/dist/chunk-UALRNVSE.mjs +0 -4
  463. package/dist/chunk-V3HXVQEM.mjs +0 -2
  464. package/dist/chunk-V5VXUJKG.mjs +0 -2
  465. package/dist/chunk-VBXMADMP.mjs +0 -2
  466. package/dist/chunk-VNV55RUA.mjs +0 -2
  467. package/dist/chunk-VPD2PS35.mjs +0 -2
  468. package/dist/chunk-W4VKN3GF.mjs +0 -7
  469. package/dist/chunk-W5BXXPDJ.mjs +0 -3
  470. package/dist/chunk-XUNHRCOG.mjs +0 -6
  471. package/skill/references/building-components.md +0 -367
  472. package/web/assets/index-Bh74DfsV.css +0 -1
  473. package/web/assets/index-UW0RyFFt.js +0 -13
@@ -2,20 +2,52 @@ import { randomBytes } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { writeAtomic } from "./files.mjs";
5
- import { layoutOf, listDocumentSlugs, pageSlug, slugPattern } from "./workspace.mjs";
6
-
7
- // The workspace's outline: which document sits under which, in what order (the sidebar's tree,
8
- // <workspace>/.gallery/outline.json, docs/workbench.md "Workspace commands"):
9
- // { version: 1, children: { "": [top-level slugs], "<slug>": [slugs under it] } }
10
- // Hierarchy and order only: a document's folder never moves with it, and no file of a document
11
- // names where it is (links between documents are relative links between their files). Read, it
12
- // lists every document of the workspace once but the 写作偏好 page (listed apart, in .gallery/pages/):
13
- // slugs that are gone drop out, documents it does not know yet come last at the top.
14
- // Written by the workspace's server (src/workbench/service.mjs: the workbench's moves, a new, copied,
15
- // restored or imported document, gallery new --under and gallery move while it runs, in the queue its
16
- // saves go through), and by a gallery command when no server runs (src/cli/tree.mjs), with the same
17
- // functions, each change read, made and written under the outline's lock (withOutlineLock: a file
18
- // that two processes cannot both make), so that two writers never lose each other's change.
5
+ import { extensionColumnIds } from "./extensions.mjs";
6
+ import { DOCS, entryPattern, extensionGroupId, GROUP_LIMITS, groupOf, isForeign, isTop, newGroupId, topIn, topOf, validGroups } from "./outline-shape.mjs";
7
+ import { storedHead } from "./sidebar-head.mjs";
8
+ import { shownWorkspaces, snapshotOf } from "./workspace-columns.mjs";
9
+ import { layoutOf, listDocumentSlugs, listLinkSlugs, pageSlug } from "./workspace.mjs";
10
+
11
+ // The workspace's outline: the sidebar's columns and which entry sits under which, in what order
12
+ // (<workspace>/.gallery/outline.json, docs/workbench.md "Workspace commands"; its shape: ./outline-shape.mjs).
13
+ // The file, version 2:
14
+ // { version: 2, children: { "": [the 文档 column's top], "<slug>": [entries under it] },
15
+ // groups: [{ id: "docs", title? }, { id, title?, role?, workspace?, children: { "": [its top], "<slug>": […] } }] }
16
+ // The 文档 column's tree is the top-level `children`, as in version 1 ({ version: 1, children }, the
17
+ // 文档 column alone, read as such), so that an older Gallery reads the documents' order as before.
18
+ // Hierarchy and order only: an entry's folder never moves with it, and no file of a document names
19
+ // where it is (links between documents are relative links between their files). Read, it lists every
20
+ // document and web link of the workspace once but the 写作偏好 page (listed apart, in .gallery/pages/):
21
+ // entries that are gone drop out, documents it does not know yet come last in the own column (文档's
22
+ // place, top key ""), links last in a column of the links an older Gallery made (else in the own column).
23
+ // Every other registered workspace has a column (./workspace-columns.mjs; the plan §7.2): one the file
24
+ // holds not yet is made as it is read (the default workspace's right after the own column, the others
25
+ // last), and a "whole" column (not `picked`) holds every document of its workspace the outline holds not
26
+ // but those the user took out (its `left`): under its own parent's entry when the outline holds that,
27
+ // else last in the column, the first time laid out as its own sidebar has them. A draft of another
28
+ // workspace ("~<ws id>/<slug>": ./drafts.mjs) stays where the file has it while its document is there;
29
+ // one gone from a workspace the sidebar shows leaves, one of a workspace it does not show (not
30
+ // registered, a clash of ids, its folder away) stays, shown grey (找不到底稿), until it is removed; one of a
31
+ // workspace whose folder did not read this time (an error of the disk: otherWorkspaces' `failed`) stays
32
+ // too, and its column is not filled. A column the sidebar does not draw (the user removed it, `hidden`;
33
+ // its workspace not shown) keeps its workspace's drafts alone: any other entry there (a document of this
34
+ // workspace, a web link, a draft of a third, dragged in) comes last in the own column with what is under it,
35
+ // so that nothing the user put there is out of sight. `others: false` reads the workspace's tree alone (what
36
+ // another workspace's sidebar reads of it, and what the commands of that workspace's own tree read).
37
+ // The columns extensions provide (extensions/<id>/sidebar.json: ./extensions.mjs) are among the groups as
38
+ // { id: "ext-<id>", extension: "<id>" }, each there while its extension has a sidebar.json: one it has not
39
+ // got yet comes last (not written until the outline is), one whose extension is gone drops out, and what an
40
+ // outline says of its entries is passed over (an extension's entries are its own, never the outline's).
41
+ // Their place is what the outline keeps of them.
42
+ // The file also keeps the order of the sidebar's head rows (`head`: 新建, 搜索 and the custom pages,
43
+ // ./sidebar-head.mjs), which only a drag of a head row and a new page write (writeHead), and every write
44
+ // of the tree keeps as it was (writeOutline).
45
+ // Written by the workspace's server (src/workbench/service.mjs: the workbench's moves and columns, a new,
46
+ // copied, restored or imported document, a new link, gallery new --under, gallery move and gallery link
47
+ // add while it runs, in the queue its saves go through), and by a gallery command when no server runs
48
+ // (src/cli/tree.mjs), with the same functions, each change read, made and written under the outline's
49
+ // lock (withOutlineLock: a file that two processes cannot both make), so that two writers never lose
50
+ // each other's change.
19
51
 
20
52
  /** The outline file's lock (.gallery/outline.lock): who holds it, until when it counts. */
21
53
  const LOCK_STALE = 10_000; // a lock older than this was left by a process that ended: taken over
@@ -53,91 +85,337 @@ export async function withOutlineLock(root, task) {
53
85
 
54
86
  const readJson = (file) => fs.readFile(file, "utf8").then(JSON.parse).catch(() => null);
55
87
 
56
- /** The outline of workspace `root` as it is read: every document but the 写作偏好 page once (above). */
57
- export async function readOutline(root) {
58
- return (await outlineAndFile(root)).outline;
88
+ /** The outline of workspace `root` as it is read: every entry once (above). `others`: with the other workspaces' columns. */
89
+ export async function readOutline(root, { others = true } = {}) {
90
+ return (await outlineAndFile(root, { others })).outline;
91
+ }
92
+
93
+ /**
94
+ * The other workspaces the sidebar of workspace `root` shows a column of, each with what its own sidebar
95
+ * holds (./workspace-columns.mjs shownWorkspaces, snapshotOf): [{ id, root, isDefault, documents, order,
96
+ * parentOf, facts, failed }]. `failed`: its folder did not read this time (EPERM, EBUSY, EIO: a network or
97
+ * removable drive), none of its documents known, so that the outline drops none of its drafts for it.
98
+ */
99
+ export async function otherWorkspaces(root, { env = process.env } = {}) {
100
+ const shown = shownWorkspaces(root, { env });
101
+ const readOwn = (other) => readOutline(other, { others: false });
102
+ const unread = () => ({ documents: new Set(), order: [], parentOf: new Map(), facts: new Map(), failed: true });
103
+ return Promise.all(shown.map(async (each) => ({ ...each, failed: false, ...(await snapshotOf(each.root, readOwn).catch(unread)) })));
59
104
  }
60
105
 
61
106
  /**
62
- * The outline as it is read and what its file holds: { outline, stored: the file's children, or null }.
63
- * A change is written when what it makes is not what the file holds (writtenAs), a document the file
64
- * does not hold yet too (one made since, listed last at the top only as it is read), so that the file
65
- * keeps every place a command or the workbench gave.
107
+ * The outline as it is read and what its file holds: { outline: { version: 2, groups, children,
108
+ * links: the slugs of its web links, drafts: the entries of other workspaces' drafts, shown: the other
109
+ * workspaces whose columns it shows ([{ id, root, isDefault }]: otherWorkspaces) }, stored: the file's
110
+ * columns and children as the outline has them (storedOf), or null; others: otherWorkspaces' answer }. A
111
+ * change is written when what it makes is not what the file holds (writtenAs), a document or a link the
112
+ * file does not hold yet too (one made since, placed only as it is read), so that the file keeps every
113
+ * place a command or the workbench gave. `others: false`: the workspace's own tree alone (no column of
114
+ * another workspace made, none filled).
66
115
  */
67
- export async function outlineAndFile(root) {
116
+ export async function outlineAndFile(root, { others: withOthers = true } = {}) {
68
117
  const instructions = await pageSlug(root);
69
- const slugs = (await listDocumentSlugs(root)).filter((slug) => slug !== instructions);
70
- const stored = await readJson(layoutOf(root).outline);
71
- const outline = { version: 1, children: ordered(stored, slugs) };
72
- return { outline, stored: stored?.children ?? null };
118
+ const documents = (await listDocumentSlugs(root)).filter((slug) => slug !== instructions);
119
+ const links = await listLinkSlugs(root);
120
+ const extensions = await extensionColumnIds(root);
121
+ const stored = storedOf(await readJson(layoutOf(root).outline));
122
+ const others = withOthers ? await otherWorkspaces(root) : [];
123
+ const shown = ordered(stored, documents, links, extensions, others, { drawn: withOthers });
124
+ const outline = {
125
+ version: 2, ...shown, links, drafts: Object.values(shown.children).flat().filter(isForeign),
126
+ shown: others.map(({ id, root: other, isDefault }) => ({ id, root: other, isDefault })),
127
+ };
128
+ return { outline, stored, others };
129
+ }
130
+
131
+ // The own column's name before it was 默认文档 or the workspace's (文档, Documents in English), which a
132
+ // rename of it confirmed unchanged wrote as its title (the field opens with the name shown): read as no
133
+ // title, so that the name it goes by now shows. A title of another column, or any other, is the user's.
134
+ const OLD_DOCS_NAMES = ["文档", "Documents"];
135
+
136
+ /**
137
+ * What outline.json's value `file` holds, as the outline has it: { groups, children } (the 文档 column's
138
+ * tree from the file's own `children`, every other column's from its `children`, its top under its top
139
+ * key), or null for a file that is not there or not an outline. A file of version 1 is the 文档 column alone.
140
+ * The own column titled with its old name (OLD_DOCS_NAMES) has none.
141
+ */
142
+ export function storedOf(file) {
143
+ if (!file || typeof file !== "object" || Array.isArray(file)) return null;
144
+ // A file that holds the head's order alone (writeHead's, before any tree was written) holds no tree.
145
+ if (!Object.hasOwn(file, "children") && !Object.hasOwn(file, "groups")) return null;
146
+ const groups = [];
147
+ const children = {};
148
+ const take = (id, own) => {
149
+ if (!own || typeof own !== "object" || Array.isArray(own)) return;
150
+ for (const [key, list] of Object.entries(own)) {
151
+ if (!Array.isArray(list)) continue;
152
+ const at = key === "" ? topOf(id) : key;
153
+ if (!children[at]) children[at] = list.filter((entry) => typeof entry === "string");
154
+ }
155
+ };
156
+ for (const value of Array.isArray(file.groups) ? file.groups : []) {
157
+ const group = groupOf(value);
158
+ if (!group || groups.some(({ id }) => id === group.id)) continue;
159
+ if (group.id === DOCS && OLD_DOCS_NAMES.includes(group.title)) delete group.title;
160
+ groups.push(group);
161
+ take(group.id, group.id === DOCS ? file.children : value.children);
162
+ }
163
+ if (!groups.some(({ id }) => id === DOCS)) {
164
+ groups.unshift({ id: DOCS });
165
+ take(DOCS, file.children);
166
+ }
167
+ return { groups, children };
168
+ }
169
+
170
+ /** The keys of `children` whose lists are not empty: what two outlines are compared by. */
171
+ const lists = (children) => Object.keys(children ?? {}).filter((key) => Array.isArray(children[key]) && children[key].length > 0);
172
+
173
+ /** The part of `children` the 文档 column holds: its top and the lists under its entries. */
174
+ function docsPart(children) {
175
+ const part = {};
176
+ const walk = (key) => {
177
+ const list = children?.[key];
178
+ if (!Array.isArray(list) || list.length === 0 || Object.hasOwn(part, key)) return;
179
+ part[key] = list;
180
+ list.forEach(walk);
181
+ };
182
+ walk("");
183
+ return part;
184
+ }
185
+
186
+ /**
187
+ * Whether outline `value` ({ groups?, children }) is what `stored` holds (outlineAndFile's, or an outline
188
+ * read): the same columns in the same order, and the same lists (an empty one as none), in whatever order
189
+ * the keys come. One without `groups` (an outline of version 1: a test's, an older workbench's) is held
190
+ * to the 文档 column alone. Also whether an outline a workbench changed (POST outline's `base`) is the one read now.
191
+ */
192
+ export function writtenAs(value, stored) {
193
+ if (!stored || typeof stored !== "object" || !value || typeof value !== "object") return false;
194
+ const ours = value.children ?? {};
195
+ const theirs = value.groups ? stored.children : docsPart(stored.children);
196
+ const [a, b] = [lists(ours), lists(theirs)];
197
+ if (a.length !== b.length || !a.every((key) => JSON.stringify(ours[key]) === JSON.stringify(theirs[key]))) return false;
198
+ if (!value.groups) return true;
199
+ const same = (groups) => JSON.stringify((groups ?? []).map(groupOf));
200
+ return same(value.groups) === same(stored.groups);
73
201
  }
74
202
 
75
203
  /**
76
- * Whether `children` is what the outline's file holds (`stored`, outlineAndFile's): the same lists
77
- * (an empty one as none), in whatever order the file has its keys. Also whether an outline a
78
- * workbench changed (POST outline's `base`) is the one read now.
204
+ * `groups` with the columns of the extensions `ids` (those with a sidebar.json): the extension columns the
205
+ * outline kept stay where it put them, once each and only while their extension is there; an extension
206
+ * with none comes last, while there is room (50 columns at most: validGroups, or no change of the outline
207
+ * would be written).
79
208
  */
80
- export function writtenAs(children, stored) {
81
- if (!stored || typeof stored !== "object") return false;
82
- const lists = (value) => Object.keys(value).filter((key) => Array.isArray(value[key]) && value[key].length > 0);
83
- const [ours, theirs] = [lists(children), lists(stored)];
84
- return ours.length === theirs.length && ours.every((key) => JSON.stringify(children[key]) === JSON.stringify(stored[key]));
209
+ function withExtensions(groups, ids) {
210
+ const wanted = new Set(ids);
211
+ const seen = new Set();
212
+ const kept = groups.filter((group) => {
213
+ if (!group.extension) return true;
214
+ if (!wanted.has(group.extension) || seen.has(group.extension)) return false;
215
+ seen.add(group.extension);
216
+ return true;
217
+ });
218
+ const coming = ids.filter((id) => !seen.has(id)).map((id) => ({ id: extensionGroupId(id), extension: id }));
219
+ return [...kept, ...coming.slice(0, Math.max(0, GROUP_LIMITS.groups - kept.length))];
220
+ }
221
+
222
+ /**
223
+ * `groups` with a column for each of the workspaces `others` (otherWorkspaces') that none has yet (hidden or
224
+ * not): a whole one (no `picked`), its id the workspace's when no column has that, the default workspace's
225
+ * right after the own column, the others last, in the registry's order; `limit` columns at most (the room
226
+ * the extensions' columns to come leave of the 50).
227
+ */
228
+ export function withWorkspaces(groups, others, limit = GROUP_LIMITS.groups) {
229
+ const result = [...groups];
230
+ const lacking = others.filter(({ id }) => !result.some((group) => group.workspace === id));
231
+ for (const { id, isDefault } of lacking) {
232
+ if (result.length >= limit) break; // 50 columns at most (validGroups)
233
+ const column = { id: result.some((group) => group.id === id) ? newGroupId(result) : id, workspace: id };
234
+ if (isDefault) result.splice(result.findIndex((group) => group.id === DOCS) + 1, 0, column);
235
+ else result.push(column);
236
+ }
237
+ return result;
85
238
  }
86
239
 
87
- // The outline's children of the documents `slugs`, in the order `stored` (the file's value) gives.
88
- function ordered(stored, slugs) {
89
- const children = { "": [] };
240
+ const DRAFT = /^~(ws-[0-9a-f]{6})\/(.+)$/;
241
+
242
+ /**
243
+ * The outline of the entries `documents` and `links` (the web links), in the order `stored` (storedOf's)
244
+ * gives, with the columns of the other workspaces `others` (otherWorkspaces'): { groups, children }, every
245
+ * column's top key there (an empty list for an empty column). Each entry once, under the first place it
246
+ * has; one of this workspace's that is gone is left out, a draft of another workspace ("~<ws id>/<slug>")
247
+ * left out when its workspace is among `others` and holds it no more (its folder read), else kept wherever
248
+ * the file has it. A document no column holds comes last in the own column; a link, last in a column of the
249
+ * links (role "links", an older Gallery's), else last in the own column; a document of another workspace
250
+ * whose column is whole, under its own parent's entry when that is held, else last in that column, in its
251
+ * own sidebar's order, but one the user took out of it (the column's `left`, kept to those still there).
252
+ * `drawn` (the sidebar's read, not a workspace's own tree alone): a workspace's column the sidebar does not
253
+ * draw (`hidden`, or its workspace not among `others`) keeps that workspace's drafts alone, any other entry
254
+ * there coming last in the own column with what is under it.
255
+ */
256
+ function ordered(stored, documents, links, extensions = [], others = [], { drawn = false } = {}) {
257
+ const listed = new Set([...documents, ...links]);
258
+ const shownById = new Map(others.map((each) => [each.id, each]));
259
+ const known = {
260
+ has: (slug) => {
261
+ if (listed.has(slug)) return true;
262
+ if (!isForeign(slug) || !entryPattern.test(slug)) return false;
263
+ const [, ws, own] = DRAFT.exec(slug);
264
+ const other = shownById.get(ws);
265
+ return other && !other.failed ? other.documents.has(own) : true;
266
+ },
267
+ };
268
+ // A whole column's `left`: those its workspace still has (its folder read), none when none is.
269
+ const kept = (group) => {
270
+ const other = group.left ? shownById.get(group.workspace) : null;
271
+ if (!other || other.failed) return group;
272
+ const { left, ...rest } = group;
273
+ const still = left.filter((slug) => other.documents.has(slug));
274
+ return still.length ? { ...rest, left: still } : rest;
275
+ };
276
+ const base = stored?.groups ?? [{ id: DOCS }];
277
+ // The room of the 50 columns an extension's column to come takes first (one the outline has not got yet).
278
+ const coming = extensions.filter((id) => !base.some((group) => group.extension === id)).length;
279
+ const groups = withExtensions(withWorkspaces(base, others, GROUP_LIMITS.groups - coming), extensions).map(kept);
280
+ const children = Object.fromEntries(groups.map(({ id }) => [topOf(id), []]));
90
281
  const seen = new Set();
91
- const place = (parent) => {
282
+ // Drawn not: the user removed it, or its workspace is not shown (forgotten, its folder away, a clash of ids).
283
+ const undrawn = (group) => drawn && Boolean(group.workspace) && (group.hidden === true || !shownById.has(group.workspace));
284
+ const astray = []; // entries of a column not drawn that are not its workspace's drafts: last in the own column
285
+ const place = (parent, only = null) => {
92
286
  for (const slug of stored?.children?.[parent] ?? []) {
93
- if (!slugs.includes(slug) || seen.has(slug)) continue;
287
+ if (!known.has(slug) || seen.has(slug)) continue;
288
+ if (only && !slug.startsWith(`~${only}/`)) {
289
+ astray.push(slug);
290
+ continue;
291
+ }
94
292
  seen.add(slug);
95
293
  (children[parent] ??= []).push(slug);
96
- place(slug);
294
+ place(slug, only);
97
295
  }
98
296
  };
99
- place("");
100
- for (const slug of slugs) if (!seen.has(slug)) children[""].push(slug);
101
- return children;
297
+ for (const group of groups) if (!group.extension) place(topOf(group.id), undrawn(group) ? group.workspace : null);
298
+ for (const slug of astray) {
299
+ if (seen.has(slug)) continue;
300
+ seen.add(slug);
301
+ children[""].push(slug);
302
+ place(slug);
303
+ }
304
+ const linked = groups.find(({ role }) => role === "links");
305
+ for (const slug of documents) if (!seen.has(slug)) children[""].push(slug);
306
+ for (const slug of links) if (!seen.has(slug)) children[linked ? topOf(linked.id) : ""].push(slug);
307
+ for (const other of others) {
308
+ const column = groups.find((group) => group.workspace === other.id);
309
+ if (!column || column.picked || other.failed) continue;
310
+ const left = new Set(column.left ?? []);
311
+ for (const slug of other.order) {
312
+ const entry = `~${other.id}/${slug}`;
313
+ if (seen.has(entry) || left.has(slug)) continue;
314
+ const parent = other.parentOf.get(slug);
315
+ const above = parent ? `~${other.id}/${parent}` : null;
316
+ (children[above !== null && seen.has(above) ? above : topOf(column.id)] ??= []).push(entry);
317
+ seen.add(entry);
318
+ }
319
+ }
320
+ return { groups, children };
102
321
  }
103
322
 
104
- /** Whether `value` is an outline to store: each document at most once, never under itself. */
323
+ /**
324
+ * Whether `value` ({ groups?, children }) is an outline to store: its columns valid (validGroups: 文档
325
+ * among them), each list's key a column's top or an entry, each entry at most once, never under itself.
326
+ * Without `groups` (an outline of version 1) the only top is 文档's ("").
327
+ */
105
328
  export function validOutline(value) {
106
329
  const children = value?.children;
107
330
  if (!children || typeof children !== "object" || Array.isArray(children)) return false;
331
+ if (value.groups !== undefined && !validGroups(value.groups)) return false;
332
+ const tops = new Set((value.groups ?? [{ id: DOCS }]).map(({ id }) => topOf(id)));
333
+ // An extension's column holds none of the outline's entries: they are the extension's own.
334
+ const extensionTops = new Set((value.groups ?? []).filter((group) => groupOf(group)?.extension).map(({ id }) => topOf(id)));
108
335
  const listed = new Set();
109
336
  for (const [parent, list] of Object.entries(children)) {
110
- if (parent !== "" && !slugPattern.test(parent)) return false;
111
- if (!Array.isArray(list)) return false;
337
+ if (!tops.has(parent) && !entryPattern.test(parent)) return false;
338
+ if (!Array.isArray(list) || (extensionTops.has(parent) && list.length > 0)) return false;
112
339
  for (const slug of list) {
113
- if (typeof slug !== "string" || !slugPattern.test(slug) || listed.has(slug) || slug === parent) return false;
340
+ if (typeof slug !== "string" || !entryPattern.test(slug) || listed.has(slug) || slug === parent) return false;
114
341
  listed.add(slug);
115
342
  }
116
343
  }
117
344
  return true;
118
345
  }
119
346
 
120
- /** Write outline `value` ({ children }) of workspace `root` (.gallery/ made when there is none yet). */
347
+ /**
348
+ * Outline `value` ({ groups?, children }) as outline.json holds it, version 2: the 文档 column's tree in
349
+ * `children`, where version 1 had the whole tree (an older Gallery reads its documents' order as before:
350
+ * it sees no column, and writes version 1 back, which this one reads as 文档 alone), and every column in
351
+ * `groups`, in the sidebar's order, the others each with its own `children` ("" its top).
352
+ */
353
+ export function fileOf(value) {
354
+ const groups = value.groups ?? [{ id: DOCS }];
355
+ const own = (top) => {
356
+ const tree = { "": [] };
357
+ const walk = (key, as) => {
358
+ const list = value.children[key];
359
+ if (!Array.isArray(list) || list.length === 0) return;
360
+ tree[as] = [...list];
361
+ for (const entry of list) if (!Object.hasOwn(tree, entry)) walk(entry, entry);
362
+ };
363
+ walk(top, "");
364
+ return tree;
365
+ };
366
+ return {
367
+ version: 2,
368
+ children: own(""),
369
+ groups: groups.map((group) => {
370
+ const kept = groupOf(group);
371
+ return kept.id === DOCS || kept.extension ? kept : { ...kept, children: own(topOf(kept.id)) };
372
+ }),
373
+ };
374
+ }
375
+
376
+ /**
377
+ * Write outline `value` ({ groups?, children }) of workspace `root` (.gallery/ made when there is none yet),
378
+ * the head's order the file holds kept as it is (writeHead's).
379
+ */
121
380
  export async function writeOutline(root, value) {
122
381
  const file = layoutOf(root).outline;
123
382
  await fs.mkdir(path.dirname(file), { recursive: true });
124
- return writeAtomic(file, `${JSON.stringify({ version: 1, children: value.children }, null, 2)}\n`);
383
+ const head = storedHead((await readJson(file))?.head);
384
+ return writeAtomic(file, `${JSON.stringify({ ...fileOf(value), ...(head ? { head } : {}) }, null, 2)}\n`);
385
+ }
386
+
387
+ /** The head's order as workspace `root`'s outline file keeps it (./sidebar-head.mjs storedHead), or null where it keeps none. */
388
+ export async function readStoredHead(root) {
389
+ return storedHead((await readJson(layoutOf(root).outline))?.head);
125
390
  }
126
391
 
127
- /** The document `slug` sits under in `children` ("" at the top), or null when the outline holds it not. */
392
+ /**
393
+ * Write the head's order `head` (a stored head: ./sidebar-head.mjs) into workspace `root`'s outline file,
394
+ * all else it holds as it was (a file there is none of yet holds it alone, which reads as no tree:
395
+ * storedOf). Run under the outline's lock (withOutlineLock), as every write of the file is.
396
+ */
397
+ export async function writeHead(root, head) {
398
+ const file = layoutOf(root).outline;
399
+ await fs.mkdir(path.dirname(file), { recursive: true });
400
+ const value = await readJson(file);
401
+ const kept = value && typeof value === "object" && !Array.isArray(value) ? value : {};
402
+ return writeAtomic(file, `${JSON.stringify({ ...kept, head: storedHead(head) ?? [] }, null, 2)}\n`);
403
+ }
404
+
405
+ /** The key `slug` sits under in `children` (a column's top key at a top level), or null when the outline holds it not. */
128
406
  export function parentIn(children, slug) {
129
407
  for (const [parent, list] of Object.entries(children)) if (list.includes(slug)) return parent;
130
408
  return null;
131
409
  }
132
410
 
133
- /** The documents above `slug` in `children`, from the top down (itself not among them). */
411
+ /** The entries above `slug` in `children`, from the top down (itself and its column not among them). */
134
412
  export function ancestorsIn(children, slug) {
135
413
  const above = [];
136
- for (let parent = parentIn(children, slug); parent; parent = parentIn(children, parent)) above.unshift(parent);
414
+ for (let parent = parentIn(children, slug); parent !== null && !isTop(parent) && !above.includes(parent); parent = parentIn(children, parent)) above.unshift(parent);
137
415
  return above;
138
416
  }
139
417
 
140
- /** Every document under `slug` in `children` ("": all), depth first, in the sidebar's order. */
418
+ /** Every entry under `slug` in `children` (a top key: the column's), depth first, in the sidebar's order. */
141
419
  export function descendantsIn(children, slug) {
142
420
  const found = [];
143
421
  const walk = (parent) => (children[parent] ?? []).forEach((child) => { found.push(child); walk(child); });
@@ -146,18 +424,18 @@ export function descendantsIn(children, slug) {
146
424
  }
147
425
 
148
426
  /**
149
- * Document `slug` moved in `children` (an outline as read: changed in place) with the documents under
150
- * it, to where `where` says: `parent` ("" the top; undefined: where it is now, or `after`'s parent),
151
- * right after `after` (one of that parent's), first (`first`), else last. Resolves to { ok: true,
152
- * moved: whether its place changed, parent, index, from: { parent, index } } or { ok: false, reason }:
153
- * "missing" (no such document in the outline), "parent" (no such parent), "after" (no such document
154
- * to go after), "level" (`after` is not under `parent`), "itself" (after itself), "inside" (under
155
- * itself or one of the documents under it).
427
+ * Entry `slug` moved in `children` (an outline as read: changed in place) with the entries under it, to
428
+ * where `where` says: `parent` ("" the top level of the column it is in; a column's top key, that
429
+ * column's; undefined: where it is now, or `after`'s parent), right after `after` (one of that parent's),
430
+ * first (`first`), else last. Resolves to { ok: true, moved: whether its place changed, parent, index,
431
+ * from: { parent, index } } or { ok: false, reason }: "missing" (no such entry in the outline), "parent"
432
+ * (no such parent), "after" (no such entry to go after), "level" (`after` is not under `parent`),
433
+ * "itself" (after itself), "inside" (under itself or one of the entries under it).
156
434
  */
157
435
  export function moveIn(children, slug, { parent = undefined, after = null, first = false } = {}) {
158
436
  const from = parentIn(children, slug);
159
437
  if (from === null) return { ok: false, reason: "missing" };
160
- let to = parent;
438
+ let to = parent === "" ? topIn(children, slug) ?? "" : parent;
161
439
  if (after !== null) {
162
440
  if (after === slug) return { ok: false, reason: "itself" };
163
441
  const its = parentIn(children, after);
@@ -166,29 +444,43 @@ export function moveIn(children, slug, { parent = undefined, after = null, first
166
444
  to = its;
167
445
  }
168
446
  to ??= from;
169
- if (to !== "" && parentIn(children, to) === null) return { ok: false, reason: "parent" };
447
+ if (isTop(to) ? !Object.hasOwn(children, to) : parentIn(children, to) === null) return { ok: false, reason: "parent" };
170
448
  if (to === slug || ancestorsIn(children, to).includes(slug)) return { ok: false, reason: "inside" };
171
449
  const index = children[from].indexOf(slug);
172
450
  children[from].splice(index, 1);
173
451
  const list = (children[to] ??= []);
174
452
  const at = after !== null ? list.indexOf(after) + 1 : first ? 0 : list.length;
175
453
  list.splice(at, 0, slug);
176
- for (const key of Object.keys(children)) if (key !== "" && children[key].length === 0) delete children[key];
454
+ for (const key of Object.keys(children)) if (!isTop(key) && children[key].length === 0) delete children[key];
177
455
  return { ok: true, moved: to !== from || at !== index, parent: to, index: at, from: { parent: from, index } };
178
456
  }
179
457
 
180
458
  /**
181
- * Move document `slug` of workspace `root` where `where` says (moveIn), under the outline's lock: the
182
- * outline read, changed and written when that is not what its file holds (writtenAs: a move, or a
183
- * document the file does not hold yet placed where it is read). Resolves to moveIn's answer, with
459
+ * Move entry `slug` of workspace `root` where `where` says (moveIn), under the outline's lock: the
460
+ * outline read, changed and written when that is not what its file holds (writtenAs: a move, or an
461
+ * entry the file does not hold yet placed where it is read). Resolves to moveIn's answer, with
184
462
  * `written`: whether the file was written.
185
463
  */
186
464
  export function moveDocument(root, slug, where) {
187
465
  return withOutlineLock(root, async () => {
188
466
  const { outline, stored } = await outlineAndFile(root);
189
467
  const result = moveIn(outline.children, slug, where);
190
- const written = result.ok && !writtenAs(outline.children, stored);
468
+ const written = result.ok && !writtenAs(outline, stored);
191
469
  if (written) await writeOutline(root, outline);
192
470
  return { ...result, ...(result.ok ? { written } : {}) };
193
471
  });
194
472
  }
473
+
474
+ /**
475
+ * Entry `slug` put in `outline` (changed in place: on disk already, so that the outline as read lists
476
+ * it somewhere) under `parent` (a column's top key or an entry; one the outline holds not: the 文档
477
+ * column's top) at `index` (the end when past it).
478
+ */
479
+ export function placeIn(outline, slug, parent = "", index = Infinity) {
480
+ const { children } = outline;
481
+ for (const list of Object.values(children)) if (list.includes(slug)) list.splice(list.indexOf(slug), 1);
482
+ const known = parent !== slug && (isTop(parent) ? Object.hasOwn(children, parent) : Object.values(children).some((list) => list.includes(parent)));
483
+ const list = (children[known ? parent : ""] ??= []);
484
+ list.splice(Math.max(0, Math.min(index, list.length)), 0, slug);
485
+ for (const key of Object.keys(children)) if (!isTop(key) && children[key].length === 0) delete children[key];
486
+ }
@@ -0,0 +1,88 @@
1
+ import { refusal, said } from "./i18n/index.mjs";
2
+ import { DEVICE_NAMES } from "./workspace.mjs";
3
+
4
+ // A block pack's manifest (自定义块包: notes/plans/block-packs-plan-2026-11-09.md §1; docs/render.md "Block
5
+ // packs"): pack.json at the top of a pack, a folder of components/ whose blocks are its scope's
6
+ // (components/<id>/<name>/, the block's id <id>/<name>), what a pack says of itself to whoever lists it
7
+ // (设置 › 组件列表's pack cards, gallery block pack list) and to whoever installs it (./block-packs.mjs).
8
+ // Plain data, as a block's manifest is: a field it does not know keeps it from reading, so that a pack
9
+ // made for a later Gallery says so rather than half reads.
10
+ //
11
+ // id the scope its blocks are of: lowercase letters, digits and hyphens (my-cards); not
12
+ // local, the workspace's own blocks' scope, nor a name Windows keeps for a device
13
+ // name what a reader calls it: words, or { zh, en } in both languages
14
+ // description what it is, the same way (optional)
15
+ // version x.y.z
16
+ // author who made it, words (optional)
17
+ // homepage where it comes from, an http(s) address (optional)
18
+ // icon an emoji or a character or two its card shows (optional; a card has the custom block's mark)
19
+
20
+ /** The file that makes a folder of components/ a pack. */
21
+ export const PACK_FILE = "pack.json";
22
+
23
+ /** What a pack's id, a scope, may be: as a block id's scope (shared/components/installed.mjs PACKAGE_ID). */
24
+ export const PACK_ID = /^[a-z0-9][a-z0-9-]*$/;
25
+
26
+ /**
27
+ * The scope a block of the workspace's own is in (gallery block new local/<name>, or a <name> alone), which
28
+ * no pack installed takes: the default pack (默认块包, DEFAULT_PACK), where an Agent's block goes when it
29
+ * names no pack, so that a single block needs no pack of its own. It has no pack.json (none may name it):
30
+ * its name and words are these, and 设置 › 组件列表 lists it first, its blocks or none (shared/block-packs.mjs).
31
+ */
32
+ export const OWN_SCOPE = "local";
33
+
34
+ /** What the default pack (OWN_SCOPE's) is called and says of itself, in both languages. */
35
+ export const DEFAULT_PACK = Object.freeze({
36
+ name: Object.freeze({ zh: "默认块包", en: "Default pack" }),
37
+ description: Object.freeze({ zh: "Agent 写自定义块时没有指定块包,块就放在这里。只写一个块,不用另建块包。", en: "Where an Agent's custom block goes when it names no pack: a single block needs no pack of its own." }),
38
+ });
39
+
40
+ const FIELDS = ["id", "name", "description", "version", "author", "homepage", "icon"];
41
+ const VERSION = /^\d+\.\d+\.\d+$/;
42
+ const isTable = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
43
+ const isWords = (value) => typeof value === "string" && value.trim() !== "";
44
+ const fail = (message) => {
45
+ throw refusal(message, { code: "manifest" });
46
+ };
47
+
48
+ // Words a reader reads, in both languages: one string for both, or { zh, en }.
49
+ function bothOf(value, field) {
50
+ if (isWords(value) && value.length <= 300) return { zh: value.trim(), en: value.trim() };
51
+ if (isTable(value) && Object.keys(value).length === 2 && isWords(value.zh) && isWords(value.en) && value.zh.length <= 300 && value.en.length <= 300) return { zh: value.zh.trim(), en: value.en.trim() };
52
+ fail(said("pack.json 的 {0} 要写成一段文字,或 { zh, en } 两种语言的文字", field));
53
+ }
54
+
55
+ /**
56
+ * pack.json's text read as a pack's manifest: { id, name: { zh, en }, description: { zh, en } | null,
57
+ * version, author, homepage, icon (each null where it says none) }. One that does not read as one is
58
+ * refused (code "manifest") with why, said()'s.
59
+ */
60
+ export function readPackManifest(text) {
61
+ let value;
62
+ try {
63
+ value = JSON.parse(String(text).replace(/^/, ""));
64
+ } catch {
65
+ fail(said("pack.json 不是 JSON"));
66
+ }
67
+ if (!isTable(value)) fail(said("pack.json 要写成一张表"));
68
+ for (const key of Object.keys(value)) if (!FIELDS.includes(key)) fail(said("{0} 不是 pack.json 能写的字段", key));
69
+ const { id, name, description, version, author, homepage, icon } = value;
70
+ if (id === undefined) fail(said("pack.json 缺少 {0}", "id"));
71
+ if (typeof id !== "string" || !PACK_ID.test(id) || DEVICE_NAMES.has(id)) fail(said("pack.json 的 id 只用小写字母、数字和连字符,如 my-cards"));
72
+ if (id === OWN_SCOPE) fail(said("{0} 留给工作区自己的块,块包换一个 id", OWN_SCOPE));
73
+ if (name === undefined) fail(said("pack.json 缺少 {0}", "name"));
74
+ if (version === undefined) fail(said("pack.json 缺少 {0}", "version"));
75
+ if (typeof version !== "string" || !VERSION.test(version)) fail(said("pack.json 的 version 要写成 1.0.0 这样的三段数字"));
76
+ if (author !== undefined && !(isWords(author) && author.length <= 100)) fail(said("pack.json 的 {0} 要写成一段文字", "author"));
77
+ if (homepage !== undefined && !(typeof homepage === "string" && /^https?:\/\/\S+$/.test(homepage) && homepage.length <= 500)) fail(said("pack.json 的 homepage 要写成 http 或 https 开头的地址"));
78
+ if (icon !== undefined && !(isWords(icon) && [...icon].length <= 8 && !/[<>&"]/.test(icon))) fail(said("pack.json 的 icon 要写成一个表情或一两个字"));
79
+ return {
80
+ id,
81
+ name: bothOf(name, "name"),
82
+ description: description === undefined ? null : bothOf(description, "description"),
83
+ version,
84
+ author: author?.trim() ?? null,
85
+ homepage: homepage ?? null,
86
+ icon: icon?.trim() ?? null,
87
+ };
88
+ }