@heycys/gallery 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/THIRD-PARTY-NOTICES.md +1 -1
  2. package/browser/editor/components/columns/page.mjs +4 -4
  3. package/browser/editor/components/embed/index.mjs +2 -2
  4. package/browser/editor/components/flowchart/add.mjs +7 -6
  5. package/browser/editor/components/flowchart/page.mjs +1 -1
  6. package/browser/editor/components/icons.mjs +8 -0
  7. package/browser/editor/components/image/crop.mjs +6 -9
  8. package/browser/editor/components/installed/index.mjs +11 -4
  9. package/browser/editor/components/installed/page.mjs +27 -16
  10. package/browser/editor/components/installed/served.mjs +6 -5
  11. package/browser/editor/components/instances.mjs +18 -0
  12. package/browser/editor/components/list/page.mjs +3 -6
  13. package/browser/editor/components/registry.mjs +13 -4
  14. package/browser/editor/components/table/index.mjs +59 -2
  15. package/browser/editor/components/table/page.mjs +1 -1
  16. package/browser/editor/page/annotate/card.mjs +3 -6
  17. package/browser/editor/page/autosave.mjs +310 -37
  18. package/browser/editor/page/board/client.mjs +57 -0
  19. package/browser/editor/page/board/hint.mjs +145 -0
  20. package/browser/editor/page/board/history.mjs +119 -0
  21. package/browser/editor/page/board/index.mjs +970 -0
  22. package/browser/editor/page/board/settings.mjs +209 -0
  23. package/browser/editor/page/caret.mjs +1 -1
  24. package/browser/editor/page/changes/bar.mjs +6 -14
  25. package/browser/editor/page/changes/index.mjs +2 -2
  26. package/browser/editor/page/clipboard/import/html.mjs +29 -6
  27. package/browser/editor/page/clipboard/import/inline.mjs +32 -0
  28. package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
  29. package/browser/editor/page/clipboard/import/read.mjs +6 -1
  30. package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
  31. package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
  32. package/browser/editor/page/clipboard/index.mjs +124 -13
  33. package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
  34. package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
  35. package/browser/editor/page/clipboard/own/selection.mjs +1 -1
  36. package/browser/editor/page/columns/index.mjs +48 -46
  37. package/browser/editor/page/commit.mjs +80 -22
  38. package/browser/editor/page/conflicts.mjs +7 -7
  39. package/browser/editor/page/core.mjs +31 -15
  40. package/browser/editor/page/custom-page.mjs +106 -0
  41. package/browser/editor/page/editor.mjs +51 -14
  42. package/browser/editor/page/emoji-picker/styles.mjs +7 -12
  43. package/browser/editor/page/emoji-picker/upload.mjs +2 -2
  44. package/browser/editor/page/handle/index.mjs +5 -8
  45. package/browser/editor/page/history.mjs +24 -4
  46. package/browser/editor/page/links/index.mjs +5 -3
  47. package/browser/editor/page/links/pages.mjs +27 -9
  48. package/browser/editor/page/look.mjs +54 -3
  49. package/browser/editor/page/margin.mjs +18 -3
  50. package/browser/editor/page/media-menu/index.mjs +6 -7
  51. package/browser/editor/page/menu.mjs +18 -7
  52. package/browser/editor/page/notices.mjs +2 -2
  53. package/browser/editor/page/page-objects.mjs +13 -2
  54. package/browser/editor/page/panel.mjs +4 -9
  55. package/browser/editor/page/present-links.mjs +11 -2
  56. package/browser/editor/page/resize/index.mjs +42 -29
  57. package/browser/editor/page/served.mjs +14 -0
  58. package/browser/editor/page/session.mjs +48 -4
  59. package/browser/editor/page/slash-menu/index.mjs +3 -3
  60. package/browser/editor/page/table/cells.mjs +102 -46
  61. package/browser/editor/page/table/icons.mjs +2 -0
  62. package/browser/editor/page/table/index.mjs +29 -10
  63. package/browser/editor/page/table/pictures.mjs +299 -0
  64. package/browser/editor/page/table/views.mjs +150 -0
  65. package/browser/editor/page/text-select/index.mjs +26 -12
  66. package/browser/editor/page/todo-writes.mjs +75 -0
  67. package/browser/editor/page/typing/index.mjs +53 -3
  68. package/browser/editor/page/ui.mjs +13 -0
  69. package/browser/shared/block-packs.mjs +404 -0
  70. package/browser/shared/board-places.mjs +224 -0
  71. package/browser/shared/card-size.mjs +59 -0
  72. package/browser/shared/colors.mjs +3 -2
  73. package/browser/shared/component-catalog.mjs +323 -46
  74. package/browser/shared/component-source.mjs +37 -0
  75. package/browser/shared/components/index.mjs +12 -3
  76. package/browser/shared/components/installed.mjs +35 -3
  77. package/browser/shared/components/page.mjs +18 -8
  78. package/browser/shared/components/state.mjs +5 -1
  79. package/browser/shared/document-look.mjs +10 -5
  80. package/browser/shared/drafts.mjs +432 -0
  81. package/browser/shared/extension-sidebar.mjs +126 -0
  82. package/browser/shared/extensions.mjs +79 -0
  83. package/browser/shared/file-table.mjs +101 -0
  84. package/browser/shared/i18n/en.mjs +307 -27
  85. package/browser/shared/json-layout.mjs +47 -0
  86. package/browser/shared/net-cache.mjs +133 -0
  87. package/browser/shared/net-keys.mjs +32 -0
  88. package/browser/shared/net-policy.mjs +177 -0
  89. package/browser/shared/outline-shape.mjs +128 -0
  90. package/browser/shared/outline.mjs +331 -67
  91. package/browser/shared/pack-manifest.mjs +77 -0
  92. package/browser/shared/page-address.mjs +6 -3
  93. package/browser/shared/page-cards.mjs +308 -0
  94. package/browser/shared/page-links.mjs +69 -6
  95. package/browser/shared/pages.mjs +388 -0
  96. package/browser/shared/paths.mjs +6 -0
  97. package/browser/shared/preview-server.mjs +3 -7
  98. package/browser/shared/session.mjs +8 -1
  99. package/browser/shared/shipped-blocks.mjs +299 -0
  100. package/browser/shared/table-chart.mjs +33 -1
  101. package/browser/shared/todos.mjs +83 -0
  102. package/browser/shared/user-directory.mjs +24 -0
  103. package/browser/shared/versions.mjs +14 -0
  104. package/browser/shared/web-links.mjs +134 -0
  105. package/browser/shared/workspace-columns.mjs +120 -0
  106. package/browser/shared/workspace-name.mjs +154 -0
  107. package/browser/shared/workspace-query.mjs +125 -0
  108. package/browser/shared/workspace-registry.mjs +272 -0
  109. package/browser/shared/workspace.mjs +171 -20
  110. package/dist/chunk-2NRBY6AJ.mjs +2 -0
  111. package/dist/chunk-32ZUEFZ7.mjs +2 -0
  112. package/dist/chunk-35NQ4PLQ.mjs +2 -0
  113. package/dist/chunk-3KJHLHUO.mjs +2 -0
  114. package/dist/chunk-3NJACR4W.mjs +2 -0
  115. package/dist/chunk-4AV456RQ.mjs +2 -0
  116. package/dist/chunk-4OZWXZE5.mjs +5 -0
  117. package/dist/chunk-5XYHDBJ5.mjs +2 -0
  118. package/dist/chunk-5ZJA7CXT.mjs +2 -0
  119. package/dist/chunk-6A53MZYZ.mjs +2 -0
  120. package/dist/chunk-6EXH3ORZ.mjs +166 -0
  121. package/dist/chunk-6EZ7FNIK.mjs +3 -0
  122. package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
  123. package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
  124. package/dist/chunk-B4P2CM2J.mjs +3 -0
  125. package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
  126. package/dist/chunk-BVEFGE4B.mjs +2 -0
  127. package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
  128. package/dist/chunk-CRJEG7MT.mjs +2 -0
  129. package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
  130. package/dist/chunk-D4EURAX4.mjs +4 -0
  131. package/dist/chunk-DQCE7TJY.mjs +5 -0
  132. package/dist/chunk-DR73Y2UJ.mjs +3 -0
  133. package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
  134. package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
  135. package/dist/chunk-EURLTOTZ.mjs +4 -0
  136. package/dist/chunk-FJYN7RTH.mjs +4 -0
  137. package/dist/chunk-FWNRPMCJ.mjs +12 -0
  138. package/dist/chunk-FWW4CSAZ.mjs +2 -0
  139. package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
  140. package/dist/chunk-GY3FAUVM.mjs +2 -0
  141. package/dist/chunk-HLYV7RIH.mjs +2 -0
  142. package/dist/chunk-HMYTM3GT.mjs +15 -0
  143. package/dist/chunk-IA4PSIMD.mjs +2 -0
  144. package/dist/chunk-JEUK3ZO7.mjs +2 -0
  145. package/dist/chunk-KTKHRC6H.mjs +2 -0
  146. package/dist/chunk-L7UP52JO.mjs +2 -0
  147. package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
  148. package/dist/chunk-N76HWBA3.mjs +10 -0
  149. package/dist/chunk-N7AZZLGT.mjs +548 -0
  150. package/dist/chunk-NAMN3RAF.mjs +2 -0
  151. package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
  152. package/dist/chunk-NG375ILQ.mjs +2 -0
  153. package/dist/chunk-NVRNLK25.mjs +6 -0
  154. package/dist/chunk-NWFBE2YA.mjs +2 -0
  155. package/dist/chunk-NWR2LT5B.mjs +2 -0
  156. package/dist/chunk-OJKMINIU.mjs +20 -0
  157. package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
  158. package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
  159. package/dist/chunk-PAREWQGB.mjs +4 -0
  160. package/dist/chunk-POQSE4C2.mjs +2 -0
  161. package/dist/chunk-PUOQ65NC.mjs +2 -0
  162. package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
  163. package/dist/chunk-PZDLJJTJ.mjs +3 -0
  164. package/dist/chunk-Q47PLKIJ.mjs +15 -0
  165. package/dist/chunk-Q4UEPUWF.mjs +2 -0
  166. package/dist/chunk-QKXMPUXC.mjs +2 -0
  167. package/dist/chunk-QOUEFUCH.mjs +16 -0
  168. package/dist/chunk-RFDZXFJF.mjs +13 -0
  169. package/dist/chunk-RKPWLGZ3.mjs +4 -0
  170. package/dist/chunk-RMECZA2K.mjs +2 -0
  171. package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
  172. package/dist/chunk-SX4LJYT6.mjs +8 -0
  173. package/dist/chunk-SXHBYZST.mjs +16 -0
  174. package/dist/chunk-TANZRINB.mjs +2 -0
  175. package/dist/chunk-TEZAWDSQ.mjs +2 -0
  176. package/dist/chunk-UBBRTJ2R.mjs +24 -0
  177. package/dist/chunk-UFNYFFKO.mjs +20 -0
  178. package/dist/chunk-UGWYKHBL.mjs +170 -0
  179. package/dist/chunk-UKSHDZZ6.mjs +2 -0
  180. package/dist/chunk-UNOYVIYR.mjs +2 -0
  181. package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
  182. package/dist/chunk-VJPFVNNW.mjs +15 -0
  183. package/dist/chunk-VS3ZMVGX.mjs +5 -0
  184. package/dist/chunk-VTKNEYT3.mjs +6 -0
  185. package/dist/chunk-VXACTLO3.mjs +3 -0
  186. package/dist/chunk-YI43RG4P.mjs +2 -0
  187. package/dist/chunk-YQGT4AB2.mjs +6 -0
  188. package/dist/chunk-ZB4H7ZN4.mjs +2 -0
  189. package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
  190. package/dist/chunk-ZTHICSSF.mjs +2 -0
  191. package/dist/chunk-ZUNVAXLH.mjs +2 -0
  192. package/dist/chunk-ZWPMCK7P.mjs +2 -0
  193. package/dist/cli.mjs +1 -1
  194. package/dist/server.mjs +1 -1
  195. package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
  196. package/dist/worker.mjs +1 -1
  197. package/kit/board.css +95 -0
  198. package/kit/board.js +250 -0
  199. package/kit/charts.js +5 -71
  200. package/kit/code.js +14 -14
  201. package/kit/report.css +255 -17
  202. package/kit/runtime.js +1600 -125
  203. package/kit/state.js +10 -1
  204. package/kit/tables.js +128 -43
  205. package/kit/ui.js +77 -35
  206. package/package.json +2 -1
  207. package/packs/gallery/card/component.json +14 -0
  208. package/packs/gallery/card/example.html +10 -0
  209. package/packs/gallery/card/preview.svg +1 -0
  210. package/packs/gallery/card/style.css +6 -0
  211. package/packs/gallery/card/template.html +6 -0
  212. package/packs/gallery/card/writing.md +14 -0
  213. package/packs/gallery/chart-card/component.json +15 -0
  214. package/packs/gallery/chart-card/example.html +14 -0
  215. package/packs/gallery/chart-card/kit.js +130 -0
  216. package/packs/gallery/chart-card/preview.svg +1 -0
  217. package/packs/gallery/chart-card/style.css +42 -0
  218. package/packs/gallery/chart-card/template.html +13 -0
  219. package/packs/gallery/chart-card/writing.md +20 -0
  220. package/packs/gallery/chart-view/component.json +20 -0
  221. package/packs/gallery/chart-view/example.html +13 -0
  222. package/packs/gallery/chart-view/kit.js +355 -0
  223. package/packs/gallery/chart-view/preview.svg +1 -0
  224. package/packs/gallery/chart-view/style.css +94 -0
  225. package/packs/gallery/chart-view/template.html +3 -0
  226. package/packs/gallery/chart-view/writing.md +22 -0
  227. package/packs/gallery/countdown/component.json +18 -0
  228. package/packs/gallery/countdown/example.html +3 -0
  229. package/packs/gallery/countdown/kit.js +156 -0
  230. package/packs/gallery/countdown/preview.svg +1 -0
  231. package/packs/gallery/countdown/style.css +130 -0
  232. package/packs/gallery/countdown/template.html +3 -0
  233. package/packs/gallery/countdown/writing.md +22 -0
  234. package/packs/gallery/habit/component.json +14 -0
  235. package/packs/gallery/habit/example.html +3 -0
  236. package/packs/gallery/habit/kit.js +295 -0
  237. package/packs/gallery/habit/preview.svg +1 -0
  238. package/packs/gallery/habit/style.css +246 -0
  239. package/packs/gallery/habit/template.html +3 -0
  240. package/packs/gallery/habit/writing.md +21 -0
  241. package/packs/gallery/heatmap/component.json +18 -0
  242. package/packs/gallery/heatmap/example.html +3 -0
  243. package/packs/gallery/heatmap/kit.js +351 -0
  244. package/packs/gallery/heatmap/preview.svg +1 -0
  245. package/packs/gallery/heatmap/style.css +201 -0
  246. package/packs/gallery/heatmap/template.html +3 -0
  247. package/packs/gallery/heatmap/writing.md +21 -0
  248. package/packs/gallery/kanban/component.json +18 -0
  249. package/packs/gallery/kanban/example.html +12 -0
  250. package/packs/gallery/kanban/kit.js +540 -0
  251. package/packs/gallery/kanban/preview.svg +1 -0
  252. package/packs/gallery/kanban/style.css +212 -0
  253. package/packs/gallery/kanban/template.html +3 -0
  254. package/packs/gallery/kanban/writing.md +20 -0
  255. package/packs/gallery/metric/component.json +22 -0
  256. package/packs/gallery/metric/example.html +15 -0
  257. package/packs/gallery/metric/kit.js +248 -0
  258. package/packs/gallery/metric/preview.svg +1 -0
  259. package/packs/gallery/metric/style.css +92 -0
  260. package/packs/gallery/metric/template.html +3 -0
  261. package/packs/gallery/metric/writing.md +23 -0
  262. package/packs/gallery/note/component.json +14 -0
  263. package/packs/gallery/note/example.html +3 -0
  264. package/packs/gallery/note/kit.js +102 -0
  265. package/packs/gallery/note/preview.svg +1 -0
  266. package/packs/gallery/note/style.css +76 -0
  267. package/packs/gallery/note/template.html +3 -0
  268. package/packs/gallery/note/writing.md +18 -0
  269. package/packs/gallery/pack.json +8 -0
  270. package/packs/gallery/recent/component.json +13 -0
  271. package/packs/gallery/recent/example.html +1 -0
  272. package/packs/gallery/recent/kit.js +238 -0
  273. package/packs/gallery/recent/preview.svg +1 -0
  274. package/packs/gallery/recent/style.css +158 -0
  275. package/packs/gallery/recent/template.html +1 -0
  276. package/packs/gallery/recent/writing.md +15 -0
  277. package/packs/gallery/static/component.json +13 -0
  278. package/packs/gallery/static/example.html +11 -0
  279. package/packs/gallery/static/preview.svg +1 -0
  280. package/packs/gallery/static/style.css +138 -0
  281. package/packs/gallery/static/template.html +1 -0
  282. package/packs/gallery/static/writing.md +18 -0
  283. package/packs/gallery/stats/component.json +13 -0
  284. package/packs/gallery/stats/example.html +1 -0
  285. package/packs/gallery/stats/kit.js +166 -0
  286. package/packs/gallery/stats/preview.svg +1 -0
  287. package/packs/gallery/stats/style.css +151 -0
  288. package/packs/gallery/stats/template.html +1 -0
  289. package/packs/gallery/stats/writing.md +11 -0
  290. package/packs/gallery/today/component.json +12 -0
  291. package/packs/gallery/today/example.html +1 -0
  292. package/packs/gallery/today/kit.js +185 -0
  293. package/packs/gallery/today/preview.svg +1 -0
  294. package/packs/gallery/today/style.css +120 -0
  295. package/packs/gallery/today/template.html +1 -0
  296. package/packs/gallery/today/writing.md +14 -0
  297. package/packs/gallery/todos/component.json +19 -0
  298. package/packs/gallery/todos/example.html +3 -0
  299. package/packs/gallery/todos/kit.js +342 -0
  300. package/packs/gallery/todos/preview.svg +1 -0
  301. package/packs/gallery/todos/style.css +267 -0
  302. package/packs/gallery/todos/template.html +3 -0
  303. package/packs/gallery/todos/writing.md +30 -0
  304. package/packs/gallery/weather/component.json +19 -0
  305. package/packs/gallery/weather/example.html +3 -0
  306. package/packs/gallery/weather/kit.js +312 -0
  307. package/packs/gallery/weather/preview.svg +1 -0
  308. package/packs/gallery/weather/style.css +258 -0
  309. package/packs/gallery/weather/template.html +3 -0
  310. package/packs/gallery/weather/writing.md +26 -0
  311. package/packs/gallery-soft/pack.json +8 -0
  312. package/release.json +12 -10
  313. package/sdk/doc-kit.d.ts +396 -5
  314. package/sdk/doc-state.d.ts +2 -0
  315. package/skill/SKILL.md +56 -23
  316. package/skill/references/block-script.md +140 -0
  317. package/skill/references/building-blocks.md +257 -0
  318. package/skill/references/extensions.md +59 -0
  319. package/skill/references/pages-and-data.md +221 -0
  320. package/skill/references/writing-html.md +6 -4
  321. package/skill/sdk/doc-kit.d.ts +396 -5
  322. package/skill/sdk/doc-state.d.ts +2 -0
  323. package/web/assets/index-BK2e_07w.js +13 -0
  324. package/web/assets/index-CWFI83mF.css +1 -0
  325. package/web/index.html +2 -2
  326. package/dist/chunk-3HIRPVLU.mjs +0 -2
  327. package/dist/chunk-5MHMDLZD.mjs +0 -4
  328. package/dist/chunk-64K7IPLO.mjs +0 -255
  329. package/dist/chunk-6QXGZM36.mjs +0 -8
  330. package/dist/chunk-6RGJOMJP.mjs +0 -16
  331. package/dist/chunk-AJ73XJF3.mjs +0 -2
  332. package/dist/chunk-AUZGSZJH.mjs +0 -13
  333. package/dist/chunk-BFE45AKL.mjs +0 -2
  334. package/dist/chunk-BXO2ZY5P.mjs +0 -2
  335. package/dist/chunk-CEH4MRCC.mjs +0 -2
  336. package/dist/chunk-CY2FNNAB.mjs +0 -16
  337. package/dist/chunk-DFUDV4LX.mjs +0 -2
  338. package/dist/chunk-DKXG4XDG.mjs +0 -2
  339. package/dist/chunk-EZQ34M3F.mjs +0 -124
  340. package/dist/chunk-EZY3WC6Z.mjs +0 -15
  341. package/dist/chunk-FUKQNOQS.mjs +0 -9
  342. package/dist/chunk-JPOSLGOC.mjs +0 -6
  343. package/dist/chunk-L3QKH4OK.mjs +0 -26
  344. package/dist/chunk-LAE5NQ4B.mjs +0 -2
  345. package/dist/chunk-LEF6DRCK.mjs +0 -59
  346. package/dist/chunk-NO5NJCHO.mjs +0 -2
  347. package/dist/chunk-NQPVAU63.mjs +0 -2
  348. package/dist/chunk-NSKUTQ5J.mjs +0 -5
  349. package/dist/chunk-O35FIXRB.mjs +0 -2
  350. package/dist/chunk-P2CAZPDP.mjs +0 -3
  351. package/dist/chunk-PL6GHLGD.mjs +0 -15
  352. package/dist/chunk-QJIR2BLP.mjs +0 -6
  353. package/dist/chunk-QUPLVPB2.mjs +0 -7
  354. package/dist/chunk-R7BNENLY.mjs +0 -4
  355. package/dist/chunk-S6O4UHLC.mjs +0 -2
  356. package/dist/chunk-SGJWQKEI.mjs +0 -2
  357. package/dist/chunk-TX6JH7TR.mjs +0 -2
  358. package/dist/chunk-TYK6VNAB.mjs +0 -10
  359. package/dist/chunk-UALRNVSE.mjs +0 -4
  360. package/dist/chunk-V3HXVQEM.mjs +0 -2
  361. package/dist/chunk-V5VXUJKG.mjs +0 -2
  362. package/dist/chunk-VBXMADMP.mjs +0 -2
  363. package/dist/chunk-VNV55RUA.mjs +0 -2
  364. package/dist/chunk-VPD2PS35.mjs +0 -2
  365. package/dist/chunk-W4VKN3GF.mjs +0 -7
  366. package/dist/chunk-W5BXXPDJ.mjs +0 -3
  367. package/dist/chunk-XUNHRCOG.mjs +0 -6
  368. package/skill/references/building-components.md +0 -367
  369. package/web/assets/index-Bh74DfsV.css +0 -1
  370. package/web/assets/index-UW0RyFFt.js +0 -13
@@ -137,9 +137,16 @@ export const EVENTS = {
137
137
  "doc-theme-request": "a workbench asks for a theme (ui.js listens)",
138
138
  "doc-look-request": "the document's look is asked of the page (字体与布局: the page's session for the workbench, the preview's live reload for doc.json written again; ui.js puts it on <html>, src/shared/document-look.mjs)",
139
139
  "doc-look-change": "the look the page shows changed (ui.js): the page editor's session tells the workbench",
140
- "doc-layout-change": "a card's size changed where widths do not tell (the editor), or the document's look changed (ui.js)",
140
+ "doc-layout-change": "a card's size changed where widths do not tell (the editor, a custom page's canvas), or the document's look changed (ui.js)",
141
+ "doc-workspace-change": "what the workspace holds changed, the kind its detail (the page's live script, as the server's \"data <kind>\" says it): the kit tells the instances that hear data and read that kind",
142
+ "doc-todo-set": "a block's context.workspace.setTodo asks whoever shows the page to check a to-do of a document (cancelable: a custom page's board or a document's editor cancels it and answers from the workspace's service)",
143
+ "doc-open-request": "a block's context.workspace.open asks to open a document or a custom page (cancelable: the page's session for the workbench answers it, else the kit goes there)",
141
144
  "doc-editable": "the page takes what a component writes from now on (the editor)",
142
145
  "doc-component-edit": "a component's context.edit asks to write a part: the editor cancels it to say it took the edit, the kit writing no file (on the instance)",
146
+ "doc-table-intent": "a view's context.table() asks to change the table it reads (set a cell, move, add or take out a row), or whether it would take such a change now (canEdit, op check): the editor cancels it to say it made the change (or would), one undo step, the kit writing nothing (on the instance)",
147
+ "doc-settings-changed": "what gallery.json says of the components changed (the page's event stream, its settings line: hidden, disabled, shown, the consents to the net, unlisted and the packs off): the editor's menus follow it, and the kit starts again an instance whose block's consent changed (runtime/net.js)",
148
+ "doc-net-fetch": "a block's context.fetch asks whoever serves the page to fetch for it (runtime/net.js sends it; the server's live script cancels it and answers from its net route)",
149
+ "doc-net-consent": "a block's 允许访问 asks the reader to let it online (runtime/net.js sends it; the page's session cancels it and the workbench opens its consent dialog)",
143
150
  "doc-export-copy-done": "导出当前视图副本 finished (ui.js)",
144
151
  "doc-table-sort": "a table's sort changed (tables/, on the table)",
145
152
  "doc-jump-request": "演示视图 asks the kit to go to a block of its own page, a step of the history (runtime/reveal.js listens, editor/page/present-links.mjs sends)",
@@ -171,9 +178,11 @@ export const FLOW_BLOCKS = `${FLOWS.join(", ")}, :is(${GROUPING_FLOWS.join(", ")
171
178
  * What the kit's runtime reads of the descriptions (runtime/kernel.js): where each component's
172
179
  * instances are (`match`) and what it leaves on the page (`runtime`), of the components that say
173
180
  * either. /kit/runtime.js carries these alone (assets.mjs, `define`), so an export holds no prose.
174
- * A field the kernel starts reading is added to RUNTIME_FIELDS (tests/unit/runtime.mjs checks it).
181
+ * A field the kernel starts reading is added to RUNTIME_FIELDS (tests/unit/runtime.mjs checks it):
182
+ * `needs`, what a custom block uses (its context.workspace), which only a package describes (no
183
+ * component of the kit has it, so the runtime carries none).
175
184
  */
176
- export const RUNTIME_FIELDS = ["match", "runtime", "schema"];
185
+ export const RUNTIME_FIELDS = ["match", "runtime", "schema", "needs"];
177
186
  export const runtimeDescriptions = (components = COMPONENTS) => components
178
187
  .filter((component) => RUNTIME_FIELDS.some((field) => component[field] !== undefined))
179
188
  .map((component) => Object.fromEntries([["id", component.id], ...RUNTIME_FIELDS.filter((field) => component[field] !== undefined).map((field) => [field, component[field]])]));
@@ -50,6 +50,19 @@ export function heldBy(node) {
50
50
  return owner !== null && edgeAround(edge) === owner ? null : owner;
51
51
  }
52
52
 
53
+ /**
54
+ * The region what an element holds stands in (its children, its words): the instance itself for an
55
+ * instance, the document's (null) inside a slot that opens it again, and otherwise the region the
56
+ * element stands in, as heldBy says it. heldBy for a child the element does not have yet, or has lost:
57
+ * where a change of what it holds happened.
58
+ */
59
+ export function heldWithin(element) {
60
+ if (!element.matches(REGION)) return heldBy(element);
61
+ const owner = heldBy(element);
62
+ if (element.matches(INSTANCE)) return owner ?? element;
63
+ return owner !== null && edgeAround(element) === owner ? null : owner;
64
+ }
65
+
53
66
  /** Whether an element is a slot that opens the document's region again: one of an instance that stands in the document's. */
54
67
  const opensDocument = (node) => node.matches(SLOT_PART) && heldBy(node) !== null && edgeAround(node) === heldBy(node);
55
68
 
@@ -110,8 +123,24 @@ export const PACKAGE_MARK = "data-doc-package";
110
123
  */
111
124
  export const ARRIVING = "data-ge-arriving";
112
125
 
113
- /** What gallery.json's lists make of a package that loads: "disabled", "hidden" or "normal". */
114
- export const settingState = (id, { hidden, disabled }) => (disabled.includes(id) ? "disabled" : hidden.includes(id) ? "hidden" : "normal");
126
+ /**
127
+ * Whether the document's insert entries ("/", 在下方新建, the handle's grid, a table's 视图 ›) offer the
128
+ * block `id` whose manifest's `places` are those (left out: ["document"]), by gallery.json's lists
129
+ * (`settings`: hidden, shown, disabled, unlisted, packsOff, each left out for none): one that says it stands in a
130
+ * document is offered unless hidden; one that does not, only where shown; one disabled never, as it does
131
+ * not load, nor one unlisted (设置 › 组件列表's master switch off: no menu and no library offers it, what
132
+ * is written of it shows as ever), nor one of a pack switched off (packsOff, the ids of the packs off: the
133
+ * scope of its id; the same as unlisted, for a whole pack). The one rule, which the server and the page
134
+ * both run (notes/plans/custom-blocks-spec-2026-11-07.md section 2, block-packs-plan-2026-11-09.md §1).
135
+ */
136
+ export function inDocumentMenus(places, settings, id) {
137
+ const { hidden = [], shown = [], disabled = [], unlisted = [], packsOff = [] } = settings ?? {};
138
+ if (disabled.includes(id) || unlisted.includes(id) || packsOff.includes(String(id).split("/")[0])) return false;
139
+ return (places ?? ["document"]).includes("document") ? !hidden.includes(id) : shown.includes(id);
140
+ }
141
+
142
+ /** What gallery.json's lists make of a package that loads, its manifest's `places` with them: "disabled", "hidden" (no document's menu offers it: inDocumentMenus, which a block unlisted is too) or "normal". */
143
+ export const settingState = (id, settings, places) => (settings.disabled.includes(id) ? "disabled" : inDocumentMenus(places, settings, id) ? "normal" : "hidden");
115
144
 
116
145
  /** The class of the kit's note in such an instance, in a preview alone. */
117
146
  export const NOTE = "doc-component-note";
@@ -223,10 +252,13 @@ export default {
223
252
  "data-doc-component": "the package an instance is of: <scope>/<name>, on a block-level div or section",
224
253
  "data-doc-schema": "the shape an instance holds its content in: a whole number its component chooses, read-only past its manifest's schema",
225
254
  "data-doc-slot": "a slot of an instance: the part of its manifest edited as flow, by name, on a block-level div or section in the instance's own region; what it holds is the document's blocks",
255
+ "data-doc-from": "on an instance of a custom block whose needs hold tables: the key of the table it is a view of, which the table's frame carries as data-doc-chart-source",
226
256
  },
227
257
  runtime: {
228
258
  generated: [".doc-component-note", ".doc-component-data"],
229
259
  attrs: { "[data-doc-fallback]": ["data-doc-fallback"], "[data-doc-fallback-control]": ["data-doc-fallback-control", "inert"] },
230
- internal: [PACKAGE_MARK, "doc-component-more", DATA_ONE, DATA_LINE, DATA_SHORT], // of what the kit drew of a script of data: a line saying what it left out, and its marks
260
+ // Of what the kit drew of a script of data: a line saying what it left out, and its marks; and the mark
261
+ // DocKit.drag (runtime/gestures.js) puts on what a component draws while it is dragged, a class.
262
+ internal: [PACKAGE_MARK, "doc-component-more", DATA_ONE, DATA_LINE, DATA_SHORT, "doc-dragging"],
231
263
  },
232
264
  };
@@ -15,10 +15,12 @@ export function pageLanguage(html) {
15
15
 
16
16
  /**
17
17
  * A document's look on its <html> (字体与布局: its doc.json's look, which the render puts on and the kit's
18
- * tokens read; src/shared/document-look.mjs), the attribute of each choice. The width's is
19
- * data-doc-measure (a column's measure, its line length): data-doc-width is the reader's own 全宽.
18
+ * tokens read; src/shared/document-look.mjs), the attribute of each choice. The 正文宽度's is
19
+ * data-doc-measure (a column's measure, its line length); 全宽 (the top bar's 更多, under 目录) is
20
+ * data-doc-width="full", the one attribute the kit's page, the editor's room and the display menu's
21
+ * 全宽显示 (the reader's own switch in an export) all read: one way to be wide, not two.
20
22
  */
21
- export const LOOK_ATTRIBUTES = Object.freeze({ font: "data-doc-font", size: "data-doc-size", spacing: "data-doc-spacing", width: "data-doc-measure" });
23
+ export const LOOK_ATTRIBUTES = Object.freeze({ font: "data-doc-font", size: "data-doc-size", spacing: "data-doc-spacing", width: "data-doc-measure", full: "data-doc-width" });
22
24
 
23
25
  export default {
24
26
  id: "page",
@@ -37,10 +39,18 @@ export default {
37
39
  classes: ["doc-page", "doc-header", "doc-kicker", "doc-title", "doc-button"],
38
40
  runtime: {
39
41
  generated: [".doc-display", "script[data-doc-state]"],
40
- root: { attrs: ["data-doc-ready", "data-doc-drawn", "data-doc-controls"] },
41
- // The reader's theme and width (ui.js), and the document's look (字体与布局: its doc.json's, put on by
42
- // the render, src/shared/document-look.mjs), which a copy of the view keeps; never written in doc.html.
43
- state: { html: ["data-doc-theme", "data-doc-width", ...Object.values(LOOK_ATTRIBUTES)] },
44
- internal: ["doc-display*", "data-doc-export-copy", "data-theme", "doc-export-id", "doc-reveal", "doc-kit-error"],
42
+ // data-doc-effective-theme: the theme the page shows now (ui.js), "light" or "dark", which a custom page's own colors go by.
43
+ // data-doc-platform: "mac" on an Apple platform (runtime/index.js sets it as the page opens), the text's color and smoothing go by it; a copy of the view lets it go, its reader's machine judges anew.
44
+ root: { attrs: ["data-doc-ready", "data-doc-drawn", "data-doc-controls", "data-doc-effective-theme", "data-doc-platform"] },
45
+ // The reader's theme (ui.js) and the document's look (字体与布局 and 全宽: its doc.json's, put on by
46
+ // the render, src/shared/document-look.mjs; the reader's own 全宽显示 is the same data-doc-width), which
47
+ // a copy of the view keeps; never written in doc.html.
48
+ state: { html: ["data-doc-theme", ...Object.values(LOOK_ATTRIBUTES)] },
49
+ internal: ["doc-display*", "data-doc-export-copy", "data-theme", "doc-export-id", "doc-reveal", "doc-kit-error", "doc-net-consent*", "data-doc-snapshot",
50
+ // The time an export's snapshot was made (its data-doc-snapshot tag's: render/net-snapshot.mjs), which context.workspace.query answers as `at`.
51
+ "data-at",
52
+ // A custom page's (no document's: src/render/page-html.mjs, the kit's board/): its cards' cells, the Agent's
53
+ // (data-x, data-y, data-w, data-h), and its boards' columns now (data-columns, board.js's).
54
+ "data-x", "data-y", "data-w", "data-h", "data-columns"],
45
55
  },
46
56
  };
@@ -2,7 +2,9 @@
2
2
  // a key (data-doc-bind), elements shown for a key's value (data-doc-show),
3
3
  // keys forgotten when the page is left (data-doc-transient); and the kit's styles of such
4
4
  // controls (styles/state.css). A filter (.doc-filter) is one block: its row of buttons and what
5
- // they filter below it (a list, say), together.
5
+ // they filter below it (a list, say), together. A <select> of any control or block, bound or not, opens
6
+ // the kit's menu of choices (runtime/choice.js, styles/choice.css), never the browser's popup: a card at
7
+ // the end of <body> while it is open, nothing an author writes, which an export of the view leaves out.
6
8
  export default {
7
9
  id: "state",
8
10
  match: "[data-doc-bind], [data-doc-show]",
@@ -14,7 +16,9 @@ export default {
14
16
  classes: ["doc-state-fields", "doc-state-field", "doc-state-actions", "doc-filter", "doc-filter-row", "doc-filter-button", "doc-state-list"],
15
17
  wholes: ["doc-filter", "doc-filter-row", "doc-state-fields", "doc-state-list"], // the classes above that are a whole block of their own (the others are parts inside one)
16
18
  runtime: {
19
+ generated: [".doc-choice"],
17
20
  attrs: { "[data-doc-show]": ["hidden"] },
18
21
  state: { "button[data-doc-bind]": ["aria-pressed"] },
22
+ internal: ["doc-choice-*"], // the menu's rows, their words, the check and the group headings
19
23
  },
20
24
  };
@@ -1,13 +1,15 @@
1
- // A document's look (字体与布局, under the workbench's 更多: Notion's page format, notion-lite's
2
- // view/frame/page-format.js): its font, the size of its text, the spacing of its lines and blocks and
3
- // the width of its reading column. The document's own, as Notion keeps it on the page: `look` in its
1
+ // A document's look (字体与布局 and 全宽, under the workbench's 更多: Notion's page format, notion-lite's
2
+ // view/frame/page-format.js, whose page_full_width is a field of the page as ours is of the document):
3
+ // its font, the size of its text, the spacing of its lines and blocks, the width of its reading column
4
+ // and whether it is shown at the full width of the page whatever that is (全宽). The document's own, as
5
+ // Notion keeps it on the page: `look` in its
4
6
  // doc.json (report.json in a document made before Gallery 0.1), over the workspace's default look,
5
7
  // `look` in its gallery.json (which the card's 应用到全部文档 writes, every document's own taken away:
6
8
  // ./workspace-look.mjs), choice by choice (effectiveLook). The workspace's keeps each choice that is
7
9
  // not the default, the field gone when none is; a doc.json each choice that is not the workspace's,
8
10
  // the field gone when none is:
9
11
  //
10
- // "look": { "font": "serif" | "mixed", "size": "small" | "large", "spacing": "compact" | "loose", "width": "narrow" | "wide" }
12
+ // "look": { "font": "serif" | "mixed", "size": "small" | "large", "spacing": "compact" | "loose", "width": "narrow" | "wide", "full": "full" }
11
13
  //
12
14
  // A doc.json may say a default too ("font": "sans", "size": "default"…): 默认 chosen on a page while
13
15
  // the workspace's default says otherwise. The look a document shows is never written in doc.html: the
@@ -26,6 +28,9 @@ export const LOOK = Object.freeze({
26
28
  size: Object.freeze(["default", "small", "large"]), // 字体大小: 小号, 默认, 大号
27
29
  spacing: Object.freeze(["default", "compact", "loose"]), // 正文间距: 紧凑, 默认, 宽松
28
30
  width: Object.freeze(["default", "narrow", "wide"]), // 正文宽度: 偏窄, 默认, 偏宽
31
+ // 全宽 (the 更多 menu's switch under 目录): the page at the full width of its area, whatever its 正文宽度 is; its attribute is
32
+ // the kit's own data-doc-width="full", the reader's display menu's 全宽显示 too (components/page.mjs LOOK_ATTRIBUTES)
33
+ full: Object.freeze(["default", "full"]),
29
34
  });
30
35
 
31
36
  /** The attribute each choice is on <html> by: the page's description says them (components/page.mjs). */
@@ -80,7 +85,7 @@ export function lookOf(value) {
80
85
  return look;
81
86
  }
82
87
 
83
- /** Each choice of a look, the default where it makes none: { font, size, spacing, width }. */
88
+ /** Each choice of a look, the default where it makes none: { font, size, spacing, width, full }. */
84
89
  export const fullLook = (value) => ({ ...Object.fromEntries(Object.entries(LOOK).map(([key, [first]]) => [key, first])), ...lookOf(value) });
85
90
 
86
91
  /** Whether two looks are the same. */
@@ -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);