@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
@@ -2,20 +2,48 @@ import { randomBytes } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { writeAtomic } from "./files.mjs";
5
- import { layoutOf, listDocumentSlugs, pageSlug, slugPattern } from "./workspace.mjs";
6
-
7
- // The workspace's outline: which document sits under which, in what order (the sidebar's tree,
8
- // <workspace>/.gallery/outline.json, docs/workbench.md "Workspace commands"):
9
- // { version: 1, children: { "": [top-level slugs], "<slug>": [slugs under it] } }
10
- // Hierarchy and order only: a document's folder never moves with it, and no file of a document
11
- // names where it is (links between documents are relative links between their files). Read, it
12
- // lists every document of the workspace once but the 写作偏好 page (listed apart, in .gallery/pages/):
13
- // slugs that are gone drop out, documents it does not know yet come last at the top.
14
- // Written by the workspace's server (src/workbench/service.mjs: the workbench's moves, a new, copied,
15
- // restored or imported document, gallery new --under and gallery move while it runs, in the queue its
16
- // saves go through), and by a gallery command when no server runs (src/cli/tree.mjs), with the same
17
- // functions, each change read, made and written under the outline's lock (withOutlineLock: a file
18
- // that two processes cannot both make), so that two writers never lose each other's change.
5
+ import { extensionColumnIds } from "./extensions.mjs";
6
+ import { DOCS, entryPattern, extensionGroupId, GROUP_LIMITS, groupOf, isForeign, isTop, newGroupId, topIn, topOf, validGroups } from "./outline-shape.mjs";
7
+ import { shownWorkspaces, snapshotOf } from "./workspace-columns.mjs";
8
+ import { layoutOf, listDocumentSlugs, listLinkSlugs, pageSlug } from "./workspace.mjs";
9
+
10
+ // The workspace's outline: the sidebar's columns and which entry sits under which, in what order
11
+ // (<workspace>/.gallery/outline.json, docs/workbench.md "Workspace commands"; its shape: ./outline-shape.mjs).
12
+ // The file, version 2:
13
+ // { version: 2, children: { "": [the 文档 column's top], "<slug>": [entries under it] },
14
+ // groups: [{ id: "docs", title? }, { id, title?, role?, workspace?, children: { "": [its top], "<slug>": […] } }] }
15
+ // The 文档 column's tree is the top-level `children`, as in version 1 ({ version: 1, children }, the
16
+ // 文档 column alone, read as such), so that an older Gallery reads the documents' order as before.
17
+ // Hierarchy and order only: an entry's folder never moves with it, and no file of a document names
18
+ // where it is (links between documents are relative links between their files). Read, it lists every
19
+ // document and web link of the workspace once but the 写作偏好 page (listed apart, in .gallery/pages/):
20
+ // entries that are gone drop out, documents it does not know yet come last in the own column (文档's
21
+ // place, top key ""), links last in a column of the links an older Gallery made (else in the own column).
22
+ // Every other registered workspace has a column (./workspace-columns.mjs; the plan §7.2): one the file
23
+ // holds not yet is made as it is read (the default workspace's right after the own column, the others
24
+ // last), and a "whole" column (not `picked`) holds every document of its workspace the outline holds not
25
+ // but those the user took out (its `left`): under its own parent's entry when the outline holds that,
26
+ // else last in the column, the first time laid out as its own sidebar has them. A draft of another
27
+ // workspace ("~<ws id>/<slug>": ./drafts.mjs) stays where the file has it while its document is there;
28
+ // one gone from a workspace the sidebar shows leaves, one of a workspace it does not show (not
29
+ // registered, a clash of ids, its folder away) stays, shown grey (找不到底稿), until it is removed; one of a
30
+ // workspace whose folder did not read this time (an error of the disk: otherWorkspaces' `failed`) stays
31
+ // too, and its column is not filled. A column the sidebar does not draw (the user removed it, `hidden`;
32
+ // its workspace not shown) keeps its workspace's drafts alone: any other entry there (a document of this
33
+ // workspace, a web link, a draft of a third, dragged in) comes last in the own column with what is under it,
34
+ // so that nothing the user put there is out of sight. `others: false` reads the workspace's tree alone (what
35
+ // another workspace's sidebar reads of it, and what the commands of that workspace's own tree read).
36
+ // The columns extensions provide (extensions/<id>/sidebar.json: ./extensions.mjs) are among the groups as
37
+ // { id: "ext-<id>", extension: "<id>" }, each there while its extension has a sidebar.json: one it has not
38
+ // got yet comes last (not written until the outline is), one whose extension is gone drops out, and what an
39
+ // outline says of its entries is passed over (an extension's entries are its own, never the outline's).
40
+ // Their place is what the outline keeps of them.
41
+ // Written by the workspace's server (src/workbench/service.mjs: the workbench's moves and columns, a new,
42
+ // copied, restored or imported document, a new link, gallery new --under, gallery move and gallery link
43
+ // add while it runs, in the queue its saves go through), and by a gallery command when no server runs
44
+ // (src/cli/tree.mjs), with the same functions, each change read, made and written under the outline's
45
+ // lock (withOutlineLock: a file that two processes cannot both make), so that two writers never lose
46
+ // each other's change.
19
47
 
20
48
  /** The outline file's lock (.gallery/outline.lock): who holds it, until when it counts. */
21
49
  const LOCK_STALE = 10_000; // a lock older than this was left by a process that ended: taken over
@@ -53,91 +81,313 @@ export async function withOutlineLock(root, task) {
53
81
 
54
82
  const readJson = (file) => fs.readFile(file, "utf8").then(JSON.parse).catch(() => null);
55
83
 
56
- /** The outline of workspace `root` as it is read: every document but the 写作偏好 page once (above). */
57
- export async function readOutline(root) {
58
- return (await outlineAndFile(root)).outline;
84
+ /** The outline of workspace `root` as it is read: every entry once (above). `others`: with the other workspaces' columns. */
85
+ export async function readOutline(root, { others = true } = {}) {
86
+ return (await outlineAndFile(root, { others })).outline;
59
87
  }
60
88
 
61
89
  /**
62
- * The outline as it is read and what its file holds: { outline, stored: the file's children, or null }.
63
- * A change is written when what it makes is not what the file holds (writtenAs), a document the file
64
- * does not hold yet too (one made since, listed last at the top only as it is read), so that the file
65
- * keeps every place a command or the workbench gave.
90
+ * The other workspaces the sidebar of workspace `root` shows a column of, each with what its own sidebar
91
+ * holds (./workspace-columns.mjs shownWorkspaces, snapshotOf): [{ id, root, isDefault, documents, order,
92
+ * parentOf, facts, failed }]. `failed`: its folder did not read this time (EPERM, EBUSY, EIO: a network or
93
+ * removable drive), none of its documents known, so that the outline drops none of its drafts for it.
66
94
  */
67
- export async function outlineAndFile(root) {
95
+ export async function otherWorkspaces(root, { env = process.env } = {}) {
96
+ const shown = shownWorkspaces(root, { env });
97
+ const readOwn = (other) => readOutline(other, { others: false });
98
+ const unread = () => ({ documents: new Set(), order: [], parentOf: new Map(), facts: new Map(), failed: true });
99
+ return Promise.all(shown.map(async (each) => ({ ...each, failed: false, ...(await snapshotOf(each.root, readOwn).catch(unread)) })));
100
+ }
101
+
102
+ /**
103
+ * The outline as it is read and what its file holds: { outline: { version: 2, groups, children,
104
+ * links: the slugs of its web links, drafts: the entries of other workspaces' drafts, shown: the other
105
+ * workspaces whose columns it shows ([{ id, root, isDefault }]: otherWorkspaces) }, stored: the file's
106
+ * columns and children as the outline has them (storedOf), or null; others: otherWorkspaces' answer }. A
107
+ * change is written when what it makes is not what the file holds (writtenAs), a document or a link the
108
+ * file does not hold yet too (one made since, placed only as it is read), so that the file keeps every
109
+ * place a command or the workbench gave. `others: false`: the workspace's own tree alone (no column of
110
+ * another workspace made, none filled).
111
+ */
112
+ export async function outlineAndFile(root, { others: withOthers = true } = {}) {
68
113
  const instructions = await pageSlug(root);
69
- const slugs = (await listDocumentSlugs(root)).filter((slug) => slug !== instructions);
70
- const stored = await readJson(layoutOf(root).outline);
71
- const outline = { version: 1, children: ordered(stored, slugs) };
72
- return { outline, stored: stored?.children ?? null };
114
+ const documents = (await listDocumentSlugs(root)).filter((slug) => slug !== instructions);
115
+ const links = await listLinkSlugs(root);
116
+ const extensions = await extensionColumnIds(root);
117
+ const stored = storedOf(await readJson(layoutOf(root).outline));
118
+ const others = withOthers ? await otherWorkspaces(root) : [];
119
+ const shown = ordered(stored, documents, links, extensions, others, { drawn: withOthers });
120
+ const outline = {
121
+ version: 2, ...shown, links, drafts: Object.values(shown.children).flat().filter(isForeign),
122
+ shown: others.map(({ id, root: other, isDefault }) => ({ id, root: other, isDefault })),
123
+ };
124
+ return { outline, stored, others };
125
+ }
126
+
127
+ // The own column's name before it was 默认文档 or the workspace's (文档, Documents in English), which a
128
+ // rename of it confirmed unchanged wrote as its title (the field opens with the name shown): read as no
129
+ // title, so that the name it goes by now shows. A title of another column, or any other, is the user's.
130
+ const OLD_DOCS_NAMES = ["文档", "Documents"];
131
+
132
+ /**
133
+ * What outline.json's value `file` holds, as the outline has it: { groups, children } (the 文档 column's
134
+ * tree from the file's own `children`, every other column's from its `children`, its top under its top
135
+ * key), or null for a file that is not there or not an outline. A file of version 1 is the 文档 column alone.
136
+ * The own column titled with its old name (OLD_DOCS_NAMES) has none.
137
+ */
138
+ export function storedOf(file) {
139
+ if (!file || typeof file !== "object" || Array.isArray(file)) return null;
140
+ const groups = [];
141
+ const children = {};
142
+ const take = (id, own) => {
143
+ if (!own || typeof own !== "object" || Array.isArray(own)) return;
144
+ for (const [key, list] of Object.entries(own)) {
145
+ if (!Array.isArray(list)) continue;
146
+ const at = key === "" ? topOf(id) : key;
147
+ if (!children[at]) children[at] = list.filter((entry) => typeof entry === "string");
148
+ }
149
+ };
150
+ for (const value of Array.isArray(file.groups) ? file.groups : []) {
151
+ const group = groupOf(value);
152
+ if (!group || groups.some(({ id }) => id === group.id)) continue;
153
+ if (group.id === DOCS && OLD_DOCS_NAMES.includes(group.title)) delete group.title;
154
+ groups.push(group);
155
+ take(group.id, group.id === DOCS ? file.children : value.children);
156
+ }
157
+ if (!groups.some(({ id }) => id === DOCS)) {
158
+ groups.unshift({ id: DOCS });
159
+ take(DOCS, file.children);
160
+ }
161
+ return { groups, children };
162
+ }
163
+
164
+ /** The keys of `children` whose lists are not empty: what two outlines are compared by. */
165
+ const lists = (children) => Object.keys(children ?? {}).filter((key) => Array.isArray(children[key]) && children[key].length > 0);
166
+
167
+ /** The part of `children` the 文档 column holds: its top and the lists under its entries. */
168
+ function docsPart(children) {
169
+ const part = {};
170
+ const walk = (key) => {
171
+ const list = children?.[key];
172
+ if (!Array.isArray(list) || list.length === 0 || Object.hasOwn(part, key)) return;
173
+ part[key] = list;
174
+ list.forEach(walk);
175
+ };
176
+ walk("");
177
+ return part;
178
+ }
179
+
180
+ /**
181
+ * Whether outline `value` ({ groups?, children }) is what `stored` holds (outlineAndFile's, or an outline
182
+ * read): the same columns in the same order, and the same lists (an empty one as none), in whatever order
183
+ * the keys come. One without `groups` (an outline of version 1: a test's, an older workbench's) is held
184
+ * to the 文档 column alone. Also whether an outline a workbench changed (POST outline's `base`) is the one read now.
185
+ */
186
+ export function writtenAs(value, stored) {
187
+ if (!stored || typeof stored !== "object" || !value || typeof value !== "object") return false;
188
+ const ours = value.children ?? {};
189
+ const theirs = value.groups ? stored.children : docsPart(stored.children);
190
+ const [a, b] = [lists(ours), lists(theirs)];
191
+ if (a.length !== b.length || !a.every((key) => JSON.stringify(ours[key]) === JSON.stringify(theirs[key]))) return false;
192
+ if (!value.groups) return true;
193
+ const same = (groups) => JSON.stringify((groups ?? []).map(groupOf));
194
+ return same(value.groups) === same(stored.groups);
195
+ }
196
+
197
+ /**
198
+ * `groups` with the columns of the extensions `ids` (those with a sidebar.json): the extension columns the
199
+ * outline kept stay where it put them, once each and only while their extension is there; an extension
200
+ * with none comes last, while there is room (50 columns at most: validGroups, or no change of the outline
201
+ * would be written).
202
+ */
203
+ function withExtensions(groups, ids) {
204
+ const wanted = new Set(ids);
205
+ const seen = new Set();
206
+ const kept = groups.filter((group) => {
207
+ if (!group.extension) return true;
208
+ if (!wanted.has(group.extension) || seen.has(group.extension)) return false;
209
+ seen.add(group.extension);
210
+ return true;
211
+ });
212
+ const coming = ids.filter((id) => !seen.has(id)).map((id) => ({ id: extensionGroupId(id), extension: id }));
213
+ return [...kept, ...coming.slice(0, Math.max(0, GROUP_LIMITS.groups - kept.length))];
73
214
  }
74
215
 
75
216
  /**
76
- * Whether `children` is what the outline's file holds (`stored`, outlineAndFile's): the same lists
77
- * (an empty one as none), in whatever order the file has its keys. Also whether an outline a
78
- * workbench changed (POST outline's `base`) is the one read now.
217
+ * `groups` with a column for each of the workspaces `others` (otherWorkspaces') that none has yet (hidden or
218
+ * not): a whole one (no `picked`), its id the workspace's when no column has that, the default workspace's
219
+ * right after the own column, the others last, in the registry's order; `limit` columns at most (the room
220
+ * the extensions' columns to come leave of the 50).
79
221
  */
80
- export function writtenAs(children, stored) {
81
- if (!stored || typeof stored !== "object") return false;
82
- const lists = (value) => Object.keys(value).filter((key) => Array.isArray(value[key]) && value[key].length > 0);
83
- const [ours, theirs] = [lists(children), lists(stored)];
84
- return ours.length === theirs.length && ours.every((key) => JSON.stringify(children[key]) === JSON.stringify(stored[key]));
222
+ export function withWorkspaces(groups, others, limit = GROUP_LIMITS.groups) {
223
+ const result = [...groups];
224
+ const lacking = others.filter(({ id }) => !result.some((group) => group.workspace === id));
225
+ for (const { id, isDefault } of lacking) {
226
+ if (result.length >= limit) break; // 50 columns at most (validGroups)
227
+ const column = { id: result.some((group) => group.id === id) ? newGroupId(result) : id, workspace: id };
228
+ if (isDefault) result.splice(result.findIndex((group) => group.id === DOCS) + 1, 0, column);
229
+ else result.push(column);
230
+ }
231
+ return result;
85
232
  }
86
233
 
87
- // The outline's children of the documents `slugs`, in the order `stored` (the file's value) gives.
88
- function ordered(stored, slugs) {
89
- const children = { "": [] };
234
+ const DRAFT = /^~(ws-[0-9a-f]{6})\/(.+)$/;
235
+
236
+ /**
237
+ * The outline of the entries `documents` and `links` (the web links), in the order `stored` (storedOf's)
238
+ * gives, with the columns of the other workspaces `others` (otherWorkspaces'): { groups, children }, every
239
+ * column's top key there (an empty list for an empty column). Each entry once, under the first place it
240
+ * has; one of this workspace's that is gone is left out, a draft of another workspace ("~<ws id>/<slug>")
241
+ * left out when its workspace is among `others` and holds it no more (its folder read), else kept wherever
242
+ * the file has it. A document no column holds comes last in the own column; a link, last in a column of the
243
+ * links (role "links", an older Gallery's), else last in the own column; a document of another workspace
244
+ * whose column is whole, under its own parent's entry when that is held, else last in that column, in its
245
+ * own sidebar's order, but one the user took out of it (the column's `left`, kept to those still there).
246
+ * `drawn` (the sidebar's read, not a workspace's own tree alone): a workspace's column the sidebar does not
247
+ * draw (`hidden`, or its workspace not among `others`) keeps that workspace's drafts alone, any other entry
248
+ * there coming last in the own column with what is under it.
249
+ */
250
+ function ordered(stored, documents, links, extensions = [], others = [], { drawn = false } = {}) {
251
+ const listed = new Set([...documents, ...links]);
252
+ const shownById = new Map(others.map((each) => [each.id, each]));
253
+ const known = {
254
+ has: (slug) => {
255
+ if (listed.has(slug)) return true;
256
+ if (!isForeign(slug) || !entryPattern.test(slug)) return false;
257
+ const [, ws, own] = DRAFT.exec(slug);
258
+ const other = shownById.get(ws);
259
+ return other && !other.failed ? other.documents.has(own) : true;
260
+ },
261
+ };
262
+ // A whole column's `left`: those its workspace still has (its folder read), none when none is.
263
+ const kept = (group) => {
264
+ const other = group.left ? shownById.get(group.workspace) : null;
265
+ if (!other || other.failed) return group;
266
+ const { left, ...rest } = group;
267
+ const still = left.filter((slug) => other.documents.has(slug));
268
+ return still.length ? { ...rest, left: still } : rest;
269
+ };
270
+ const base = stored?.groups ?? [{ id: DOCS }];
271
+ // The room of the 50 columns an extension's column to come takes first (one the outline has not got yet).
272
+ const coming = extensions.filter((id) => !base.some((group) => group.extension === id)).length;
273
+ const groups = withExtensions(withWorkspaces(base, others, GROUP_LIMITS.groups - coming), extensions).map(kept);
274
+ const children = Object.fromEntries(groups.map(({ id }) => [topOf(id), []]));
90
275
  const seen = new Set();
91
- const place = (parent) => {
276
+ // Drawn not: the user removed it, or its workspace is not shown (forgotten, its folder away, a clash of ids).
277
+ const undrawn = (group) => drawn && Boolean(group.workspace) && (group.hidden === true || !shownById.has(group.workspace));
278
+ const astray = []; // entries of a column not drawn that are not its workspace's drafts: last in the own column
279
+ const place = (parent, only = null) => {
92
280
  for (const slug of stored?.children?.[parent] ?? []) {
93
- if (!slugs.includes(slug) || seen.has(slug)) continue;
281
+ if (!known.has(slug) || seen.has(slug)) continue;
282
+ if (only && !slug.startsWith(`~${only}/`)) {
283
+ astray.push(slug);
284
+ continue;
285
+ }
94
286
  seen.add(slug);
95
287
  (children[parent] ??= []).push(slug);
96
- place(slug);
288
+ place(slug, only);
97
289
  }
98
290
  };
99
- place("");
100
- for (const slug of slugs) if (!seen.has(slug)) children[""].push(slug);
101
- return children;
291
+ for (const group of groups) if (!group.extension) place(topOf(group.id), undrawn(group) ? group.workspace : null);
292
+ for (const slug of astray) {
293
+ if (seen.has(slug)) continue;
294
+ seen.add(slug);
295
+ children[""].push(slug);
296
+ place(slug);
297
+ }
298
+ const linked = groups.find(({ role }) => role === "links");
299
+ for (const slug of documents) if (!seen.has(slug)) children[""].push(slug);
300
+ for (const slug of links) if (!seen.has(slug)) children[linked ? topOf(linked.id) : ""].push(slug);
301
+ for (const other of others) {
302
+ const column = groups.find((group) => group.workspace === other.id);
303
+ if (!column || column.picked || other.failed) continue;
304
+ const left = new Set(column.left ?? []);
305
+ for (const slug of other.order) {
306
+ const entry = `~${other.id}/${slug}`;
307
+ if (seen.has(entry) || left.has(slug)) continue;
308
+ const parent = other.parentOf.get(slug);
309
+ const above = parent ? `~${other.id}/${parent}` : null;
310
+ (children[above !== null && seen.has(above) ? above : topOf(column.id)] ??= []).push(entry);
311
+ seen.add(entry);
312
+ }
313
+ }
314
+ return { groups, children };
102
315
  }
103
316
 
104
- /** Whether `value` is an outline to store: each document at most once, never under itself. */
317
+ /**
318
+ * Whether `value` ({ groups?, children }) is an outline to store: its columns valid (validGroups: 文档
319
+ * among them), each list's key a column's top or an entry, each entry at most once, never under itself.
320
+ * Without `groups` (an outline of version 1) the only top is 文档's ("").
321
+ */
105
322
  export function validOutline(value) {
106
323
  const children = value?.children;
107
324
  if (!children || typeof children !== "object" || Array.isArray(children)) return false;
325
+ if (value.groups !== undefined && !validGroups(value.groups)) return false;
326
+ const tops = new Set((value.groups ?? [{ id: DOCS }]).map(({ id }) => topOf(id)));
327
+ // An extension's column holds none of the outline's entries: they are the extension's own.
328
+ const extensionTops = new Set((value.groups ?? []).filter((group) => groupOf(group)?.extension).map(({ id }) => topOf(id)));
108
329
  const listed = new Set();
109
330
  for (const [parent, list] of Object.entries(children)) {
110
- if (parent !== "" && !slugPattern.test(parent)) return false;
111
- if (!Array.isArray(list)) return false;
331
+ if (!tops.has(parent) && !entryPattern.test(parent)) return false;
332
+ if (!Array.isArray(list) || (extensionTops.has(parent) && list.length > 0)) return false;
112
333
  for (const slug of list) {
113
- if (typeof slug !== "string" || !slugPattern.test(slug) || listed.has(slug) || slug === parent) return false;
334
+ if (typeof slug !== "string" || !entryPattern.test(slug) || listed.has(slug) || slug === parent) return false;
114
335
  listed.add(slug);
115
336
  }
116
337
  }
117
338
  return true;
118
339
  }
119
340
 
120
- /** Write outline `value` ({ children }) of workspace `root` (.gallery/ made when there is none yet). */
341
+ /**
342
+ * Outline `value` ({ groups?, children }) as outline.json holds it, version 2: the 文档 column's tree in
343
+ * `children`, where version 1 had the whole tree (an older Gallery reads its documents' order as before:
344
+ * it sees no column, and writes version 1 back, which this one reads as 文档 alone), and every column in
345
+ * `groups`, in the sidebar's order, the others each with its own `children` ("" its top).
346
+ */
347
+ export function fileOf(value) {
348
+ const groups = value.groups ?? [{ id: DOCS }];
349
+ const own = (top) => {
350
+ const tree = { "": [] };
351
+ const walk = (key, as) => {
352
+ const list = value.children[key];
353
+ if (!Array.isArray(list) || list.length === 0) return;
354
+ tree[as] = [...list];
355
+ for (const entry of list) if (!Object.hasOwn(tree, entry)) walk(entry, entry);
356
+ };
357
+ walk(top, "");
358
+ return tree;
359
+ };
360
+ return {
361
+ version: 2,
362
+ children: own(""),
363
+ groups: groups.map((group) => {
364
+ const kept = groupOf(group);
365
+ return kept.id === DOCS || kept.extension ? kept : { ...kept, children: own(topOf(kept.id)) };
366
+ }),
367
+ };
368
+ }
369
+
370
+ /** Write outline `value` ({ groups?, children }) of workspace `root` (.gallery/ made when there is none yet). */
121
371
  export async function writeOutline(root, value) {
122
372
  const file = layoutOf(root).outline;
123
373
  await fs.mkdir(path.dirname(file), { recursive: true });
124
- return writeAtomic(file, `${JSON.stringify({ version: 1, children: value.children }, null, 2)}\n`);
374
+ return writeAtomic(file, `${JSON.stringify(fileOf(value), null, 2)}\n`);
125
375
  }
126
376
 
127
- /** The document `slug` sits under in `children` ("" at the top), or null when the outline holds it not. */
377
+ /** The key `slug` sits under in `children` (a column's top key at a top level), or null when the outline holds it not. */
128
378
  export function parentIn(children, slug) {
129
379
  for (const [parent, list] of Object.entries(children)) if (list.includes(slug)) return parent;
130
380
  return null;
131
381
  }
132
382
 
133
- /** The documents above `slug` in `children`, from the top down (itself not among them). */
383
+ /** The entries above `slug` in `children`, from the top down (itself and its column not among them). */
134
384
  export function ancestorsIn(children, slug) {
135
385
  const above = [];
136
- for (let parent = parentIn(children, slug); parent; parent = parentIn(children, parent)) above.unshift(parent);
386
+ for (let parent = parentIn(children, slug); parent !== null && !isTop(parent) && !above.includes(parent); parent = parentIn(children, parent)) above.unshift(parent);
137
387
  return above;
138
388
  }
139
389
 
140
- /** Every document under `slug` in `children` ("": all), depth first, in the sidebar's order. */
390
+ /** Every entry under `slug` in `children` (a top key: the column's), depth first, in the sidebar's order. */
141
391
  export function descendantsIn(children, slug) {
142
392
  const found = [];
143
393
  const walk = (parent) => (children[parent] ?? []).forEach((child) => { found.push(child); walk(child); });
@@ -146,18 +396,18 @@ export function descendantsIn(children, slug) {
146
396
  }
147
397
 
148
398
  /**
149
- * Document `slug` moved in `children` (an outline as read: changed in place) with the documents under
150
- * it, to where `where` says: `parent` ("" the top; undefined: where it is now, or `after`'s parent),
151
- * right after `after` (one of that parent's), first (`first`), else last. Resolves to { ok: true,
152
- * moved: whether its place changed, parent, index, from: { parent, index } } or { ok: false, reason }:
153
- * "missing" (no such document in the outline), "parent" (no such parent), "after" (no such document
154
- * to go after), "level" (`after` is not under `parent`), "itself" (after itself), "inside" (under
155
- * itself or one of the documents under it).
399
+ * Entry `slug` moved in `children` (an outline as read: changed in place) with the entries under it, to
400
+ * where `where` says: `parent` ("" the top level of the column it is in; a column's top key, that
401
+ * column's; undefined: where it is now, or `after`'s parent), right after `after` (one of that parent's),
402
+ * first (`first`), else last. Resolves to { ok: true, moved: whether its place changed, parent, index,
403
+ * from: { parent, index } } or { ok: false, reason }: "missing" (no such entry in the outline), "parent"
404
+ * (no such parent), "after" (no such entry to go after), "level" (`after` is not under `parent`),
405
+ * "itself" (after itself), "inside" (under itself or one of the entries under it).
156
406
  */
157
407
  export function moveIn(children, slug, { parent = undefined, after = null, first = false } = {}) {
158
408
  const from = parentIn(children, slug);
159
409
  if (from === null) return { ok: false, reason: "missing" };
160
- let to = parent;
410
+ let to = parent === "" ? topIn(children, slug) ?? "" : parent;
161
411
  if (after !== null) {
162
412
  if (after === slug) return { ok: false, reason: "itself" };
163
413
  const its = parentIn(children, after);
@@ -166,29 +416,43 @@ export function moveIn(children, slug, { parent = undefined, after = null, first
166
416
  to = its;
167
417
  }
168
418
  to ??= from;
169
- if (to !== "" && parentIn(children, to) === null) return { ok: false, reason: "parent" };
419
+ if (isTop(to) ? !Object.hasOwn(children, to) : parentIn(children, to) === null) return { ok: false, reason: "parent" };
170
420
  if (to === slug || ancestorsIn(children, to).includes(slug)) return { ok: false, reason: "inside" };
171
421
  const index = children[from].indexOf(slug);
172
422
  children[from].splice(index, 1);
173
423
  const list = (children[to] ??= []);
174
424
  const at = after !== null ? list.indexOf(after) + 1 : first ? 0 : list.length;
175
425
  list.splice(at, 0, slug);
176
- for (const key of Object.keys(children)) if (key !== "" && children[key].length === 0) delete children[key];
426
+ for (const key of Object.keys(children)) if (!isTop(key) && children[key].length === 0) delete children[key];
177
427
  return { ok: true, moved: to !== from || at !== index, parent: to, index: at, from: { parent: from, index } };
178
428
  }
179
429
 
180
430
  /**
181
- * Move document `slug` of workspace `root` where `where` says (moveIn), under the outline's lock: the
182
- * outline read, changed and written when that is not what its file holds (writtenAs: a move, or a
183
- * document the file does not hold yet placed where it is read). Resolves to moveIn's answer, with
431
+ * Move entry `slug` of workspace `root` where `where` says (moveIn), under the outline's lock: the
432
+ * outline read, changed and written when that is not what its file holds (writtenAs: a move, or an
433
+ * entry the file does not hold yet placed where it is read). Resolves to moveIn's answer, with
184
434
  * `written`: whether the file was written.
185
435
  */
186
436
  export function moveDocument(root, slug, where) {
187
437
  return withOutlineLock(root, async () => {
188
438
  const { outline, stored } = await outlineAndFile(root);
189
439
  const result = moveIn(outline.children, slug, where);
190
- const written = result.ok && !writtenAs(outline.children, stored);
440
+ const written = result.ok && !writtenAs(outline, stored);
191
441
  if (written) await writeOutline(root, outline);
192
442
  return { ...result, ...(result.ok ? { written } : {}) };
193
443
  });
194
444
  }
445
+
446
+ /**
447
+ * Entry `slug` put in `outline` (changed in place: on disk already, so that the outline as read lists
448
+ * it somewhere) under `parent` (a column's top key or an entry; one the outline holds not: the 文档
449
+ * column's top) at `index` (the end when past it).
450
+ */
451
+ export function placeIn(outline, slug, parent = "", index = Infinity) {
452
+ const { children } = outline;
453
+ for (const list of Object.values(children)) if (list.includes(slug)) list.splice(list.indexOf(slug), 1);
454
+ const known = parent !== slug && (isTop(parent) ? Object.hasOwn(children, parent) : Object.values(children).some((list) => list.includes(parent)));
455
+ const list = (children[known ? parent : ""] ??= []);
456
+ list.splice(Math.max(0, Math.min(index, list.length)), 0, slug);
457
+ for (const key of Object.keys(children)) if (!isTop(key) && children[key].length === 0) delete children[key];
458
+ }
@@ -0,0 +1,77 @@
1
+ import { refusal, said } from "./i18n/index.mjs";
2
+ import { DEVICE_NAMES } from "./workspace.mjs";
3
+
4
+ // A block pack's manifest (自定义块包: notes/plans/block-packs-plan-2026-11-09.md §1; docs/render.md "Block
5
+ // packs"): pack.json at the top of a pack, a folder of components/ whose blocks are its scope's
6
+ // (components/<id>/<name>/, the block's id <id>/<name>), what a pack says of itself to whoever lists it
7
+ // (设置 › 组件列表's pack cards, gallery block pack list) and to whoever installs it (./block-packs.mjs).
8
+ // Plain data, as a block's manifest is: a field it does not know keeps it from reading, so that a pack
9
+ // made for a later Gallery says so rather than half reads.
10
+ //
11
+ // id the scope its blocks are of: lowercase letters, digits and hyphens (my-cards); not
12
+ // local, the workspace's own blocks' scope, nor a name Windows keeps for a device
13
+ // name what a reader calls it: words, or { zh, en } in both languages
14
+ // description what it is, the same way (optional)
15
+ // version x.y.z
16
+ // author who made it, words (optional)
17
+ // homepage where it comes from, an http(s) address (optional)
18
+ // icon an emoji or a character or two its card shows (optional; a card has the custom block's mark)
19
+
20
+ /** The file that makes a folder of components/ a pack. */
21
+ export const PACK_FILE = "pack.json";
22
+
23
+ /** What a pack's id, a scope, may be: as a block id's scope (shared/components/installed.mjs PACKAGE_ID). */
24
+ export const PACK_ID = /^[a-z0-9][a-z0-9-]*$/;
25
+
26
+ /** The scope a block of the workspace's own is in (gallery block new local/<name>, or a <name> alone), which no pack takes. */
27
+ export const OWN_SCOPE = "local";
28
+
29
+ const FIELDS = ["id", "name", "description", "version", "author", "homepage", "icon"];
30
+ const VERSION = /^\d+\.\d+\.\d+$/;
31
+ const isTable = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
32
+ const isWords = (value) => typeof value === "string" && value.trim() !== "";
33
+ const fail = (message) => {
34
+ throw refusal(message, { code: "manifest" });
35
+ };
36
+
37
+ // Words a reader reads, in both languages: one string for both, or { zh, en }.
38
+ function bothOf(value, field) {
39
+ if (isWords(value) && value.length <= 300) return { zh: value.trim(), en: value.trim() };
40
+ if (isTable(value) && Object.keys(value).length === 2 && isWords(value.zh) && isWords(value.en) && value.zh.length <= 300 && value.en.length <= 300) return { zh: value.zh.trim(), en: value.en.trim() };
41
+ fail(said("pack.json 的 {0} 要写成一段文字,或 { zh, en } 两种语言的文字", field));
42
+ }
43
+
44
+ /**
45
+ * pack.json's text read as a pack's manifest: { id, name: { zh, en }, description: { zh, en } | null,
46
+ * version, author, homepage, icon (each null where it says none) }. One that does not read as one is
47
+ * refused (code "manifest") with why, said()'s.
48
+ */
49
+ export function readPackManifest(text) {
50
+ let value;
51
+ try {
52
+ value = JSON.parse(String(text).replace(/^/, ""));
53
+ } catch {
54
+ fail(said("pack.json 不是 JSON"));
55
+ }
56
+ if (!isTable(value)) fail(said("pack.json 要写成一张表"));
57
+ for (const key of Object.keys(value)) if (!FIELDS.includes(key)) fail(said("{0} 不是 pack.json 能写的字段", key));
58
+ const { id, name, description, version, author, homepage, icon } = value;
59
+ if (id === undefined) fail(said("pack.json 缺少 {0}", "id"));
60
+ if (typeof id !== "string" || !PACK_ID.test(id) || DEVICE_NAMES.has(id)) fail(said("pack.json 的 id 只用小写字母、数字和连字符,如 my-cards"));
61
+ if (id === OWN_SCOPE) fail(said("{0} 留给工作区自己的块,块包换一个 id", OWN_SCOPE));
62
+ if (name === undefined) fail(said("pack.json 缺少 {0}", "name"));
63
+ if (version === undefined) fail(said("pack.json 缺少 {0}", "version"));
64
+ if (typeof version !== "string" || !VERSION.test(version)) fail(said("pack.json 的 version 要写成 1.0.0 这样的三段数字"));
65
+ if (author !== undefined && !(isWords(author) && author.length <= 100)) fail(said("pack.json 的 {0} 要写成一段文字", "author"));
66
+ if (homepage !== undefined && !(typeof homepage === "string" && /^https?:\/\/\S+$/.test(homepage) && homepage.length <= 500)) fail(said("pack.json 的 homepage 要写成 http 或 https 开头的地址"));
67
+ if (icon !== undefined && !(isWords(icon) && [...icon].length <= 8 && !/[<>&"]/.test(icon))) fail(said("pack.json 的 icon 要写成一个表情或一两个字"));
68
+ return {
69
+ id,
70
+ name: bothOf(name, "name"),
71
+ description: description === undefined ? null : bothOf(description, "description"),
72
+ version,
73
+ author: author?.trim() ?? null,
74
+ homepage: homepage ?? null,
75
+ icon: icon?.trim() ?? null,
76
+ };
77
+ }
@@ -1,4 +1,4 @@
1
- import { pageLink, pageOfLink } from "./page-links.mjs";
1
+ import { earlierLink, pageLink, pageOfLink } from "./page-links.mjs";
2
2
 
3
3
  // A document's address, as a link, a paste or the address bar may write it (docs/workbench.md, "A
4
4
  // document's addresses"), read in one place: { slug, block }, block null for the page itself. Pure: the
@@ -19,7 +19,8 @@ import { pageLink, pageOfLink } from "./page-links.mjs";
19
19
  // which has the list. pageAddress reads no port, as a link outlives a server; but this computer runs
20
20
  // other programs too, and other workspaces, whose folder of one name has one page id, so an address
21
21
  // on another port (elsewhere) is a document's only if the list holds it, by its folder name or its
22
- // link of now (addressOn).
22
+ // link of now (addressOn), or for a folder named doc- and eight characters, the link it had before
23
+ // those eight were its id (shared/page-links.mjs earlierId).
23
24
 
24
25
  const SLUG = "[a-z0-9]+(?:-[a-z0-9]+)*"; // shared/workspace.mjs slugPattern, which needs Node
25
26
  const FILE = "(?:doc|report)\\.html";
@@ -96,7 +97,9 @@ export function workbenchHash(text) {
96
97
  export function addressOn(text, links, origin = "http://127.0.0.1") {
97
98
  const place = pageAddress(text, links);
98
99
  const name = place && elsewhere(text, origin) ? workbenchHash(text)?.split("/")[0] : undefined;
99
- return name === undefined || links.has(name) || links.get(place.slug) === name ? place : null;
100
+ if (name === undefined || links.has(name)) return place;
101
+ const link = links.get(place.slug);
102
+ return link === name || earlierLink(place.slug, link) === name ? place : null;
100
103
  }
101
104
 
102
105
  /** Whether `text` is an address of a document of this workspace, as far as can be told with no list. */