@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
@@ -0,0 +1,432 @@
1
+ import { existsSync } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { descendantsIn, outlineAndFile, parentIn, placeIn, readOutline, withOutlineLock, writeOutline, writtenAs } from "./outline.mjs";
6
+ import { DOCS, GROUP_LIMITS, isForeign, isTop, newGroupId, topIn, topOf } from "./outline-shape.mjs";
7
+ import { findPreviewServer } from "./preview-server.mjs";
8
+ import { idsOf, shownIcon } from "./workspace-columns.mjs";
9
+ import { nameStops, workspaceLabels, workspaceName } from "./workspace-name.mjs";
10
+ import { isWorkspace, readRegistry, workspaceId, workspaceKey } from "./workspace-registry.mjs";
11
+ import { defaultWorkspace, documentFiles, isSlugName, listDocumentSlugs } from "./workspace.mjs";
12
+
13
+ // The drafts of other workspaces in a workspace's sidebar (notes/plans/workspaces-links-plan-2026-11-08.md
14
+ // §2.2, §2.5, §7.2; docs/workbench.md "Drafts of other workspaces"): an entry of the outline written
15
+ // "~<ws id>/<slug>" (./outline-shape.mjs), the workspace's id its path's hash on the machine's registry
16
+ // (./workspace-registry.mjs), the draft its document <root>/<slug>/doc.html there. Only where the sidebar
17
+ // shows it is this workspace's to keep: the draft's folder never moves, and its own workspace's tree is
18
+ // never written (its server shows it as it was). Every registered workspace has a column of its own
19
+ // (./workspace-columns.mjs, ./outline.mjs), "whole" (all its documents) unless the user picked a document
20
+ // of it in 添加其他 Gallery 项目… (POST draft-add) while it had none shown: then it is `picked`, holding that
21
+ // document and those under it alone, and what an Agent makes or opens there later (gallery new, gallery
22
+ // open in another workspace than the default one: joinDefaultTree). The column the user removed is
23
+ // `hidden`; an Agent's gallery new or open there, or the user's pick, shows it again. A draft the user took
24
+ // out of a whole column (从文档树移除) is in its `left`, not filled in again until it is put back here.
25
+ // A draft whose workspace is not on the registry, two registered folders sharing its id (six hex digits
26
+ // of a hash: a clash is refused, never guessed), or whose folder is gone stays in the tree, shown grey
27
+ // (找不到底稿) until the user removes it. Reads other workspaces' files only; writes this one's outline.
28
+
29
+ /** A draft's entry in the outline. */
30
+ export const DRAFT_ENTRY = /^~(ws-[0-9a-f]{6})\/([a-z0-9]+(?:-[a-z0-9]+)*)$/;
31
+ export const draftEntry = (id, slug) => `~${id}/${slug}`;
32
+ /** The workspace's id and the slug a draft's entry names, or null for an entry that names none. */
33
+ export function draftOf(entry) {
34
+ const match = DRAFT_ENTRY.exec(String(entry ?? ""));
35
+ return match ? { ws: match[1], slug: match[2] } : null;
36
+ }
37
+
38
+ const oneLine = (value) => (typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "");
39
+ const exists = (target) => fs.access(target).then(() => true, () => false);
40
+
41
+ /**
42
+ * The registered workspaces by their ids: a Map of id to the roots registered under it (two for one id:
43
+ * a clash of the hash's six digits, which names neither: workspaceOf). `registry`: readRegistry()'s.
44
+ */
45
+ export const registeredIds = (registry = readRegistry()) => idsOf(registry);
46
+
47
+ /**
48
+ * The workspace `id` names on the registry: { root }, or { refused: "unknown" (no registered workspace
49
+ * of that id: forgotten, or never registered) | "ambiguous" (two registered folders share it), roots }.
50
+ */
51
+ export function workspaceOf(id, ids = registeredIds()) {
52
+ const roots = ids.get(id) ?? [];
53
+ if (roots.length === 1) return { root: roots[0] };
54
+ return { refused: roots.length ? "ambiguous" : "unknown", roots };
55
+ }
56
+
57
+ /** The ids two or more registered folders share: [{ id, roots }] (gallery workspaces and the sidebar say so). */
58
+ export const clashingIds = (ids = registeredIds()) => [...ids].filter(([, roots]) => roots.length > 1).map(([id, roots]) => ({ id, roots }));
59
+
60
+ /** Whether a draft's doc.json says it is a document (not the 写作偏好 page of an old layout), and what it says. */
61
+ async function metadataAt(folder) {
62
+ const files = documentFiles(folder);
63
+ const [html, metadata] = await Promise.all([exists(files.html), fs.readFile(files.json, "utf8").then((text) => JSON.parse(text.replace(/^/, "")), () => null)]);
64
+ return { files, html, metadata: metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null };
65
+ }
66
+
67
+ /**
68
+ * What the sidebar shows of draft `entry`: { entry, ws, slug, root (null: no workspace of that id),
69
+ * folder, path (its doc.html, or an old one's report.html), title, description, icon, missing: null, or
70
+ * why it is shown grey: "workspace" (none of that id on the registry, or its folder gone), "ambiguous"
71
+ * (two of that id), "draft" (its document is gone) }.
72
+ */
73
+ export async function draftFacts(entry, ids = registeredIds()) {
74
+ const { ws, slug } = draftOf(entry) ?? { ws: "", slug: entry };
75
+ const found = workspaceOf(ws, ids);
76
+ const none = { entry, ws, slug, root: null, folder: null, path: null, title: slug, description: "", icon: null };
77
+ if (!found.root) return { ...none, missing: found.refused === "ambiguous" ? "ambiguous" : "workspace" };
78
+ const folder = path.join(found.root, slug);
79
+ const { files, html, metadata } = await metadataAt(folder);
80
+ const missing = !isWorkspace(found.root) ? "workspace" : !html || metadata?.instructions === true ? "draft" : null;
81
+ // An untitled document says so (its title empty: the workbench names it 未命名文档); one with no doc.json to read, its slug.
82
+ return { entry, ws, slug, root: found.root, folder, path: files.html, title: typeof metadata?.title === "string" ? oneLine(metadata.title) : slug, description: oneLine(metadata?.description), icon: shownIcon(metadata?.icon), missing };
83
+ }
84
+
85
+ /**
86
+ * The names the sidebar calls the registered workspaces by: a Map of id to { name, above, upper, root,
87
+ * there }, for the ids `wanted`. `above` is what tells it apart from another of `among` (ids: those shown
88
+ * side by side, the sidebar's columns or the list of 添加其他 Gallery 项目…; `wanted` when none is given) that has
89
+ * its name (./workspace-name.mjs workspaceLabels), else null: never a workspace of the machine that is not
90
+ * shown. `upper`: the name of the folder above the one named (null at a drive's root), which the sidebar
91
+ * shows when a column of another kind has the name. `stops`: nameStops()'s (asked once by a server: it
92
+ * reads Windows' registry).
93
+ */
94
+ export function workspaceNames(wanted, { ids = registeredIds(), stops = nameStops(), among = wanted } = {}) {
95
+ const named = [...ids].filter(([id, roots]) => roots.length === 1 && (wanted.has(id) || among.has(id))).map(([id, [root]]) => ({ id, root, ...workspaceName(root, { stops }) }));
96
+ const compared = named.filter((entry) => among.has(entry.id));
97
+ const labels = new Map(workspaceLabels(compared).map((label, index) => [compared[index].id, label]));
98
+ const names = new Map();
99
+ for (const entry of named) {
100
+ if (wanted.has(entry.id)) names.set(entry.id, { name: entry.name, above: labels.get(entry.id)?.above ?? null, upper: entry.above, root: entry.root, there: isWorkspace(entry.root) });
101
+ }
102
+ return names;
103
+ }
104
+
105
+ /**
106
+ * What a path the user pasted or picked (添加其他 Gallery 项目…) names: a workspace's folder, a document's
107
+ * doc.html (an old one's report.html) or its folder; a file:// address of one too, and quotes around it are
108
+ * taken away. One of: { kind: "workspace", root } (a folder holding Gallery's gallery.json);
109
+ * { kind: "draft", folder, slug, root (the workspace it is in: the nearest folder up holding Gallery's
110
+ * gallery.json; null for none, `parent` then the folder that would become one) }; { kind: "folder", folder }
111
+ * (a folder that is no workspace yet but holds Gallery documents: the one that would become a workspace);
112
+ * { kind: "missing" } (nothing there, or not an absolute path); { kind: "plain" } (an HTML file, or a folder
113
+ * that is no Gallery draft, workspace or folder of documents); { kind: "nested", root } (a draft that is not
114
+ * right in its workspace: the trash's, the 写作偏好 page's); { kind: "name" } (a folder whose name is no slug).
115
+ */
116
+ export async function findDraft(written) {
117
+ let target = String(written ?? "").trim().replace(/^["']+|["']+$/g, "");
118
+ if (/^file:\/\//i.test(target)) {
119
+ try {
120
+ target = fileURLToPath(target);
121
+ } catch {
122
+ return { kind: "missing", path: target };
123
+ }
124
+ }
125
+ if (!target || !path.isAbsolute(target)) return { kind: "missing", path: target };
126
+ target = path.resolve(target);
127
+ const stat = await fs.stat(target).catch(() => null);
128
+ if (!stat) return { kind: "missing", path: target };
129
+ let folder;
130
+ if (stat.isDirectory()) folder = target;
131
+ else if (/^(doc|report)\.html$/i.test(path.basename(target))) folder = path.dirname(target);
132
+ else return { kind: "plain", path: target };
133
+ if (stat.isDirectory() && isWorkspace(folder)) return { kind: "workspace", path: target, root: folder };
134
+ const { files, html, metadata } = await metadataAt(folder);
135
+ if (stat.isDirectory() && !html && (await listDocumentSlugs(folder).catch(() => [])).some(isSlugName)) return { kind: "folder", path: target, folder };
136
+ if (!html || (!files.old && !metadata) || metadata?.instructions === true) return { kind: "plain", path: target };
137
+ let root = null;
138
+ for (let directory = path.dirname(folder); ; directory = path.dirname(directory)) {
139
+ if (isWorkspace(directory)) {
140
+ root = directory;
141
+ break;
142
+ }
143
+ if (path.dirname(directory) === directory) break;
144
+ }
145
+ const slug = path.basename(folder);
146
+ if (root && workspaceKey(path.dirname(folder)) !== workspaceKey(root)) return { kind: "nested", path: target, root };
147
+ if (!isSlugName(slug)) return { kind: "name", path: target };
148
+ return { kind: "draft", path: files.html, folder, slug, root, parent: path.dirname(folder) };
149
+ }
150
+
151
+ /**
152
+ * The column of workspace `id` in `outline` (changed in place): the one that has `workspace: id`; with none
153
+ * (a workspace the sidebar does not show as it was read: the outline of a workspace's own tree, or past 50
154
+ * columns), one made right after the column `after` (an id; none: last), picked (`picked`) or whole, or null
155
+ * where no column can be made (50 already: the entry goes to the own column's top).
156
+ */
157
+ function columnFor(outline, id, { after = null, picked = false } = {}) {
158
+ let group = outline.groups.find((each) => each.workspace === id);
159
+ if (group) return group;
160
+ if (outline.groups.length >= GROUP_LIMITS.groups) return null;
161
+ group = { id: outline.groups.some((each) => each.id === id) ? newGroupId(outline.groups) : id, workspace: id, ...(picked ? { picked: true } : {}) };
162
+ const at = outline.groups.findIndex((each) => each.id === after);
163
+ outline.groups = at < 0 ? [...outline.groups, group] : [...outline.groups.slice(0, at + 1), group, ...outline.groups.slice(at + 1)];
164
+ outline.children[topOf(group.id)] ??= [];
165
+ return group;
166
+ }
167
+
168
+ /** `outline`'s column `id` (changed in place: its object replaced) as `change(column)` gives it, at index `to` when given. */
169
+ function changeColumn(outline, id, change, to = null) {
170
+ const groups = outline.groups.filter((each) => each.id !== id);
171
+ const at = outline.groups.findIndex((each) => each.id === id);
172
+ const next = change(outline.groups[at]);
173
+ groups.splice(to === null ? at : Math.min(to, groups.length), 0, next);
174
+ outline.groups = groups;
175
+ return next;
176
+ }
177
+
178
+ /** `outline`'s column `id` moved right after column `after` (one of it; none, or itself: where it is). */
179
+ function moveAfter(outline, id, after) {
180
+ if (!after || after === id || !outline.groups.some((each) => each.id === after)) return;
181
+ const groups = outline.groups.filter((each) => each.id !== id);
182
+ groups.splice(groups.findIndex((each) => each.id === after) + 1, 0, outline.groups.find((each) => each.id === id));
183
+ outline.groups = groups;
184
+ }
185
+
186
+ const shownColumn = ({ hidden, ...column }) => column;
187
+
188
+ /**
189
+ * Draft `slug` of workspace `draftRoot` put in the sidebar of workspace `treeRoot` (its outline, under the
190
+ * outline's lock; `changed()` once it is written), with the drafts under it in its own workspace's tree
191
+ * (documents: a web link of that workspace is none, nor a draft of a third its tree shows, the drafts under
192
+ * one going under the draft above it), those the tree does not hold yet. Its workspace's column is shown
193
+ * again when the user had removed it (hidden). How, `pick` says:
194
+ * - false (an Agent's gallery new or open there: joinDefaultTree, POST draft-join): one the outline holds
195
+ * (the file, or a whole column as it is read) stays where it is; else (a picked column) under its own
196
+ * parent's entry when the tree holds that, else last in its workspace's column;
197
+ * - true (the user's pick in 添加其他 Gallery 项目…, `after`: the column whose menu it came from): one shown
198
+ * in the sidebar already stays where it is (revealed, `existing`); its workspace's column hidden, or one the
199
+ * sidebar showed not before (`fresh`: its workspace registered just now, its column made only as the outline
200
+ * is read, filled whole), it is made a picked one holding it and those under it alone, right after `after`;
201
+ * one shown picked or whole takes it (under its own parent's entry, else last: one the user had taken out of
202
+ * a whole one); with none, a picked column is made right after `after`.
203
+ * Either way, what is put in a whole column is in its `left` no more.
204
+ * Resolves to { entry, existing: whether the file held it, so that nothing was put in, parent, added: the
205
+ * entries put in, column: the id of the column it is in, under: the entry it is under, or null, shown: whether
206
+ * the column was shown again }.
207
+ */
208
+ export async function addDraft(treeRoot, draftRoot, slug, { changed = () => {}, pick = false, after = null, fresh = false } = {}) {
209
+ const id = workspaceId(draftRoot);
210
+ const entry = draftEntry(id, slug);
211
+ const own = await readOutline(draftRoot, { others: false }).catch(() => ({ children: {}, links: [] })); // its own tree, read alone
212
+ // No document of that workspace: its web links, and the drafts of others its tree shows (~<ws id>/<slug>).
213
+ const none = (key) => own.links.includes(key) || isForeign(key);
214
+ const ownParent = (child) => {
215
+ let up = parentIn(own.children, child);
216
+ while (up !== null && !isTop(up) && none(up)) up = parentIn(own.children, up);
217
+ return up === null || isTop(up) ? null : up;
218
+ };
219
+ return withOutlineLock(treeRoot, async () => {
220
+ const { outline, stored } = await outlineAndFile(treeRoot);
221
+ const holds = (key) => Object.values(outline.children).some((list) => list.includes(key));
222
+ const storedHolds = Object.values(stored?.children ?? {}).some((list) => list.includes(entry));
223
+ const where = () => {
224
+ const parent = parentIn(outline.children, entry);
225
+ const top = topIn(outline.children, entry);
226
+ return { parent, column: top === null || top === "" ? DOCS : top.slice(1), under: parent !== null && !isTop(parent) ? parent : null };
227
+ };
228
+ const columnOf = (key) => outline.groups.find((each) => topOf(each.id) === topIn(outline.children, key)) ?? null;
229
+ let shown = false;
230
+ let column = outline.groups.find((each) => each.workspace === id) ?? null;
231
+ const put = () => {
232
+ const added = [];
233
+ const top = column ? topOf(column.id) : topOf(DOCS);
234
+ const above = ownParent(slug);
235
+ placeIn(outline, entry, above !== null && holds(draftEntry(id, above)) ? draftEntry(id, above) : top);
236
+ added.push(entry);
237
+ for (const child of descendantsIn(own.children, slug)) {
238
+ const childEntry = draftEntry(id, child);
239
+ if (none(child) || holds(childEntry)) continue;
240
+ placeIn(outline, childEntry, draftEntry(id, ownParent(child) ?? slug));
241
+ added.push(childEntry);
242
+ }
243
+ // Put in again: taken out of a whole column no more.
244
+ if (column?.left) {
245
+ const back = new Set(added.map((each) => draftOf(each).slug));
246
+ column = changeColumn(outline, column.id, ({ left, ...rest }) => {
247
+ const still = left.filter((taken) => !back.has(taken));
248
+ return still.length ? { ...rest, left: still } : rest;
249
+ });
250
+ }
251
+ return added;
252
+ };
253
+ let added = [];
254
+ let existing = false;
255
+ if (!pick) {
256
+ if (column?.hidden) {
257
+ column = changeColumn(outline, column.id, shownColumn);
258
+ shown = true;
259
+ }
260
+ existing = storedHolds;
261
+ if (!holds(entry)) {
262
+ column ??= columnFor(outline, id);
263
+ added = put();
264
+ } else if (!storedHolds) added = [entry];
265
+ } else {
266
+ // A column the file holds not, of a workspace the sidebar showed not before: made as it was read, whole.
267
+ const madeNow = Boolean(column) && fresh && !(stored?.groups ?? []).some((each) => each.workspace === id);
268
+ const at = columnOf(entry);
269
+ const there = holds(entry) && !at?.hidden && !(madeNow && at?.id === column.id);
270
+ if (there) existing = true;
271
+ else if (column?.hidden || madeNow) {
272
+ // Made a picked column holding it and those under it alone, shown, right after `after`.
273
+ const top = topOf(column.id);
274
+ for (const key of descendantsIn(outline.children, top)) delete outline.children[key];
275
+ outline.children[top] = [];
276
+ shown = Boolean(column.hidden);
277
+ column = changeColumn(outline, column.id, ({ left: _left, ...each }) => ({ ...shownColumn(each), picked: true }));
278
+ moveAfter(outline, column.id, after);
279
+ added = put();
280
+ } else {
281
+ column ??= columnFor(outline, id, { after, picked: true });
282
+ added = put();
283
+ }
284
+ }
285
+ if (!writtenAs(outline, stored)) {
286
+ await writeOutline(treeRoot, outline);
287
+ changed();
288
+ }
289
+ return { entry, existing, added, shown, ...where() };
290
+ });
291
+ }
292
+
293
+ /**
294
+ * Workspace `otherRoot` shown whole in the sidebar of workspace `treeRoot` (添加其他 Gallery 项目… with a
295
+ * workspace: its outline, under the outline's lock; `changed()` once it is written): its column (made when
296
+ * there is none) shown, whole (all its documents, those the user had taken out of it too: those no column
297
+ * holds come as the outline is read), right after the column `after` (an id). Resolves to { column: its id,
298
+ * moved, shown, whole: whether each changed }.
299
+ */
300
+ export async function addWorkspace(treeRoot, otherRoot, { changed = () => {}, after = null } = {}) {
301
+ const id = workspaceId(otherRoot);
302
+ return withOutlineLock(treeRoot, async () => {
303
+ const { outline, stored } = await outlineAndFile(treeRoot);
304
+ let column = columnFor(outline, id, { after });
305
+ if (!column) return { column: null, moved: false, shown: false, whole: false };
306
+ const shown = Boolean(column.hidden);
307
+ const whole = Boolean(column.picked || column.left);
308
+ // Whole: all its documents, those the user took out of it before too (its `left` gone).
309
+ column = changeColumn(outline, column.id, ({ picked: _picked, left: _left, ...each }) => shownColumn(each));
310
+ const before = outline.groups.findIndex((each) => each.id === column.id);
311
+ moveAfter(outline, column.id, after);
312
+ const moved = outline.groups.findIndex((each) => each.id === column.id) !== before;
313
+ if (!writtenAs(outline, stored)) {
314
+ await writeOutline(treeRoot, outline);
315
+ changed();
316
+ }
317
+ return { column: column.id, moved, shown, whole };
318
+ });
319
+ }
320
+
321
+ /**
322
+ * The other registered workspaces (but `treeRoot`'s own), for 添加其他 Gallery 项目…'s list: [{ ws, root, name,
323
+ * above, documents: [{ entry, slug, title, icon, path, depth }] }], each workspace's documents in the order
324
+ * its own sidebar has them (depth: how deep under others there), those whose folder is no workspace now left
325
+ * out, a clash of ids too (refused, never guessed), the one used last first. Reads files only: nothing of
326
+ * another workspace is written (its 写作偏好 page is no draft).
327
+ */
328
+ export async function projectChoices(treeRoot, { ids = registeredIds(), stops = nameStops(), registry = readRegistry() } = {}) {
329
+ const seen = new Map(registry.workspaces.map(({ root, seen: at }) => [workspaceKey(root), Date.parse(at)]));
330
+ const wanted = new Set([...ids].filter(([, roots]) => roots.length === 1 && workspaceKey(roots[0]) !== workspaceKey(treeRoot)).map(([id]) => id));
331
+ const names = workspaceNames(wanted, { ids, stops });
332
+ const found = await Promise.all([...names].filter(([, { there }]) => there).map(async ([id, { name, above, root }]) => {
333
+ const tree = await readOutline(root, { others: false }).catch(() => null);
334
+ const documents = [];
335
+ const isDocument = (slug) => !(tree?.links ?? []).includes(slug) && !isForeign(slug) && isSlugName(slug);
336
+ const walk = (key, depth) => (tree?.children[key] ?? []).forEach((slug) => {
337
+ const document = isDocument(slug);
338
+ if (document) documents.push({ slug, depth });
339
+ walk(slug, document ? depth + 1 : depth);
340
+ });
341
+ for (const { id: column } of tree?.groups ?? []) walk(topOf(column), 0);
342
+ const listed = await Promise.all(documents.map(async ({ slug, depth }) => {
343
+ const { files, metadata } = await metadataAt(path.join(root, slug));
344
+ return { entry: draftEntry(id, slug), slug, title: oneLine(metadata?.title) || slug, icon: shownIcon(metadata?.icon), path: files.html, depth };
345
+ }));
346
+ return { ws: id, root, name, above, seen: seen.get(workspaceKey(root)) ?? 0, documents: listed };
347
+ }));
348
+ return found.sort((a, b) => b.seen - a.seen).map(({ seen: _seen, ...rest }) => rest);
349
+ }
350
+
351
+
352
+ /** The environment variable that keeps gallery new and gallery open from putting their draft in the default workspace's sidebar (the tests set it). */
353
+ export const NO_DEFAULT_TREE = "GALLERY_NO_DEFAULT_TREE";
354
+ const set = (value) => !["", "0", undefined].includes(value);
355
+
356
+ /**
357
+ * gallery new and gallery open in a workspace other than the default one (src/shared/workspace.mjs
358
+ * defaultWorkspace): draft `slug` of workspace `root` put in the default workspace's sidebar (addDraft,
359
+ * with the drafts under it), through the default workspace's server when one runs (POST
360
+ * /__gallery/workspace/draft-join: in its queue, its workbench told at once), else here under the
361
+ * outline's lock. Null where it is not done: NO_DEFAULT_TREE set, `root` the default workspace, no
362
+ * default workspace there yet (nothing is made for it), or `root` not on the registry under its id (a
363
+ * workspace in the system's temporary folder, or a clash of ids: an entry would name none). Else
364
+ * addDraft's answer with `home` (the default workspace) and `names`: { column: the name of the column
365
+ * it is in, under: the title of the draft it is under, or null }; or { failed: why } (nothing written).
366
+ */
367
+ export async function joinDefaultTree(root, slug, { env = process.env } = {}) {
368
+ if (set(env[NO_DEFAULT_TREE])) return null;
369
+ const home = defaultWorkspace({ env });
370
+ if (workspaceKey(home) === workspaceKey(root) || !isWorkspace(home)) return null;
371
+ const ids = registeredIds();
372
+ const found = workspaceOf(workspaceId(root), ids);
373
+ if (!found.root || workspaceKey(found.root) !== workspaceKey(root)) return null;
374
+ let done = null;
375
+ try {
376
+ const server = await findPreviewServer({ reportsDirectory: home });
377
+ if (server) done = await joinThrough(server, root, slug);
378
+ done ??= await addDraft(home, root, slug);
379
+ } catch (error) {
380
+ return { failed: error.message, home };
381
+ }
382
+ return { ...done, home, names: await placeNames(home, done, ids) };
383
+ }
384
+
385
+ // POST draft-join to the default workspace's server; null when no server of it takes it (gone, or an older Gallery's).
386
+ async function joinThrough(server, root, slug) {
387
+ const host = `127.0.0.1:${server.port}`;
388
+ try {
389
+ const response = await fetch(`http://${host}/__gallery/workspace/draft-join`, {
390
+ method: "POST",
391
+ headers: { "Content-Type": "application/json", Origin: `http://${host}`, "X-Gallery-Token": server.token },
392
+ body: JSON.stringify({ root, slug }),
393
+ signal: AbortSignal.timeout(30000),
394
+ });
395
+ const answer = await response.json().catch(() => null);
396
+ return answer?.ok === true && typeof answer.entry === "string" ? answer : null;
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
401
+
402
+ // The names a line says of where a draft went: its column's (the user's title, else its workspace's
403
+ // name, else 默认文档, the default workspace's own column, or 链接) and the title of the draft or document it is under.
404
+ async function placeNames(home, { column, under }, ids) {
405
+ const { groups } = await readOutline(home);
406
+ const group = groups.find((each) => each.id === column);
407
+ let name = group?.title ?? null;
408
+ if (!name && group?.workspace) name = workspaceNames(new Set([group.workspace]), { ids }).get(group.workspace)?.name ?? null;
409
+ name ??= group?.role === "links" ? "链接" : group?.id === DOCS || !group ? "默认文档" : "未命名栏"; // its own column is the default workspace's
410
+ let above = null;
411
+ if (under) {
412
+ if (draftOf(under)) above = (await draftFacts(under, ids)).title;
413
+ else {
414
+ const metadata = await fs.readFile(documentFiles(path.join(home, under)).json, "utf8").then(JSON.parse, () => null);
415
+ above = oneLine(metadata?.title) || under;
416
+ }
417
+ }
418
+ return { column: name, under: above };
419
+ }
420
+
421
+ /** The line gallery new and gallery open print of joinDefaultTree's answer (null for none). */
422
+ export function joinedLine(joined) {
423
+ if (!joined) return null;
424
+ const where = `默认工作区(${joined.home})的侧边栏`;
425
+ if (joined.failed) return `${where}:没有加进去(${joined.failed})`;
426
+ if (joined.existing) return `${where}里已经有它(${joined.names.under ? `在「${joined.names.under}」下面` : `「${joined.names.column}」栏`})`;
427
+ const more = joined.added.length > 1 ? `,连同它下面的 ${joined.added.length - 1} 篇` : "";
428
+ return `${where}:已加进${joined.names.under ? `「${joined.names.under}」下面` : `「${joined.names.column}」栏`}${more}(拖动只改那里的显示位置,文件不动)`;
429
+ }
430
+
431
+ /** Whether a gallery.json at `folder` would be another program's or not readable (what draft-add refuses to make a workspace of). */
432
+ export const foreignMarker = (folder) => existsSync(path.join(folder, "gallery.json")) && !isWorkspace(folder);
@@ -0,0 +1,37 @@
1
+ import { ICON_COLOR_VALUES } from "./icon-value.mjs";
2
+
3
+ // Gallery's own line icons as a custom page's icon (page.json's `icon`): the look of the workbench's sidebar
4
+ // head, whose 新建 and 搜索 are Lucide's line icons (a 1.75 line in 24 units, workbench/app/components/icons.ts),
5
+ // so that a page Gallery makes stands among them in the same hand (the user's word of 2026-11-09: no filled
6
+ // icon there). A value is `/icons/lucide/<name>.svg`, with no color of its own: the workbench masks it in the
7
+ // color of where it stands (the sidebar's icon gray, as an icon of no icon's color), the browser tab draws it
8
+ // in the tab's gray (as a page with no icon, icon-value.mjs DEFAULT_TAB_ICON). It is none of the three kinds
9
+ // of a document's icon (icon-value.mjs iconKind: no document has one), nor an icon of the 图标 tab: Gallery's
10
+ // default home page's house (src/editor/home-page.mjs HOME_ICON), alone so far. The icon route draws it
11
+ // (src/render/emoji.mjs iconImage).
12
+
13
+ const LINE_ICONS = {
14
+ // Lucide's house (1.67.0), the one the sidebar drew for the home page before its page.json said its icon.
15
+ house: '<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
16
+ };
17
+ const LINE = /^\/icons\/lucide\/([a-z0-9-]+)\.svg$/;
18
+
19
+ /** The value of Gallery's line icon `name` (one LINE_ICONS has). */
20
+ export const lineIconValue = (name) => `/icons/lucide/${name}.svg`;
21
+
22
+ /** Whether `value` names one of Gallery's line icons. */
23
+ export const isLineIcon = (value) => lineIconSvg(value) !== null;
24
+
25
+ const [TAB_LIGHT, TAB_DARK] = ICON_COLOR_VALUES.gray;
26
+
27
+ /**
28
+ * The SVG of the line icon `value` names, or null (none of them): its lines in `color` (black, the
29
+ * workbench's mask); with `tab`, a browser tab's, its box cropped to `1 1 22 22` as the tab's icon of a
30
+ * page with no icon is, in the tab glyphs' gray, light or dark as the system is.
31
+ */
32
+ export function lineIconSvg(value, { color = "currentColor", tab = false } = {}) {
33
+ const name = typeof value === "string" ? LINE.exec(value)?.[1] : null;
34
+ if (!name || !Object.hasOwn(LINE_ICONS, name)) return null;
35
+ const style = tab ? `<style>svg{color:${TAB_LIGHT}}@media (prefers-color-scheme:dark){svg{color:${TAB_DARK}}}</style>` : "";
36
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${tab ? "1 1 22 22" : "0 0 24 24"}" fill="none" stroke="${tab ? "currentColor" : color}" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">${style}${LINE_ICONS[name]}</svg>`;
37
+ }
@@ -0,0 +1,126 @@
1
+ import { said } from "./i18n/index.mjs";
2
+ import { isExtensionId } from "./outline-shape.mjs";
3
+ import { linkAddress, linkIcon } from "./web-links.mjs";
4
+
5
+ // The sidebar column an extension provides (docs/workbench.md "Extension columns", references/extensions.md
6
+ // for the Agent that writes one): <workspace>/extensions/<id>/sidebar.json,
7
+ // { "title": "语雀", "icon": "📗", "entries": [
8
+ // { "id": "team", "title": "团队知识库", "url": "https://www.yuque.com/team",
9
+ // "children": [ { "id": "weekly", "title": "周报", "url": "https://www.yuque.com/team/weekly" } ] },
10
+ // { "id": "panel", "title": "发布面板", "url": "http://127.0.0.1:4318/" } ] }
11
+ // `title` is the column's name (100 characters at most), `icon` an emoji or an icon of the 图标 tab (no
12
+ // picture is ever loaded from the web). An entry has an `id` (1 to 128 of A-Z a-z 0-9 . _ : -, once in
13
+ // the file: the extension keeps it, for it is what an address and an open entry go by), a `title` (200
14
+ // at most), an `url` (http or https, 2048 at most: none, and the entry only holds its `children`, which
15
+ // a click opens and closes), an `icon` and `children` (8 levels, 2000 entries in all, 1 MB the file).
16
+ // Pure and the same wherever it is read (the server answers the workbench with it, gallery extension
17
+ // check prints it); reading the file is ./extensions.mjs's (Node's). What makes the file unusable is a
18
+ // refusal (`error`, said() of a reason: the column then shows one row, 条目读不出来:<reason>); what is
19
+ // only left out of it (an icon that is none) is a note, said by gallery extension check and shown nowhere.
20
+
21
+ /** The extension's file in its folder. */
22
+ export const SIDEBAR_FILE = "sidebar.json";
23
+ export const SIDEBAR_LIMITS = { bytes: 1_048_576, levels: 8, entries: 2000, id: 128, title: 200, column: 100 };
24
+ export const entryIdPattern = /^[A-Za-z0-9._:-]{1,128}$/;
25
+
26
+ /** An entry's key in the workbench's address (`#~<extension id>/<entry id>`), and as its row says it. */
27
+ export const extensionKey = (extension, entry) => `~${extension}/${entry}`;
28
+ /** The extension and the entry a key names: { extension, entry }, or null for what is none. */
29
+ export function parseExtensionKey(value) {
30
+ if (typeof value !== "string" || !value.startsWith("~")) return null;
31
+ const at = value.indexOf("/");
32
+ if (at < 0) return null;
33
+ const extension = value.slice(1, at);
34
+ const entry = value.slice(at + 1);
35
+ return isExtensionId(extension) && entryIdPattern.test(entry) ? { extension, entry } : null;
36
+ }
37
+
38
+ const tidy = (value) => (typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "");
39
+ const refused = (message) => ({ error: message });
40
+
41
+ /**
42
+ * What the text of a sidebar.json says: { sidebar: { title, icon?, entries, count }, notes } with each
43
+ * entry { id, title, icon?, url?, children? } (the address as the URL spells it, a title's white space
44
+ * run together), or { error } (said(): why the file is no sidebar), the first reason found. `notes`: said()
45
+ * of what was left out (an icon that is no emoji nor icon of the 图标 tab).
46
+ */
47
+ export function readSidebarText(text) {
48
+ const source = String(text);
49
+ if (source.length > SIDEBAR_LIMITS.bytes) return refused(said("文件超过 1MB"));
50
+ let value;
51
+ try {
52
+ value = JSON.parse(source.replace(/^/, ""));
53
+ } catch (error) {
54
+ return refused(said("不是 JSON:{0}", error.message));
55
+ }
56
+ if (!value || typeof value !== "object" || Array.isArray(value)) return refused(said("最外层要是一个对象,有 title 和 entries"));
57
+ const title = tidy(value.title);
58
+ if (!title || title.length > SIDEBAR_LIMITS.column) return refused(said("title 要有,是栏的名字(100 个字以内)"));
59
+ if (!Array.isArray(value.entries)) return refused(said("entries 要是一个列表"));
60
+ const notes = [];
61
+ let icon;
62
+ if (value.icon !== undefined && value.icon !== null && value.icon !== "") {
63
+ if (linkIcon(value.icon)) icon = value.icon;
64
+ else notes.push(said("icon 不是表情,也不是图标页签里的图标,没有显示"));
65
+ }
66
+ const ids = new Set();
67
+ let count = 0;
68
+ const walk = (list, path, level) => {
69
+ if (level > SIDEBAR_LIMITS.levels && list.length) return { error: said("条目超过 {0} 层({1})", SIDEBAR_LIMITS.levels, path) };
70
+ const made = [];
71
+ for (const [index, raw] of list.entries()) {
72
+ const where = `${path}[${index}]`;
73
+ if (++count > SIDEBAR_LIMITS.entries) return { error: said("条目超过 {0} 个", SIDEBAR_LIMITS.entries) };
74
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { error: said("{0} 要是一个对象", where) };
75
+ if (typeof raw.id !== "string" || !entryIdPattern.test(raw.id)) return { error: said("{0} 的 id 不对(1 到 128 个字符,只能用字母、数字和 . _ : -)", where) };
76
+ if (ids.has(raw.id)) return { error: said("{0} 的 id「{1}」重复了(同一个文件里 id 不能重)", where, raw.id) };
77
+ ids.add(raw.id);
78
+ const name = tidy(raw.title);
79
+ if (!name || name.length > SIDEBAR_LIMITS.title) return { error: said("{0} 要有 title(名称,200 个字以内)", where) };
80
+ const entry = { id: raw.id, title: name };
81
+ if (raw.url !== undefined && raw.url !== null && raw.url !== "") {
82
+ const address = linkAddress(raw.url);
83
+ if (address.refused === "scheme") return { error: said("{0} 的 url 只能是 http 或 https 的网址", where) };
84
+ if (address.refused === "long") return { error: said("{0} 的 url 太长了(最多 2048 个字符)", where) };
85
+ if (address.refused) return { error: said("{0} 的 url 不是一个网址", where) };
86
+ entry.url = address.url;
87
+ }
88
+ if (raw.icon !== undefined && raw.icon !== null && raw.icon !== "") {
89
+ if (linkIcon(raw.icon)) entry.icon = raw.icon;
90
+ else notes.push(said("{0} 的 icon 不是表情,也不是图标页签里的图标,没有显示", where));
91
+ }
92
+ if (raw.children !== undefined && raw.children !== null) {
93
+ if (!Array.isArray(raw.children)) return { error: said("{0} 的 children 要是一个列表", where) };
94
+ const inner = walk(raw.children, `${where}.children`, level + 1);
95
+ if (inner.error) return inner;
96
+ if (inner.entries.length) entry.children = inner.entries;
97
+ }
98
+ made.push(entry);
99
+ }
100
+ return { entries: made };
101
+ };
102
+ const walked = walk(value.entries, "entries", 1);
103
+ if (walked.error) return refused(walked.error);
104
+ return { sidebar: { title, ...(icon ? { icon } : {}), entries: walked.entries, count }, notes };
105
+ }
106
+
107
+ /** The entry `id` of `entries` (a tree: children under each), or null. */
108
+ export function entryIn(entries, id) {
109
+ for (const entry of entries ?? []) {
110
+ if (entry.id === id) return entry;
111
+ const inside = entryIn(entry.children, id);
112
+ if (inside) return inside;
113
+ }
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * The entry key `key` (`~<extension id>/<entry id>`, a workbench's hash, decoded) names among `extensions`
119
+ * (the columns as GET outline lists them: [{ id, entries }]), or null: what a link to an entry's address
120
+ * opens (one with an `url`) wherever a page reads it (the workbench, a page alone in a tab, 演示视图).
121
+ */
122
+ export function entryOfKey(extensions, key) {
123
+ const named = parseExtensionKey(key);
124
+ const extension = named && (extensions ?? []).find(({ id }) => id === named.extension);
125
+ return extension ? entryIn(extension.entries, named.entry) : null;
126
+ }