@heycys/gallery 0.1.0 → 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 (465) hide show
  1. package/THIRD-PARTY-NOTICES.md +51 -1
  2. package/browser/editor/components/callout/index.mjs +40 -15
  3. package/browser/editor/components/columns/page.mjs +45 -32
  4. package/browser/editor/components/details/index.mjs +2 -1
  5. package/browser/editor/components/dialog/index.mjs +2 -1
  6. package/browser/editor/components/dialog/page.mjs +0 -18
  7. package/browser/editor/components/divider/index.mjs +1 -1
  8. package/browser/editor/components/embed/index.mjs +2 -2
  9. package/browser/editor/components/figure/page.mjs +10 -1
  10. package/browser/editor/components/file/page.mjs +2 -2
  11. package/browser/editor/components/flowchart/add.mjs +7 -6
  12. package/browser/editor/components/flowchart/page.mjs +1 -1
  13. package/browser/editor/components/heading/index.mjs +2 -0
  14. package/browser/editor/components/heading/page.mjs +6 -0
  15. package/browser/editor/components/highlight/index.mjs +4 -20
  16. package/browser/editor/components/icons.mjs +13 -2
  17. package/browser/editor/components/image/crop.mjs +6 -9
  18. package/browser/editor/components/image/page.mjs +2 -2
  19. package/browser/editor/components/installed/index.mjs +82 -29
  20. package/browser/editor/components/installed/page.mjs +146 -34
  21. package/browser/editor/components/installed/served.mjs +6 -5
  22. package/browser/editor/components/instances.mjs +18 -0
  23. package/browser/editor/components/kit.mjs +3 -0
  24. package/browser/editor/components/list/page.mjs +3 -6
  25. package/browser/editor/components/media/page.mjs +44 -14
  26. package/browser/editor/components/pages.mjs +2 -2
  27. package/browser/editor/components/registry.mjs +68 -33
  28. package/browser/editor/components/section/index.mjs +2 -1
  29. package/browser/editor/components/set-apart.mjs +24 -0
  30. package/browser/editor/components/table/index.mjs +64 -5
  31. package/browser/editor/components/table/page.mjs +30 -19
  32. package/browser/editor/page/annotate/anchors.mjs +4 -3
  33. package/browser/editor/page/annotate/card.mjs +3 -6
  34. package/browser/editor/page/annotate/index.mjs +61 -34
  35. package/browser/editor/page/autosave.mjs +357 -38
  36. package/browser/editor/page/beside.mjs +5 -5
  37. package/browser/editor/page/block-menu/colors.mjs +3 -1
  38. package/browser/editor/page/block-menu/index.mjs +8 -6
  39. package/browser/editor/page/blocks.mjs +16 -7
  40. package/browser/editor/page/board/client.mjs +57 -0
  41. package/browser/editor/page/board/hint.mjs +145 -0
  42. package/browser/editor/page/board/history.mjs +119 -0
  43. package/browser/editor/page/board/index.mjs +970 -0
  44. package/browser/editor/page/board/settings.mjs +209 -0
  45. package/browser/editor/page/caret.mjs +5 -2
  46. package/browser/editor/page/changes/bar.mjs +94 -0
  47. package/browser/editor/page/changes/ghost.mjs +206 -0
  48. package/browser/editor/page/changes/gone-words.mjs +94 -0
  49. package/browser/editor/page/changes/index.mjs +447 -0
  50. package/browser/editor/page/changes/words.mjs +27 -0
  51. package/browser/editor/page/clipboard/import/colors.mjs +3 -3
  52. package/browser/editor/page/clipboard/import/html.mjs +29 -6
  53. package/browser/editor/page/clipboard/import/inline.mjs +32 -0
  54. package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
  55. package/browser/editor/page/clipboard/import/read.mjs +6 -1
  56. package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
  57. package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
  58. package/browser/editor/page/clipboard/index.mjs +156 -26
  59. package/browser/editor/page/clipboard/own/blocks.mjs +25 -16
  60. package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
  61. package/browser/editor/page/clipboard/own/describe.mjs +16 -7
  62. package/browser/editor/page/clipboard/own/export.mjs +6 -6
  63. package/browser/editor/page/clipboard/own/selection.mjs +3 -3
  64. package/browser/editor/page/clipboard/own/writers.mjs +0 -11
  65. package/browser/editor/page/columns/index.mjs +48 -46
  66. package/browser/editor/page/commit.mjs +98 -25
  67. package/browser/editor/page/conflicts.mjs +13 -33
  68. package/browser/editor/page/convert.mjs +8 -7
  69. package/browser/editor/page/core.mjs +81 -27
  70. package/browser/editor/page/custom-page.mjs +106 -0
  71. package/browser/editor/page/drag/index.mjs +40 -24
  72. package/browser/editor/page/drag/preview.mjs +9 -4
  73. package/browser/editor/page/editor.mjs +120 -28
  74. package/browser/editor/page/emoji-picker/emoji-tab.mjs +22 -13
  75. package/browser/editor/page/emoji-picker/grid.mjs +7 -5
  76. package/browser/editor/page/emoji-picker/icon-tab.mjs +4 -4
  77. package/browser/editor/page/emoji-picker/icons.mjs +0 -1
  78. package/browser/editor/page/emoji-picker/index.mjs +11 -9
  79. package/browser/editor/page/emoji-picker/styles.mjs +156 -2
  80. package/browser/editor/page/emoji-picker/support.mjs +6 -4
  81. package/browser/editor/page/emoji-picker/upload.mjs +14 -13
  82. package/browser/editor/page/fills.mjs +5 -3
  83. package/browser/editor/page/fold/index.mjs +348 -0
  84. package/browser/editor/page/fold/rule.mjs +125 -0
  85. package/browser/editor/page/folded.mjs +23 -0
  86. package/browser/editor/page/format-bar/bar.mjs +2 -1
  87. package/browser/editor/page/format-bar/index.mjs +1 -1
  88. package/browser/editor/page/geometry.mjs +8 -6
  89. package/browser/editor/page/handle/index.mjs +22 -14
  90. package/browser/editor/page/hints/index.mjs +1 -1
  91. package/browser/editor/page/history.mjs +24 -4
  92. package/browser/editor/page/insert/index.mjs +30 -17
  93. package/browser/editor/page/links/index.mjs +5 -3
  94. package/browser/editor/page/links/pages.mjs +27 -9
  95. package/browser/editor/page/long-runs.mjs +75 -0
  96. package/browser/editor/page/look.mjs +69 -6
  97. package/browser/editor/page/margin.mjs +63 -27
  98. package/browser/editor/page/media-menu/index.mjs +6 -7
  99. package/browser/editor/page/menu.mjs +86 -31
  100. package/browser/editor/page/merge.mjs +24 -0
  101. package/browser/editor/page/notices.mjs +3 -3
  102. package/browser/editor/page/offered.mjs +1 -1
  103. package/browser/editor/page/outline/folds.mjs +11 -10
  104. package/browser/editor/page/outline/index.mjs +131 -45
  105. package/browser/editor/page/outline/list.mjs +23 -11
  106. package/browser/editor/page/outline/read.mjs +1 -1
  107. package/browser/editor/page/outline/strip.mjs +7 -1
  108. package/browser/editor/page/page-objects.mjs +13 -2
  109. package/browser/editor/page/panel.mjs +9 -13
  110. package/browser/editor/page/present-links.mjs +11 -2
  111. package/browser/editor/page/resize/index.mjs +42 -29
  112. package/browser/editor/page/selection/delete.mjs +8 -6
  113. package/browser/editor/page/selection/index.mjs +10 -3
  114. package/browser/editor/page/served.mjs +14 -0
  115. package/browser/editor/page/session.mjs +101 -6
  116. package/browser/editor/page/slash-menu/index.mjs +3 -3
  117. package/browser/editor/page/table/cell-bar.mjs +4 -1
  118. package/browser/editor/page/table/cells.mjs +103 -47
  119. package/browser/editor/page/table/icons.mjs +3 -1
  120. package/browser/editor/page/table/index.mjs +40 -16
  121. package/browser/editor/page/table/pictures.mjs +299 -0
  122. package/browser/editor/page/table/views.mjs +150 -0
  123. package/browser/editor/page/table/widths.mjs +9 -4
  124. package/browser/editor/page/text-select/index.mjs +43 -14
  125. package/browser/editor/page/todo-writes.mjs +75 -0
  126. package/browser/editor/page/tooltip.mjs +1 -1
  127. package/browser/editor/page/type-grid.mjs +4 -3
  128. package/browser/editor/page/typing/code-edges.mjs +63 -17
  129. package/browser/editor/page/typing/index.mjs +65 -9
  130. package/browser/editor/page/typing/leave.mjs +1 -1
  131. package/browser/editor/page/ui.mjs +13 -0
  132. package/browser/shared/annotations.mjs +8 -10
  133. package/browser/shared/block-packs.mjs +404 -0
  134. package/browser/shared/board-places.mjs +224 -0
  135. package/browser/shared/card-size.mjs +59 -0
  136. package/browser/shared/code-folds.mjs +5 -1
  137. package/browser/shared/colors.mjs +17 -14
  138. package/browser/shared/component-catalog.mjs +345 -54
  139. package/browser/shared/component-source.mjs +37 -0
  140. package/browser/shared/components/callout.mjs +4 -2
  141. package/browser/shared/components/index.mjs +72 -9
  142. package/browser/shared/components/installed.mjs +110 -8
  143. package/browser/shared/components/page.mjs +22 -3
  144. package/browser/shared/components/state.mjs +5 -1
  145. package/browser/shared/components/table.mjs +1 -1
  146. package/browser/shared/components/text.mjs +2 -2
  147. package/browser/shared/document-look.mjs +183 -0
  148. package/browser/shared/drafts.mjs +432 -0
  149. package/browser/shared/emoji/data/LICENSE-lucide.txt +43 -0
  150. package/browser/shared/emoji/data/README.md +4 -2
  151. package/browser/shared/emoji/data/categories.json +1 -1
  152. package/browser/shared/emoji/data/keywords-en.json +1 -1
  153. package/browser/shared/emoji/data/keywords-zh.json +1 -1
  154. package/browser/shared/emoji/data/names.json +1 -1
  155. package/browser/shared/emoji/data/positions.json +1 -1
  156. package/browser/shared/emoji/data/twemoji-index.json +1 -1
  157. package/browser/shared/emoji/data/twemoji-svg.bin +0 -0
  158. package/browser/shared/emoji/data.mjs +10 -12
  159. package/browser/shared/emoji/emoji.mjs +8 -7
  160. package/browser/shared/emoji/icon-value.mjs +4 -3
  161. package/browser/shared/extension-sidebar.mjs +126 -0
  162. package/browser/shared/extensions.mjs +79 -0
  163. package/browser/shared/file-table.mjs +101 -0
  164. package/browser/shared/html-tree.mjs +11 -5
  165. package/browser/shared/i18n/en.mjs +374 -35
  166. package/browser/shared/json-layout.mjs +47 -0
  167. package/browser/shared/long-runs.mjs +39 -0
  168. package/browser/shared/net-cache.mjs +133 -0
  169. package/browser/shared/net-keys.mjs +32 -0
  170. package/browser/shared/net-policy.mjs +177 -0
  171. package/browser/shared/new-names.mjs +5 -2
  172. package/browser/shared/outline-shape.mjs +128 -0
  173. package/browser/shared/outline.mjs +458 -0
  174. package/browser/shared/pack-manifest.mjs +77 -0
  175. package/browser/shared/page-address.mjs +6 -3
  176. package/browser/shared/page-cards.mjs +308 -0
  177. package/browser/shared/page-links.mjs +69 -6
  178. package/browser/shared/pages.mjs +388 -0
  179. package/browser/shared/paths.mjs +6 -0
  180. package/browser/shared/preferences.mjs +10 -4
  181. package/browser/shared/preview-server.mjs +3 -7
  182. package/browser/shared/session.mjs +13 -5
  183. package/browser/shared/shipped-blocks.mjs +299 -0
  184. package/browser/shared/table-chart.mjs +33 -1
  185. package/browser/shared/todos.mjs +83 -0
  186. package/browser/shared/user-directory.mjs +24 -0
  187. package/browser/shared/versions.mjs +14 -0
  188. package/browser/shared/web-links.mjs +134 -0
  189. package/browser/shared/workspace-columns.mjs +120 -0
  190. package/browser/shared/workspace-look.mjs +91 -0
  191. package/browser/shared/workspace-name.mjs +154 -0
  192. package/browser/shared/workspace-query.mjs +125 -0
  193. package/browser/shared/workspace-registry.mjs +272 -0
  194. package/browser/shared/workspace.mjs +171 -20
  195. package/browser/shared/zoom.mjs +5 -5
  196. package/dist/chunk-2NRBY6AJ.mjs +2 -0
  197. package/dist/chunk-32ZUEFZ7.mjs +2 -0
  198. package/dist/chunk-35NQ4PLQ.mjs +2 -0
  199. package/dist/chunk-3KJHLHUO.mjs +2 -0
  200. package/dist/chunk-3NJACR4W.mjs +2 -0
  201. package/dist/chunk-4AV456RQ.mjs +2 -0
  202. package/dist/chunk-4OZWXZE5.mjs +5 -0
  203. package/dist/chunk-5XYHDBJ5.mjs +2 -0
  204. package/dist/chunk-5ZJA7CXT.mjs +2 -0
  205. package/dist/chunk-6A53MZYZ.mjs +2 -0
  206. package/dist/chunk-6EXH3ORZ.mjs +166 -0
  207. package/dist/chunk-6EZ7FNIK.mjs +3 -0
  208. package/dist/{chunk-UXUK4PMC.mjs → chunk-6QBHU4DF.mjs} +1 -1
  209. package/dist/{chunk-6U2SLSDN.mjs → chunk-6VTU6KEO.mjs} +1 -1
  210. package/dist/chunk-B4P2CM2J.mjs +3 -0
  211. package/dist/chunk-BPL64HGA.mjs +4 -0
  212. package/dist/chunk-BVEFGE4B.mjs +2 -0
  213. package/dist/chunk-BZ5RAF7L.mjs +2 -0
  214. package/dist/chunk-CRJEG7MT.mjs +2 -0
  215. package/dist/{chunk-B3VTYBYR.mjs → chunk-D4CKRD3V.mjs} +91 -91
  216. package/dist/chunk-D4EURAX4.mjs +4 -0
  217. package/dist/chunk-DQCE7TJY.mjs +5 -0
  218. package/dist/chunk-DR73Y2UJ.mjs +3 -0
  219. package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
  220. package/dist/{chunk-DB4WKGYF.mjs → chunk-ERLAIBOA.mjs} +2 -2
  221. package/dist/chunk-EURLTOTZ.mjs +4 -0
  222. package/dist/chunk-FJYN7RTH.mjs +4 -0
  223. package/dist/chunk-FWNRPMCJ.mjs +12 -0
  224. package/dist/chunk-FWW4CSAZ.mjs +2 -0
  225. package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
  226. package/dist/chunk-GY3FAUVM.mjs +2 -0
  227. package/dist/chunk-HLYV7RIH.mjs +2 -0
  228. package/dist/chunk-HMYTM3GT.mjs +15 -0
  229. package/dist/chunk-IA4PSIMD.mjs +2 -0
  230. package/dist/chunk-JEUK3ZO7.mjs +2 -0
  231. package/dist/chunk-JVHHHGEA.mjs +3 -0
  232. package/dist/chunk-KTKHRC6H.mjs +2 -0
  233. package/dist/chunk-L7UP52JO.mjs +2 -0
  234. package/dist/chunk-MHW7ZKOU.mjs +2 -0
  235. package/dist/chunk-N76HWBA3.mjs +10 -0
  236. package/dist/chunk-N7AZZLGT.mjs +548 -0
  237. package/dist/chunk-NAMN3RAF.mjs +2 -0
  238. package/dist/{chunk-YXAUQPVK.mjs → chunk-NFMCP2HI.mjs} +3 -3
  239. package/dist/chunk-NG375ILQ.mjs +2 -0
  240. package/dist/chunk-NVRNLK25.mjs +6 -0
  241. package/dist/chunk-NWFBE2YA.mjs +2 -0
  242. package/dist/chunk-NWR2LT5B.mjs +2 -0
  243. package/dist/chunk-OJKMINIU.mjs +20 -0
  244. package/dist/chunk-OUUP3WKT.mjs +2 -0
  245. package/dist/chunk-OW5NWPOJ.mjs +10 -0
  246. package/dist/chunk-PAREWQGB.mjs +4 -0
  247. package/dist/chunk-POQSE4C2.mjs +2 -0
  248. package/dist/chunk-PUOQ65NC.mjs +2 -0
  249. package/dist/{chunk-Z52HTUW7.mjs → chunk-PVER7LSC.mjs} +2 -2
  250. package/dist/chunk-PZDLJJTJ.mjs +3 -0
  251. package/dist/chunk-Q47PLKIJ.mjs +15 -0
  252. package/dist/chunk-Q4UEPUWF.mjs +2 -0
  253. package/dist/chunk-QKXMPUXC.mjs +2 -0
  254. package/dist/chunk-QOUEFUCH.mjs +16 -0
  255. package/dist/chunk-RFDZXFJF.mjs +13 -0
  256. package/dist/chunk-RKPWLGZ3.mjs +4 -0
  257. package/dist/chunk-RMECZA2K.mjs +2 -0
  258. package/dist/{chunk-EUX7IC5J.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
  259. package/dist/chunk-SX4LJYT6.mjs +8 -0
  260. package/dist/chunk-SXHBYZST.mjs +16 -0
  261. package/dist/chunk-TANZRINB.mjs +2 -0
  262. package/dist/chunk-TEZAWDSQ.mjs +2 -0
  263. package/dist/chunk-UBBRTJ2R.mjs +24 -0
  264. package/dist/chunk-UFNYFFKO.mjs +20 -0
  265. package/dist/chunk-UGWYKHBL.mjs +170 -0
  266. package/dist/chunk-UKSHDZZ6.mjs +2 -0
  267. package/dist/chunk-UMHUSOPJ.mjs +2 -0
  268. package/dist/chunk-UNOYVIYR.mjs +2 -0
  269. package/dist/chunk-V6RIYQZZ.mjs +4 -0
  270. package/dist/chunk-VJPFVNNW.mjs +15 -0
  271. package/dist/chunk-VS3ZMVGX.mjs +5 -0
  272. package/dist/chunk-VTKNEYT3.mjs +6 -0
  273. package/dist/chunk-VXACTLO3.mjs +3 -0
  274. package/dist/chunk-W3BGBJQW.mjs +2 -0
  275. package/dist/chunk-YI43RG4P.mjs +2 -0
  276. package/dist/chunk-YQGT4AB2.mjs +6 -0
  277. package/dist/chunk-ZB4H7ZN4.mjs +2 -0
  278. package/dist/{chunk-ACOPDCNB.mjs → chunk-ZHEHPMBK.mjs} +1 -1
  279. package/dist/chunk-ZTHICSSF.mjs +2 -0
  280. package/dist/chunk-ZUNVAXLH.mjs +2 -0
  281. package/dist/chunk-ZWPMCK7P.mjs +2 -0
  282. package/dist/cli.mjs +1 -1
  283. package/dist/server.mjs +1 -1
  284. package/dist/{chunk-EVJ3BX3M.mjs → update-fetch.mjs} +1 -1
  285. package/dist/worker.mjs +1 -1
  286. package/kit/board.css +95 -0
  287. package/kit/board.js +250 -0
  288. package/kit/charts.js +5 -71
  289. package/kit/code.js +14 -14
  290. package/kit/math.js +18 -2
  291. package/kit/report.css +500 -151
  292. package/kit/runtime.js +1720 -170
  293. package/kit/state.js +10 -1
  294. package/kit/tables.js +172 -37
  295. package/kit/ui.js +166 -27
  296. package/package.json +2 -1
  297. package/packs/gallery/card/component.json +14 -0
  298. package/packs/gallery/card/example.html +10 -0
  299. package/packs/gallery/card/preview.svg +1 -0
  300. package/packs/gallery/card/style.css +6 -0
  301. package/packs/gallery/card/template.html +6 -0
  302. package/packs/gallery/card/writing.md +14 -0
  303. package/packs/gallery/chart-card/component.json +15 -0
  304. package/packs/gallery/chart-card/example.html +14 -0
  305. package/packs/gallery/chart-card/kit.js +130 -0
  306. package/packs/gallery/chart-card/preview.svg +1 -0
  307. package/packs/gallery/chart-card/style.css +42 -0
  308. package/packs/gallery/chart-card/template.html +13 -0
  309. package/packs/gallery/chart-card/writing.md +20 -0
  310. package/packs/gallery/chart-view/component.json +20 -0
  311. package/packs/gallery/chart-view/example.html +13 -0
  312. package/packs/gallery/chart-view/kit.js +355 -0
  313. package/packs/gallery/chart-view/preview.svg +1 -0
  314. package/packs/gallery/chart-view/style.css +94 -0
  315. package/packs/gallery/chart-view/template.html +3 -0
  316. package/packs/gallery/chart-view/writing.md +22 -0
  317. package/packs/gallery/countdown/component.json +18 -0
  318. package/packs/gallery/countdown/example.html +3 -0
  319. package/packs/gallery/countdown/kit.js +156 -0
  320. package/packs/gallery/countdown/preview.svg +1 -0
  321. package/packs/gallery/countdown/style.css +130 -0
  322. package/packs/gallery/countdown/template.html +3 -0
  323. package/packs/gallery/countdown/writing.md +22 -0
  324. package/packs/gallery/habit/component.json +14 -0
  325. package/packs/gallery/habit/example.html +3 -0
  326. package/packs/gallery/habit/kit.js +295 -0
  327. package/packs/gallery/habit/preview.svg +1 -0
  328. package/packs/gallery/habit/style.css +246 -0
  329. package/packs/gallery/habit/template.html +3 -0
  330. package/packs/gallery/habit/writing.md +21 -0
  331. package/packs/gallery/heatmap/component.json +18 -0
  332. package/packs/gallery/heatmap/example.html +3 -0
  333. package/packs/gallery/heatmap/kit.js +351 -0
  334. package/packs/gallery/heatmap/preview.svg +1 -0
  335. package/packs/gallery/heatmap/style.css +201 -0
  336. package/packs/gallery/heatmap/template.html +3 -0
  337. package/packs/gallery/heatmap/writing.md +21 -0
  338. package/packs/gallery/kanban/component.json +18 -0
  339. package/packs/gallery/kanban/example.html +12 -0
  340. package/packs/gallery/kanban/kit.js +540 -0
  341. package/packs/gallery/kanban/preview.svg +1 -0
  342. package/packs/gallery/kanban/style.css +212 -0
  343. package/packs/gallery/kanban/template.html +3 -0
  344. package/packs/gallery/kanban/writing.md +20 -0
  345. package/packs/gallery/metric/component.json +22 -0
  346. package/packs/gallery/metric/example.html +15 -0
  347. package/packs/gallery/metric/kit.js +248 -0
  348. package/packs/gallery/metric/preview.svg +1 -0
  349. package/packs/gallery/metric/style.css +92 -0
  350. package/packs/gallery/metric/template.html +3 -0
  351. package/packs/gallery/metric/writing.md +23 -0
  352. package/packs/gallery/note/component.json +14 -0
  353. package/packs/gallery/note/example.html +3 -0
  354. package/packs/gallery/note/kit.js +102 -0
  355. package/packs/gallery/note/preview.svg +1 -0
  356. package/packs/gallery/note/style.css +76 -0
  357. package/packs/gallery/note/template.html +3 -0
  358. package/packs/gallery/note/writing.md +18 -0
  359. package/packs/gallery/pack.json +8 -0
  360. package/packs/gallery/recent/component.json +13 -0
  361. package/packs/gallery/recent/example.html +1 -0
  362. package/packs/gallery/recent/kit.js +238 -0
  363. package/packs/gallery/recent/preview.svg +1 -0
  364. package/packs/gallery/recent/style.css +158 -0
  365. package/packs/gallery/recent/template.html +1 -0
  366. package/packs/gallery/recent/writing.md +15 -0
  367. package/packs/gallery/static/component.json +13 -0
  368. package/packs/gallery/static/example.html +11 -0
  369. package/packs/gallery/static/preview.svg +1 -0
  370. package/packs/gallery/static/style.css +138 -0
  371. package/packs/gallery/static/template.html +1 -0
  372. package/packs/gallery/static/writing.md +18 -0
  373. package/packs/gallery/stats/component.json +13 -0
  374. package/packs/gallery/stats/example.html +1 -0
  375. package/packs/gallery/stats/kit.js +166 -0
  376. package/packs/gallery/stats/preview.svg +1 -0
  377. package/packs/gallery/stats/style.css +151 -0
  378. package/packs/gallery/stats/template.html +1 -0
  379. package/packs/gallery/stats/writing.md +11 -0
  380. package/packs/gallery/today/component.json +12 -0
  381. package/packs/gallery/today/example.html +1 -0
  382. package/packs/gallery/today/kit.js +185 -0
  383. package/packs/gallery/today/preview.svg +1 -0
  384. package/packs/gallery/today/style.css +120 -0
  385. package/packs/gallery/today/template.html +1 -0
  386. package/packs/gallery/today/writing.md +14 -0
  387. package/packs/gallery/todos/component.json +19 -0
  388. package/packs/gallery/todos/example.html +3 -0
  389. package/packs/gallery/todos/kit.js +342 -0
  390. package/packs/gallery/todos/preview.svg +1 -0
  391. package/packs/gallery/todos/style.css +267 -0
  392. package/packs/gallery/todos/template.html +3 -0
  393. package/packs/gallery/todos/writing.md +30 -0
  394. package/packs/gallery/weather/component.json +19 -0
  395. package/packs/gallery/weather/example.html +3 -0
  396. package/packs/gallery/weather/kit.js +312 -0
  397. package/packs/gallery/weather/preview.svg +1 -0
  398. package/packs/gallery/weather/style.css +258 -0
  399. package/packs/gallery/weather/template.html +3 -0
  400. package/packs/gallery/weather/writing.md +26 -0
  401. package/packs/gallery-soft/pack.json +8 -0
  402. package/release.json +13 -11
  403. package/sdk/doc-kit.d.ts +404 -7
  404. package/sdk/doc-state.d.ts +2 -0
  405. package/skill/SKILL.md +71 -26
  406. package/skill/references/block-script.md +140 -0
  407. package/skill/references/building-blocks.md +257 -0
  408. package/skill/references/example-report.html +1 -1
  409. package/skill/references/extensions.md +59 -0
  410. package/skill/references/pages-and-data.md +221 -0
  411. package/skill/references/writing-html.md +9 -7
  412. package/skill/sdk/doc-kit.d.ts +404 -7
  413. package/skill/sdk/doc-state.d.ts +2 -0
  414. package/web/assets/index-BK2e_07w.js +13 -0
  415. package/web/assets/index-CWFI83mF.css +1 -0
  416. package/web/index.html +2 -2
  417. package/browser/editor/components/callout/page.mjs +0 -13
  418. package/dist/chunk-2PBALTR3.mjs +0 -57
  419. package/dist/chunk-3LA3EMDE.mjs +0 -15
  420. package/dist/chunk-5FRMCDPY.mjs +0 -16
  421. package/dist/chunk-5LEK2CBS.mjs +0 -2
  422. package/dist/chunk-5OFN5AHC.mjs +0 -2
  423. package/dist/chunk-5SIYS5HN.mjs +0 -2
  424. package/dist/chunk-5TEK3RAD.mjs +0 -2
  425. package/dist/chunk-6CYG3Z4V.mjs +0 -2
  426. package/dist/chunk-7GHT2UCA.mjs +0 -15
  427. package/dist/chunk-7QQBR7PB.mjs +0 -2
  428. package/dist/chunk-ATCJYAR4.mjs +0 -2
  429. package/dist/chunk-C7HWOBIF.mjs +0 -2
  430. package/dist/chunk-E7VYDG2X.mjs +0 -2
  431. package/dist/chunk-EZY3WC6Z.mjs +0 -15
  432. package/dist/chunk-FQGQH4KM.mjs +0 -2
  433. package/dist/chunk-G7TOXONE.mjs +0 -6
  434. package/dist/chunk-GPF4YWWB.mjs +0 -9
  435. package/dist/chunk-GYQV7Y4J.mjs +0 -120
  436. package/dist/chunk-HMHK3EQ6.mjs +0 -2
  437. package/dist/chunk-HQQMJCQJ.mjs +0 -10
  438. package/dist/chunk-IERUOGC3.mjs +0 -6
  439. package/dist/chunk-ITNLYXXV.mjs +0 -3
  440. package/dist/chunk-L4DVONOG.mjs +0 -2
  441. package/dist/chunk-LAE5NQ4B.mjs +0 -2
  442. package/dist/chunk-LC7YNQFG.mjs +0 -2
  443. package/dist/chunk-LHTFLRB6.mjs +0 -4
  444. package/dist/chunk-NFIBI4IP.mjs +0 -26
  445. package/dist/chunk-NSKUTQ5J.mjs +0 -5
  446. package/dist/chunk-O52JMOCZ.mjs +0 -2
  447. package/dist/chunk-P2CAZPDP.mjs +0 -3
  448. package/dist/chunk-QMQUXTU4.mjs +0 -12
  449. package/dist/chunk-QVWVFC5K.mjs +0 -6
  450. package/dist/chunk-QYKCJ7FG.mjs +0 -8
  451. package/dist/chunk-QZY6Q64C.mjs +0 -2
  452. package/dist/chunk-TDQP3DKQ.mjs +0 -10
  453. package/dist/chunk-TX6JH7TR.mjs +0 -2
  454. package/dist/chunk-UNUEWH4S.mjs +0 -2
  455. package/dist/chunk-V4CPWKMZ.mjs +0 -2
  456. package/dist/chunk-VACRB2NE.mjs +0 -3
  457. package/dist/chunk-W63L7367.mjs +0 -7
  458. package/dist/chunk-W7CXPCRV.mjs +0 -6
  459. package/dist/chunk-XINNG4F2.mjs +0 -4
  460. package/dist/chunk-YL2DPWVS.mjs +0 -4
  461. package/dist/chunk-YZ5Z6H4W.mjs +0 -4
  462. package/dist/chunk-ZBIM6ARY.mjs +0 -216
  463. package/skill/references/building-components.md +0 -327
  464. package/web/assets/index-B6Ai3wsa.js +0 -13
  465. package/web/assets/index-SYPC3EZy.css +0 -1
@@ -1,17 +1,20 @@
1
1
  import { isUtf8 } from "node:buffer";
2
2
  import { createHash } from "node:crypto";
3
- import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
3
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
4
4
  import fs from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import { parseFragment, serializeOuter } from "parse5";
7
7
  import { isInstance, parseCompound, parsePart, parseSelector, partOf } from "./components/index.mjs";
8
- import { ATTRIBUTE, NO_TEXT, PACKAGE_ID, PACKAGE_MARK, instanceMatch, refusedInCopy, settingState } from "./components/installed.mjs";
8
+ import { ATTRIBUTE, NO_TEXT, PACKAGE_ID, PACKAGE_MARK, SLOT, inDocumentMenus, instanceMatch, refusedInCopy, settingState } from "./components/installed.mjs";
9
9
  import { writeAtomic } from "./files.mjs";
10
10
  import { attr, walk } from "./html-tree.mjs";
11
+ import { HOST, NET, netHostsOf } from "./net-policy.mjs";
11
12
  import { inLanguage, refusal, said, saying, told } from "./i18n/index.mjs";
13
+ import { PACK_FILE, PACK_ID, readPackManifest } from "./pack-manifest.mjs";
12
14
  import { DEVICE_NAMES, PACKAGE_MANIFEST, WORKSPACE_MARK, findWorkspace, layoutOf, markerOf, packageFolders } from "./workspace.mjs";
13
15
 
14
- // 组件包: what the workspace's component packages say of themselves to Gallery, read from
16
+ // 组件包, custom blocks (自定义块) as the UI and the Agent's documents call them: what the workspace's
17
+ // component packages say of themselves to Gallery, read from
15
18
  // <workspace>/components/<scope>/<name>/component.json, plain data, whose code no system runs here. A
16
19
  // package is a description as a kit component's is (src/shared/components/): its id, where its
17
20
  // instances are, its parts and what it leaves on a page, so that what reads a built-in's reads a
@@ -19,19 +22,22 @@ import { DEVICE_NAMES, PACKAGE_MANIFEST, WORKSPACE_MARK, findWorkspace, layoutOf
19
22
  // A manifest offers only FIELDS, no unit, box, host, rank or category: those are the engines', and a
20
23
  // field it does not know keeps its package from loading.
21
24
  //
22
- // The catalog is { packages, problems, settings, stamp }. `packages`, by id, are those that load. A
25
+ // The catalog is { packages, problems, settings, packs, stamp }. `packages`, by id, are those that load. A
23
26
  // package that does not load (a manifest that does not read as one, one for an apiVersion this Gallery
24
27
  // does not run, which counts as disabled, or one the settings turned off) stands in `problems`, by its
25
28
  // id in lowercase, as an instance names it, with why (`problem`, said()'s, in either language by
26
29
  // problemIn), and touches nothing else: its instances show as those of a package that is not there,
27
- // and whoever reads them says why. `settings` is what gallery.json says of the components (设置 › 组件,
28
- // componentSettings): `hidden` and `disabled`, by the id of a package or the name of a built-in
29
- // component, and `problem` where the file cannot be read as saying anything (settingsNow). A package
30
- // hidden runs as any other and is left out of the lists that offer one, each of which reads its state
31
- // (menuOf, packageStates); one disabled is one that does not load, so that nothing that loads a
30
+ // and whoever reads them says why. `settings` is what gallery.json says of the components (设置 › 组件列表,
31
+ // componentSettings): `hidden`, `disabled`, `shown` and `unlisted`, by the id of a package (`hidden` the name of a
32
+ // built-in component too), `packs` and `packsOff`, the packs' switches (a pack off: none of its blocks offered,
33
+ // as one unlisted), and `problem` where the file cannot be read as saying anything (settingsNow). `packs`, by
34
+ // the scope of a block (a pack: ./pack-manifest.mjs), what its pack.json names it, or null for a scope with
35
+ // none or one that does not read (who lists it says it by its scope).
36
+ // A package the document's menus do not offer (inDocumentMenus: hidden, unlisted, or a custom page's alone and not
37
+ // shown) runs as any other and is left out of those menus, which read its state (menuOf, packageStates); one disabled is one that does not load, so that nothing that loads a
32
38
  // package has a case for it. A stamp is a hash of what files hold: `stamp`, of a package, over what
33
39
  // reaches a page of it (STAMPED and the files `assets` names), of one that does not load over those
34
- // and why, and of the catalog, over its packages', what is hidden and whether gallery.json reads;
40
+ // and why, and of the catalog, over its packages', what is hidden, shown and unlisted and whether gallery.json reads;
35
41
  // `definition`, over the manifest alone, is what a page's commit is to be held to.
36
42
  //
37
43
  // readCatalog reads the disk; what reads a catalog for use (the scan, the checks, a page) asks
@@ -43,16 +49,47 @@ import { DEVICE_NAMES, PACKAGE_MANIFEST, WORKSPACE_MARK, findWorkspace, layoutOf
43
49
  /** The apiVersion this Gallery runs. */
44
50
  export const API_VERSION = 1;
45
51
 
46
- /** The kit's files a package may ask for by id in `assets`, as src/render/assets.mjs names them (the others are on every page). */
52
+ /** The kit's files a package may ask for by id in `assets`, as src/render/assets.mjs names them (the others are on every page, or, the board's, on every custom page). */
47
53
  export const KIT_ASSETS = new Set(["code", "state", "sheets", "tokens", "dialogs", "katex-styles", "katex", "katex-auto-render", "math", "d3", "charts", "tables"]);
48
54
 
49
55
  // What a manifest may hold, a part of one and its `runtime`.
50
- const FIELDS = ["id", "apiVersion", "version", "name", "description", "words", "parts", "runtime", "assets", "markdown", "schema"];
56
+ const FIELDS = ["id", "apiVersion", "version", "name", "description", "words", "parts", "runtime", "assets", "markdown", "schema", "places", "card", "settings", "needs"];
51
57
  const PART_FIELDS = ["at", "edit", "optional", "min", "max"];
52
58
  const RUNTIME_FIELDS = ["generated", "attrs"];
53
59
 
54
- // What a package's part may be edited as: its text, its data through the kit's edit, or not at all.
55
- const EDITS = ["text", "data", "none"];
60
+ // Where a block may stand (`places`, notes/plans/custom-blocks-spec-2026-11-07.md section 1): in a
61
+ // document's flow, on a custom page's board as a card. One that says nothing is a document's, as every
62
+ // package before the field was.
63
+ const PLACES = ["document", "page"];
64
+ const DOCUMENT_ONLY = ["document"];
65
+ // What `card` says of a card on a board, in cells of the grid: a row is 12 of them, and a card at most
66
+ // 24 high.
67
+ const CARD_FIELDS = ["sizes", "w", "h", "min", "max", "resize", "fit", "bare"];
68
+ const GRID = { w: 12, h: 24 };
69
+ const FITS = ["fixed", "content"];
70
+ const NO_CARD = { sizes: null, any: false, w: 4, h: null, min: { w: 1, h: 1 }, max: { ...GRID }, resize: false, fit: "fixed", bare: false };
71
+ // A card setting: its key, a JavaScript name as a block reads it from its settings part; its kinds,
72
+ // each a field of the form the board draws; `required`: the card shows nothing until the setting holds a
73
+ // value (a weather card's city), which gallery page check says of a card that has none.
74
+ const SETTING_FIELDS = ["key", "label", "type", "options", "default", "required"];
75
+ const SETTING_KEY = /^[a-z][a-zA-Z0-9]*$/;
76
+ const SETTING_TYPES = ["text", "number", "select", "date", "switch", "document"];
77
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
78
+ /** The data part a block with settings keeps their values in, JSON, as the board's form writes them. */
79
+ export const SETTINGS_PART = "settings";
80
+ // What a block asks to use: the workspace's data, the keyed tables of its document, the network of a
81
+ // host (lowercase, no port and no path, "*." first for its subdomains: ./net-policy.mjs HOST).
82
+ const NEEDS = ["workspace", "tables"];
83
+
84
+ // What a package's part may be edited as: its text, its data through the kit's edit, the document's
85
+ // blocks (a slot, marked data-doc-slot="<name>" in its markup), or not at all.
86
+ const EDITS = ["text", "data", "flow", "none"];
87
+ // What a slot's name may be: it is written in an attribute's value, and a selector finds it there.
88
+ const SLOT_NAME = /^[a-z][a-z0-9-]*$/;
89
+ /** Where a part edited as flow is in an instance: the slot of its name. */
90
+ export const slotAt = (name) => `div[${SLOT}="${name}"], section[${SLOT}="${name}"]`;
91
+ // A manifest's parts as the engines read them: a slot's `at` is its mark, which the manifest does not write.
92
+ const partsRead = (parts) => Object.fromEntries(Object.entries(parts).map(([name, part]) => [name, part.edit === "flow" ? { ...part, at: slotAt(name) } : part]));
56
93
  const MARKDOWN = ["parts", "text"];
57
94
 
58
95
  // The files of a package by their fixed names: those served from its folder (kit.js and style.css are
@@ -142,7 +179,7 @@ const notTooLong = (bytes, name) => {
142
179
  };
143
180
 
144
181
  const onlyFields = (table, fields, where = "") => {
145
- for (const key of Object.keys(table)) if (!fields.includes(key)) fail(said("{0} 不是组件包能写的字段", `${where}${key}`));
182
+ for (const key of Object.keys(table)) if (!fields.includes(key)) fail(said("{0} 不是自定义块的清单能写的字段", `${where}${key}`));
146
183
  };
147
184
  const required = (manifest, fields) => {
148
185
  for (const field of fields) if (manifest[field] === undefined) fail(said("缺少 {0}", field));
@@ -158,13 +195,21 @@ function checkPart(name, part) {
158
195
  const where = `parts.${name}`;
159
196
  if (!isTable(part)) fail(said("{0} 要写成一张表", where));
160
197
  onlyFields(part, PART_FIELDS, `${where}.`);
198
+ if (part.edit === "flow") {
199
+ // A slot: found by its mark, one to an instance.
200
+ if (!SLOT_NAME.test(name)) fail(said("{0} 是插槽,名字只用小写字母、数字和连字符", where));
201
+ if (part.at !== undefined) fail(said("{0} 是插槽,由 data-doc-slot=\"{1}\" 标出,不写 at", where, name));
202
+ if (part.min !== undefined || part.max !== undefined) fail(said("{0} 是插槽,一个实例里只有一个,不写 min、max", where));
203
+ if (part.optional !== undefined && typeof part.optional !== "boolean") fail(said("{0} 要写成 true 或 false", `${where}.optional`));
204
+ return;
205
+ }
161
206
  if (!isWords(part.at)) fail(said("缺少 {0}", `${where}.at`));
162
207
  try {
163
208
  parsePart(part.at);
164
209
  } catch (error) {
165
210
  fail(said("{0} 写错了:{1}", `${where}.at`, error.message));
166
211
  }
167
- if (!EDITS.includes(part.edit)) fail(said("{0} 的 edit 只能是 text、data 或 none", where));
212
+ if (!EDITS.includes(part.edit)) fail(said("{0} 的 edit 只能是 text、data、flow 或 none", where));
168
213
  if (part.optional !== undefined && typeof part.optional !== "boolean") fail(said("{0} 要写成 true 或 false", `${where}.optional`));
169
214
  for (const bound of ["min", "max"]) if (part[bound] !== undefined && !(Number.isInteger(part[bound]) && part[bound] >= 0)) fail(said("{0} 要写成不小于 0 的整数", `${where}.${bound}`));
170
215
  if (part.min > part.max) fail(said("{0} 的 min 比 max 大,没有实例能满足", where));
@@ -195,7 +240,7 @@ function runtimeOf(runtime, id) {
195
240
  if (!isTable(attrs)) fail(said("{0} 要写成一张表", "runtime.attrs"));
196
241
  for (const names of Object.values(attrs)) {
197
242
  if (!Array.isArray(names) || !names.every(isWords)) fail(said("{0} 要写成一串文字", "runtime.attrs"));
198
- for (const name of names) if (OWN_NAME.test(name)) fail(said("{0} 是 Gallery 自己的名字,组件包不能用", name));
243
+ for (const name of names) if (OWN_NAME.test(name)) fail(said("{0} 是 Gallery 自己的名字,自定义块不能用", name));
199
244
  }
200
245
  const made = (selector) => {
201
246
  const written = scoped(selector, "runtime.generated");
@@ -208,14 +253,136 @@ function runtimeOf(runtime, id) {
208
253
  };
209
254
  }
210
255
 
256
+ const isCount = (value, most) => Number.isInteger(value) && value >= 1 && value <= most;
257
+ const twice = (list) => list.find((value, index) => list.indexOf(value) !== index);
258
+
259
+ // `places`: one place or both, each once.
260
+ function placesOf(places) {
261
+ if (places === undefined) return DOCUMENT_ONLY;
262
+ if (!Array.isArray(places) || places.length === 0 || !places.every((place) => PLACES.includes(place))) fail(said("places 要写成 document、page 中的一个或两个,如 [\"document\"]"));
263
+ if (twice(places)) fail(said("places 里 {0} 写了两次", twice(places)));
264
+ return [...places];
265
+ }
266
+
267
+ // `card`: as a board reads it, every field there (NO_CARD's where the manifest says nothing; `h` null:
268
+ // the fewest whole cells its content takes). With `sizes`, the first is the default, and no w or h is
269
+ // written beside them; a min or a max beside them frees the card (`any`): the sizes are the common ones
270
+ // its 大小 menu lists, any whole size between min and max is its too (resizing by the corner, a place),
271
+ // and each of the sizes stands inside them.
272
+ function cardOf(card) {
273
+ if (card === undefined) return NO_CARD;
274
+ if (!isTable(card)) fail(said("{0} 要写成一张表", "card"));
275
+ onlyFields(card, CARD_FIELDS, "card.");
276
+ const sized = (pair) => Array.isArray(pair) && pair.length === 2 && isCount(pair[0], GRID.w) && isCount(pair[1], GRID.h);
277
+ if (card.sizes !== undefined) {
278
+ if (!Array.isArray(card.sizes) || card.sizes.length === 0 || !card.sizes.every(sized)) fail(said("card.sizes 要写成 [宽, 高] 的一串,宽 1 到 {0} 格,高 1 到 {1} 格", GRID.w, GRID.h));
279
+ const repeated = twice(card.sizes.map((pair) => pair.join("x")));
280
+ if (repeated) fail(said("card.sizes 里 {0} 写了两次", repeated));
281
+ const beside = ["w", "h"].find((field) => card[field] !== undefined);
282
+ if (beside) fail(said("写了 card.sizes,就不再写 card.{0}:第一种大小是默认的", beside));
283
+ }
284
+ for (const axis of ["w", "h"]) if (card[axis] !== undefined && !isCount(card[axis], GRID[axis])) fail(said("{0} 要写成 1 到 {1} 的整数", `card.${axis}`, GRID[axis]));
285
+ const bounds = {};
286
+ for (const bound of ["min", "max"]) {
287
+ const value = card[bound];
288
+ if (value === undefined) continue;
289
+ if (!isTable(value)) fail(said("{0} 要写成 { w, h },单位是格", `card.${bound}`));
290
+ onlyFields(value, ["w", "h"], `card.${bound}.`);
291
+ for (const axis of ["w", "h"]) if (value[axis] !== undefined && !isCount(value[axis], GRID[axis])) fail(said("{0} 要写成 1 到 {1} 的整数", `card.${bound}.${axis}`, GRID[axis]));
292
+ bounds[bound] = value;
293
+ }
294
+ const min = { w: bounds.min?.w ?? 1, h: bounds.min?.h ?? 1 };
295
+ const max = { w: bounds.max?.w ?? GRID.w, h: bounds.max?.h ?? GRID.h };
296
+ const w = card.sizes ? card.sizes[0][0] : card.w ?? NO_CARD.w;
297
+ const h = card.sizes ? card.sizes[0][1] : card.h ?? null;
298
+ const any = Boolean(card.sizes) && (card.min !== undefined || card.max !== undefined);
299
+ if (any) {
300
+ const outside = card.sizes.find(([width, height]) => width < min.w || width > max.w || height < min.h || height > max.h);
301
+ if (outside) fail(said("card.sizes 里 {0} 不在 card.min 到 card.max 之间", outside.join("x")));
302
+ }
303
+ for (const axis of ["w", "h"]) {
304
+ if (min[axis] > max[axis]) fail(said("card.min.{0} 比 card.max.{0} 大", axis));
305
+ const value = { w, h }[axis];
306
+ if (value !== null && !card.sizes && (value < min[axis] || value > max[axis])) fail(said("默认的 card.{0} 是 {1},不在 card.min 到 card.max 之间({2} 到 {3})", axis, value, min[axis], max[axis]));
307
+ }
308
+ for (const flag of ["resize", "bare"]) if (card[flag] !== undefined && typeof card[flag] !== "boolean") fail(said("{0} 要写成 true 或 false", `card.${flag}`));
309
+ if (card.fit !== undefined && !FITS.includes(card.fit)) fail(said("card.fit 只能是 fixed 或 content"));
310
+ return {
311
+ sizes: card.sizes ? card.sizes.map(([width, height]) => [width, height]) : null,
312
+ any, w, h, min, max,
313
+ resize: card.resize ?? false,
314
+ fit: card.fit ?? "fixed",
315
+ bare: card.bare ?? false,
316
+ };
317
+ }
318
+
319
+ // Whether `value` can be the default of a setting of `type` (with its `options`).
320
+ const fitsSetting = (value, { type, options }) => ({
321
+ text: typeof value === "string",
322
+ number: typeof value === "number" && Number.isFinite(value),
323
+ select: options?.includes(value),
324
+ date: typeof value === "string" && DATE.test(value),
325
+ switch: typeof value === "boolean",
326
+ document: typeof value === "string",
327
+ })[type];
328
+
329
+ // `settings`: the fields of a card's form, each key once; a block with any keeps their values in a JSON
330
+ // data part of its own (SETTINGS_PART), which its parts are to name.
331
+ function settingsOf(settings, parts) {
332
+ if (settings === undefined) return [];
333
+ if (!Array.isArray(settings)) fail(said("{0} 要写成一串字段", "settings"));
334
+ const keys = [];
335
+ settings.forEach((setting, index) => {
336
+ const where = `settings[${index}]`;
337
+ if (!isTable(setting)) fail(said("{0} 要写成一张表", where));
338
+ onlyFields(setting, SETTING_FIELDS, `${where}.`);
339
+ if (typeof setting.key !== "string" || !SETTING_KEY.test(setting.key)) fail(said("{0} 的 key 以小写字母开头,只用字母和数字(如 city、startDate)", where));
340
+ if (keys.includes(setting.key)) fail(said("settings 里 key {0} 写了两次", setting.key));
341
+ keys.push(setting.key);
342
+ bothLanguages(setting.label, `${where}.label`);
343
+ if (!SETTING_TYPES.includes(setting.type)) fail(said("{0} 的 type 只能是 {1}", where, SETTING_TYPES.join(", ")));
344
+ if (setting.type === "select") {
345
+ if (!Array.isArray(setting.options) || setting.options.length === 0 || !setting.options.every(isWords)) fail(said("{0} 是 select,要有 options:一串文字", where));
346
+ if (twice(setting.options)) fail(said("{0} 的 options 里 {1} 写了两次", where, twice(setting.options)));
347
+ } else if (setting.options !== undefined) fail(said("{0} 不是 select,不写 options", where));
348
+ if (setting.default !== undefined && !fitsSetting(setting.default, setting)) fail(said("{0} 的 default 和它的 type {1} 不符", where, setting.type));
349
+ if (setting.required !== undefined && typeof setting.required !== "boolean") fail(said("{0} 的 required 要写 true 或 false", where));
350
+ });
351
+ const part = parts[SETTINGS_PART];
352
+ if (settings.length > 0 && !(part?.edit === "data" && tailTags(part.at).every((tag) => tag === "script") && tailTags(part.at).length > 0)) {
353
+ fail(said("写了 settings,parts 里就要有一个名为 settings、edit 为 data 的部分,它是一个 <script type=\"application/json\">,存卡片设置的值"));
354
+ }
355
+ return settings.map((setting) => ({ ...setting }));
356
+ }
357
+
358
+ // `needs`: what a block asks to use, each once.
359
+ function needsOf(needs) {
360
+ if (needs === undefined) return [];
361
+ if (!Array.isArray(needs)) fail(said("{0} 要写成一串文字", "needs"));
362
+ for (const need of needs) {
363
+ if (typeof need === "string" && (NEEDS.includes(need) || (need.startsWith(NET) && HOST.test(need.slice(NET.length))))) continue;
364
+ fail(said("needs 里的 {0} 不认识:只认 workspace、tables 和 net:<域名>(小写的主机名,不带端口和路径,可以以 *. 开头)", typeof need === "string" ? need : JSON.stringify(need)));
365
+ }
366
+ if (twice(needs)) fail(said("needs 里 {0} 写了两次", twice(needs)));
367
+ return [...needs];
368
+ }
369
+
370
+ // What a block that may stand on a custom page cannot hold: a slot, whose blocks are a document's,
371
+ // which no page has the editor and the rendering of.
372
+ function pageParts(places, parts) {
373
+ if (!places.includes("page")) return;
374
+ const slot = Object.entries(parts).find(([, part]) => part.edit === "flow");
375
+ if (slot) fail(said("places 里有 page,就不能有插槽:{0} 的 edit 是 flow,插槽放的是文档的块,自定义页上没有", `parts.${slot[0]}`));
376
+ }
377
+
211
378
  // `assets`: the kit's files a package asks for, by id, and files of its own, by their path in its folder.
212
379
  function assetsOf(assets, folder) {
213
380
  if (assets === undefined) return { kit: [], own: [] };
214
381
  if (!Array.isArray(assets) || !assets.every(isWords)) fail(said("{0} 要写成一串文字", "assets"));
215
382
  const own = assets.filter((asset) => !KIT_ASSETS.has(asset));
216
383
  for (const asset of own) {
217
- if (!OWN_FILE.test(asset)) fail(said("assets 里的 {0} 既不是套件的资源,也不是包里的文件", asset));
218
- if (!ownPath(folder, asset)) fail(said("assets 写了 {0},包里没有这个文件", asset));
384
+ if (!OWN_FILE.test(asset)) fail(said("assets 里的 {0} 既不是套件的资源,也不是块的文件夹里的文件", asset));
385
+ if (!ownPath(folder, asset)) fail(said("assets 写了 {0},块的文件夹里没有这个文件", asset));
219
386
  }
220
387
  return { kit: assets.filter((asset) => KIT_ASSETS.has(asset)), own };
221
388
  }
@@ -230,12 +397,12 @@ function packageOf(id, folder, text) {
230
397
  fail(said("component.json 不是 JSON:{0}", error.message));
231
398
  }
232
399
  if (!isTable(manifest)) fail(said("component.json 写的不是一个对象"));
233
- if (!PACKAGE_ID.test(id)) fail(said("{0} 不能做组件包的 id:范围和名字都只用小写字母、数字和连字符", id));
234
- for (const name of id.split("/")) if (DEVICE_NAMES.has(name)) fail(said("{0} 不能做组件包的 id:{1} 是 Windows 留给设备的名字", id, name));
400
+ if (!PACKAGE_ID.test(id)) fail(said("{0} 不能做自定义块的 id:范围和名字都只用小写字母、数字和连字符", id));
401
+ for (const name of id.split("/")) if (DEVICE_NAMES.has(name)) fail(said("{0} 不能做自定义块的 id:{1} 是 Windows 留给设备的名字", id, name));
235
402
  if (manifest.id !== id) fail(said("清单的 id 是 {0},和它的文件夹 {1} 不一样", String(manifest.id), id));
236
403
  required(manifest, ["apiVersion"]);
237
404
  if (!Number.isInteger(manifest.apiVersion) || manifest.apiVersion < 1) fail(said("apiVersion 要写成一个正整数"));
238
- if (manifest.apiVersion !== API_VERSION) throw refusal(said("这个组件包要 apiVersion {0},这一版 Gallery 只支持 {1}", manifest.apiVersion, API_VERSION), { disabled: true, version: isWords(manifest.version) ? manifest.version : null });
405
+ if (manifest.apiVersion !== API_VERSION) throw refusal(said("这个自定义块要 apiVersion {0},这一版 Gallery 只支持 {1}", manifest.apiVersion, API_VERSION), { disabled: true, version: isWords(manifest.version) ? manifest.version : null });
239
406
  onlyFields(manifest, FIELDS);
240
407
  required(manifest, ["version", "name", "description", "parts"]);
241
408
  if (!isWords(manifest.version)) fail(said("{0} 要写成文字", "version"));
@@ -248,12 +415,17 @@ function packageOf(id, folder, text) {
248
415
  const { kit, own } = assetsOf(manifest.assets, folder);
249
416
  if (manifest.markdown !== undefined && !MARKDOWN.includes(manifest.markdown)) fail(said("markdown 只能是 parts 或 text"));
250
417
  if (manifest.schema !== undefined && !(Number.isInteger(manifest.schema) && manifest.schema >= 1)) fail(said("schema 要写成一个正整数"));
418
+ const places = placesOf(manifest.places);
419
+ pageParts(places, manifest.parts);
420
+ const card = cardOf(manifest.card);
421
+ const settings = settingsOf(manifest.settings, manifest.parts);
422
+ const needs = needsOf(manifest.needs);
251
423
  const template = ownFile(folder, TEMPLATE);
252
424
  if (template) notTooLong(template, TEMPLATE);
253
425
  return {
254
426
  id,
255
427
  match: instanceMatch(id),
256
- parts: manifest.parts,
428
+ parts: partsRead(manifest.parts),
257
429
  ...(runtime ? { runtime } : {}),
258
430
  ...(manifest.schema !== undefined ? { schema: manifest.schema } : {}),
259
431
  apiVersion: manifest.apiVersion,
@@ -263,6 +435,10 @@ function packageOf(id, folder, text) {
263
435
  words: manifest.words ?? "",
264
436
  assets: kit,
265
437
  markdown: manifest.markdown ?? "parts",
438
+ places,
439
+ card,
440
+ settings,
441
+ needs,
266
442
  files: [...new Set([...SERVED.filter((file) => ownPath(folder, file)), ...own])],
267
443
  template: template && wordsOf(template),
268
444
  folder,
@@ -290,10 +466,20 @@ const workspaceRoot = () => findWorkspace().root;
290
466
 
291
467
  // A file edited by hand holds what it holds: of its lists, what can be an id is read, once each, and
292
468
  // the rest is passed over, one that names nothing there doing nothing. An id may be of a package that
293
- // comes back, so it stays. Only a package can be disabled.
469
+ // comes back, so it stays. Only a package can be disabled or unlisted.
294
470
  const NAMED = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)?$/;
295
471
  const idsIn = (list, shape) => [...new Set(Array.isArray(list) ? list.filter((id) => typeof id === "string" && shape.test(id)) : [])];
296
- const settingsIn = (file) => ({ hidden: idsIn(file?.components?.hidden, NAMED), disabled: idsIn(file?.components?.disabled, PACKAGE_ID) });
472
+ // `net`, the reader's consents (a block's card, 设置 › 组件列表): of each package by its id, the hosts of its
473
+ // needs it was allowed to reach, those that can be a needs' host (./net-policy.mjs HOST), once each; a
474
+ // package with none named none.
475
+ const consentsIn = (net) => Object.fromEntries(Object.entries(isTable(net) ? net : {}).filter(([id]) => PACKAGE_ID.test(id)).map(([id, hosts]) => [id, idsIn(hosts, HOST)]).filter(([, hosts]) => hosts.length > 0));
476
+ // `packs`, the packs' switches (设置 › 组件列表's pack cards): { "<pack id>": { "enabled": false } }, by id, a
477
+ // pack it names none of on; what is no pack's id or says no boolean is passed over.
478
+ const switchesIn = (packs) => Object.fromEntries(Object.entries(isTable(packs) ? packs : {}).filter(([id, entry]) => PACK_ID.test(id) && typeof entry?.enabled === "boolean").map(([id, entry]) => [id, entry.enabled]));
479
+ const settingsIn = (file) => {
480
+ const packs = switchesIn(file?.components?.packs);
481
+ return { hidden: idsIn(file?.components?.hidden, NAMED), disabled: idsIn(file?.components?.disabled, PACKAGE_ID), shown: idsIn(file?.components?.shown, PACKAGE_ID), unlisted: idsIn(file?.components?.unlisted, PACKAGE_ID), net: consentsIn(file?.components?.net), packs, packsOff: Object.keys(packs).filter((id) => packs[id] === false).sort() };
482
+ };
297
483
 
298
484
  // gallery.json's text (null: no file) as what it holds, `value`, or `problem`, why it cannot be taken
299
485
  // for a workspace's: what the catalog goes on through (settingsNow) and no writer writes over.
@@ -314,8 +500,11 @@ function fileAt(root) {
314
500
  }
315
501
 
316
502
  /**
317
- * What gallery.json says of the components: { hidden, disabled }, ids of packages and, in `hidden`,
318
- * names of built-in components. One that is not there says nothing, and neither does one that does not
503
+ * What gallery.json says of the components: { hidden, disabled, shown, unlisted, net, packs, packsOff }, ids of packages and, in
504
+ * `hidden`, names of built-in components (inDocumentMenus reads `hidden`, `shown` and `unlisted`; a block
505
+ * unlisted, 设置 › 组件列表's master switch off, is offered by no menu and no library); `net` the hosts
506
+ * each package was allowed to reach, by its id (consentsIn); `packs` each pack's switch named there, by its id (true on,
507
+ * false off), `packsOff` the ids of those off. One that is not there says nothing, and neither does one that does not
319
508
  * read or is another program's: what goes on using a workspace goes by the catalog (readCatalog).
320
509
  */
321
510
  export const componentSettings = (root = workspaceRoot()) => settingsIn(fileAt(root).value);
@@ -324,12 +513,17 @@ export const componentSettings = (root = workspaceRoot()) => settingsIn(fileAt(r
324
513
  const NOT_WRITABLE = ["EPERM", "EACCES", "EBUSY", "EROFS"];
325
514
 
326
515
  /**
327
- * gallery.json with `id` hidden or not and disabled or not, as `hidden` and `disabled` say (true,
328
- * false, or left out for as it is), written at once and with all else it holds kept. A file that does
516
+ * gallery.json with the pack `pack` on or off, as `enabled` says (components.packs, its entry's other fields
517
+ * kept), or with `id` hidden or not, shown or not, disabled or not and unlisted or not, as `hidden`,
518
+ * `shown`, `disabled` and `unlisted` say (true, false, or left out for as it is), written at once and with
519
+ * all else it holds kept; `shown` and `unlisted` are written where they hold an id or were there already
520
+ * (a block no entry names is listed). `net`, the hosts `id` is allowed
521
+ * to reach (a list: its consent, all it names in place of what was there; [] takes it back), written in
522
+ * components.net under its id, which goes with its last host, and components.net with its last id. A file that does
329
523
  * not read, is another program's, changed under this write or cannot be written is refused, and stays
330
524
  * as it is: it is not this write's to mend.
331
525
  */
332
- export async function writeComponentSettings(root, { id, hidden, disabled }) {
526
+ export async function writeComponentSettings(root, { id, hidden, shown, disabled, unlisted, net, pack, enabled }) {
333
527
  const file = layoutOf(root).marker;
334
528
  const text = await fs.readFile(file, "utf8").catch((error) => {
335
529
  if (error.code === "ENOENT") return null;
@@ -338,8 +532,26 @@ export async function writeComponentSettings(root, { id, hidden, disabled }) {
338
532
  const { value, problem } = fileOf(text);
339
533
  if (problem) fail(problem);
340
534
  const named = (list, on) => (on === undefined ? list : on ? [...new Set([...list, id])] : list.filter((other) => other !== id));
341
- const { hidden: wasHidden, disabled: wasDisabled } = settingsIn(value);
342
- const components = { ...(isTable(value?.components) ? value.components : {}), hidden: named(wasHidden, hidden), disabled: named(wasDisabled, disabled) };
535
+ const { hidden: wasHidden, disabled: wasDisabled, shown: wasShown, unlisted: wasUnlisted } = settingsIn(value);
536
+ const before = isTable(value?.components) ? value.components : {};
537
+ const nowShown = named(wasShown, shown);
538
+ const nowUnlisted = named(wasUnlisted, unlisted);
539
+ const components = {
540
+ ...before, hidden: named(wasHidden, hidden), disabled: named(wasDisabled, disabled),
541
+ ...(nowShown.length > 0 || Object.hasOwn(before, "shown") ? { shown: nowShown } : {}),
542
+ ...(nowUnlisted.length > 0 || Object.hasOwn(before, "unlisted") ? { unlisted: nowUnlisted } : {}),
543
+ };
544
+ if (pack !== undefined) {
545
+ const switches = isTable(before.packs) ? before.packs : {};
546
+ components.packs = { ...switches, [pack]: { ...(isTable(switches[pack]) ? switches[pack] : {}), enabled: enabled === true } };
547
+ }
548
+ if (net !== undefined) {
549
+ const consents = { ...(isTable(before.net) ? before.net : {}) };
550
+ if (net.length > 0) consents[id] = [...new Set(net)];
551
+ else delete consents[id];
552
+ if (Object.keys(consents).length > 0) components.net = consents;
553
+ else delete components.net;
554
+ }
343
555
  const written = await writeAtomic(file, `${JSON.stringify({ ...(value ?? WORKSPACE_MARK), components }, null, 2)}\n`, { expect: text }).catch((error) => {
344
556
  if (NOT_WRITABLE.includes(error.code)) fail(said("gallery.json 写不进去:可能是只读的,或被别的程序占着"));
345
557
  throw error;
@@ -353,31 +565,60 @@ const turnedOff = (found) => ({ ...found, off: true, disabled: true, problem: sa
353
565
 
354
566
  const NO_SETTINGS = settingsIn(null);
355
567
 
356
- /** The stamp of a catalog: of what its packages are, those that do not load and why too, of what is hidden and of why gallery.json does not read, neither of which changes a package. */
357
- export const catalogStamp = ({ packages, problems, settings = NO_SETTINGS }) => hashOf((hash) => {
568
+ /** The stamp of a catalog: of what its packages are, those that do not load and why too, of what is hidden, shown and unlisted, the packs off and their names, and of why gallery.json does not read, none of which changes a package. */
569
+ export const catalogStamp = ({ packages, problems, settings = NO_SETTINGS, packs = new Map() }) => hashOf((hash) => {
358
570
  for (const { id, stamp } of [...packages.values(), ...problems.values()].sort(byId)) hash.update(`${id} ${stamp}\n`);
359
571
  hash.update(`hidden ${[...settings.hidden].sort().join(" ")}\n`);
572
+ hash.update(`shown ${[...settings.shown].sort().join(" ")}\n`);
573
+ hash.update(`unlisted ${[...(settings.unlisted ?? [])].sort().join(" ")}\n`);
574
+ hash.update(`net ${consentPairs(settings).join(" ")}\n`);
575
+ hash.update(`packs off ${(settings.packsOff ?? []).join(" ")}\n`);
576
+ for (const [scope, name] of [...packs].sort(([a], [b]) => (a < b ? -1 : 1))) hash.update(`pack ${scope} ${JSON.stringify(name)}\n`);
360
577
  hash.update(`settings ${settings.problem?.message ?? ""}\n`);
361
578
  });
362
579
 
580
+ // The consents of `settings` as "<id>@<host>", sorted: what the stamp and the settings line say of them.
581
+ const consentPairs = ({ net = {} }) => Object.entries(net).flatMap(([id, hosts]) => hosts.map((host) => `${id}@${host}`)).sort();
582
+
363
583
  // The settings a catalog goes by: what gallery.json says or, while it cannot be read as saying
364
584
  // anything, what was in use (`held`, a broken manifest's hold, for a slip by hand), with why (`problem`).
365
585
  function settingsNow(root, held) {
366
586
  const { value, problem } = fileAt(root);
367
- return problem ? { hidden: held.hidden, disabled: held.disabled, problem } : settingsIn(value);
587
+ return problem ? { hidden: held.hidden, disabled: held.disabled, shown: held.shown ?? [], unlisted: held.unlisted ?? [], net: held.net ?? {}, packs: held.packs ?? {}, packsOff: held.packsOff ?? [], problem } : settingsIn(value);
368
588
  }
369
589
 
590
+ // What runs before the catalog reads the blocks of a workspace that holds Gallery's own (the scopes of
591
+ // Gallery's packs, `ownScopes`): (root, ids) -> anything. A server sets it as it starts
592
+ // (shared/shipped-blocks.mjs followOnRead: a copy as it was added brought up to Gallery's first), so that
593
+ // this file imports nothing of the package's paths, reading whichever workspace it is given; a command sets none.
594
+ let beforeRead = null;
595
+ let ownScopes = [];
596
+
597
+ /** `run(root, ids)` runs before each read of a workspace's blocks that finds Gallery's own (`ids`, those of `scopes`, the scopes of Gallery's packs), from now on in this process. */
598
+ export const beforeCatalogRead = (run, scopes = ["gallery"]) => {
599
+ beforeRead = run;
600
+ ownScopes = [...scopes];
601
+ };
602
+
370
603
  /**
371
604
  * The workspace's packages as they are on disk now: the catalog, whatever a package or gallery.json
372
605
  * holds. `held`: the settings of the catalog in use, for a file that does not read; a process with
373
606
  * none has nothing to go by. `on`: a package read as its files say whatever gallery.json says of it,
374
- * for what holds it to them (gallery component check).
607
+ * for what holds it to them (gallery block check). In a server, Gallery's own blocks as they were
608
+ * added are brought up to Gallery's first (beforeCatalogRead).
375
609
  */
376
610
  export function readCatalog(root = workspaceRoot(), held = NO_SETTINGS, { on = null } = {}) {
377
611
  const settings = settingsNow(root, held);
378
612
  const packages = new Map();
379
613
  const problems = new Map();
380
- for (const { id, folder } of packageFolders(root)) {
614
+ const folders = packageFolders(root);
615
+ const own = folders.map(({ id }) => id).filter((id) => ownScopes.includes(id.split("/")[0]));
616
+ if (beforeRead && own.length > 0) {
617
+ try {
618
+ beforeRead(root, own);
619
+ } catch {} // what it could not do leaves the blocks as they are
620
+ }
621
+ for (const { id, folder } of folders) {
381
622
  const found = readPackage(id, folder);
382
623
  (found.problem ? problems : packages).set(found.id, found);
383
624
  }
@@ -387,7 +628,30 @@ export function readCatalog(root = workspaceRoot(), held = NO_SETTINGS, { on = n
387
628
  problems.set(id, turnedOff(packages.get(id)));
388
629
  packages.delete(id);
389
630
  }
390
- return { packages, problems, settings, stamp: catalogStamp({ packages, problems, settings }) };
631
+ const packs = packNames(root, folders.map(({ id }) => id.split("/")[0]));
632
+ return { packages, problems, settings, packs, stamp: catalogStamp({ packages, problems, settings, packs }) };
633
+ }
634
+
635
+ /**
636
+ * What the pack.json of each scope of `scopes` (and of each folder of components/ that holds one) names
637
+ * it: a Map, by scope, of { zh, en }, or null where it has none or one that does not read (a scope of
638
+ * blocks with no pack.json is a pack of its own by its scope's name: ./block-packs.mjs).
639
+ */
640
+ export function packNames(root, scopes = []) {
641
+ const { components } = layoutOf(root);
642
+ let withFile = [];
643
+ try {
644
+ withFile = readdirSync(components).filter((name) => PACK_ID.test(name) && existsSync(path.join(components, name, PACK_FILE)));
645
+ } catch {}
646
+ const names = new Map();
647
+ for (const scope of [...new Set([...scopes, ...withFile])].sort()) {
648
+ let name = null;
649
+ try {
650
+ name = readPackManifest(readFileSync(path.join(components, scope, PACK_FILE), "utf8")).name;
651
+ } catch {}
652
+ names.set(scope, name);
653
+ }
654
+ return names;
391
655
  }
392
656
 
393
657
  const kept = new Map(); // a workspace's root -> the catalog in use
@@ -411,15 +675,21 @@ export const problemIn = (language, problem) => inLanguage(language, () => told(
411
675
  // Why a package does not load, in both languages, as a page and a list say it.
412
676
  const bothProblems = (problem) => ({ zh: problemIn("zh", problem), en: problemIn("en", problem) });
413
677
 
414
- /** Why gallery.json cannot be read, in both languages, as 设置 › 组件 says it, or null. */
678
+ /** Why gallery.json cannot be read, in both languages, as 设置 › 组件列表 says it, or null. */
415
679
  export const settingsProblem = ({ settings }) => (settings.problem ? bothProblems(settings.problem) : null);
416
680
 
417
- // What a package is now: "normal", "hidden", "disabled" (turned off, or for an apiVersion this Gallery
418
- // does not run), or "broken", one that does not load of itself.
419
- const stateOf = (found, settings) => (found.problem ? (found.disabled ? "disabled" : "broken") : settingState(found.id, settings));
681
+ // What a package is now: "normal" (the document's menus offer it: inDocumentMenus), "hidden" (they do
682
+ // not), "disabled" (turned off, or for an apiVersion this Gallery does not run), or "broken", one that
683
+ // does not load of itself.
684
+ const stateOf = (found, settings) => (found.problem ? (found.disabled ? "disabled" : "broken") : settingState(found.id, settings, found.places));
420
685
 
421
- /** What the kit is told of a package: where its instances are, its parts, what it leaves on a page and how far its content's shape goes. */
422
- export const descriptionOf = ({ id, match, parts, runtime, schema }) => ({ id, match, parts, ...(runtime ? { runtime } : {}), ...(schema !== undefined ? { schema } : {}) });
686
+ /**
687
+ * What the kit is told of a package: where its instances are, its parts, what it leaves on a page, how far
688
+ * its content's shape goes, and what it uses (`needs`, where it names any: the kit gives an instance
689
+ * context.workspace for "workspace", context.fetch for a host of the net and context.table() for
690
+ * "tables").
691
+ */
692
+ export const descriptionOf = ({ id, match, parts, runtime, schema, needs }) => ({ id, match, parts, ...(runtime ? { runtime } : {}), ...(schema !== undefined ? { schema } : {}), ...(needs?.length ? { needs } : {}) });
423
693
 
424
694
  /**
425
695
  * What the kit is told of a catalog, each by DocKit.describe, the one way a page learns a package's
@@ -494,18 +764,18 @@ export function instanceMarkup(text, id) {
494
764
  function readTemplate({ id, template, parts }) {
495
765
  if (!template) return {};
496
766
  const read = instanceMarkup(template, id);
497
- if (!read) return { problem: said("template.html 要是这个组件包的一个实例,有自己的结束标签,此外什么都没有") };
767
+ if (!read) return { problem: said("template.html 要是这个自定义块的一个实例,有自己的结束标签,此外什么都没有") };
498
768
  const held = [];
499
769
  walk({ childNodes: [read.root] }, (node) => { if (refusedInCopy(node.tagName, node.attrs)) held.push(node.tagName); });
500
770
  if (held.length > 0) return { problem: said("template.html 里的 <{0}> 进不了新建的块:脚本、事件、样式和 Gallery 自己的标记都不写在模板里", held[0]) };
501
771
  const short = Object.entries(parts).find(([, part]) => {
502
772
  const count = partOf(read.root, part.at).length;
503
- return count < (part.optional ? 0 : part.min ?? 1) || count > (part.max ?? Infinity);
773
+ return count < (part.optional ? 0 : part.min ?? 1) || count > (part.edit === "flow" ? 1 : part.max ?? Infinity);
504
774
  });
505
775
  return short ? { problem: said("template.html 的 {0} 和清单写的数目不符", short[0]) } : { markup: read.markup };
506
776
  }
507
777
 
508
- /** Why no menu offers the template of `found`, said()'s, for `gallery component check` to say, or null: it has none, or one no reason keeps out. */
778
+ /** Why no menu offers the template of `found`, said()'s, for `gallery block check` to say, or null: it has none, or one no reason keeps out. */
509
779
  export const templateProblem = (found) => readTemplate(found).problem ?? null;
510
780
 
511
781
  /** The markup of an instance of `found` as a new one is written, or null: it has no template a menu offers. */
@@ -515,13 +785,15 @@ export const templateOf = (found) => readTemplate(found).markup ?? null;
515
785
  * What the page the editor runs on holds of a catalog: its MAP's `components` (src/editor/service.mjs),
516
786
  * no route and no dynamic import. One entry for each package that loads, by id: { id, version, name:
517
787
  * { zh, en }, description: { zh, en }, words, definition, stamp, template (the markup "/" puts in: one
518
- * instance of it, or null), preview (the address of its picture, or null), state ("normal", or "hidden":
519
- * a menu offers what is "normal" and reads no setting), markdown, assets (the kit's files it asks for,
788
+ * instance of it, or null), preview (the address of its picture, or null), state ("normal", or "hidden": a menu offers what is "normal" and reads no setting), places (which the page's
789
+ * settingState reads with gallery.json's lists as its event stream says them), needs, markdown, assets (the kit's files it asks for,
520
790
  * by id), tags (packageTags: what loads it in a page that was served without it, the line that tells the
521
- * kit of its parts among them) }. A page knows what it holds of a package's parts from the kit
791
+ * kit of its parts among them), pack ({ id, name }: the pack it is of, its scope, and what its pack.json names
792
+ * it, null where none does: a menu writes the pack's name after a block's in grey where two packs offer
793
+ * blocks of one name) }. A page knows what it holds of a package's parts from the kit
522
794
  * (descriptionsOf), and of one that came after it was served from these tags.
523
795
  */
524
- export const menuOf = ({ packages, settings = NO_SETTINGS }) => [...packages.values()].sort(byId).map((found) => ({
796
+ export const menuOf = ({ packages, settings = NO_SETTINGS, packs = new Map() }) => [...packages.values()].sort(byId).map((found) => ({
525
797
  id: found.id,
526
798
  version: found.version,
527
799
  name: found.name,
@@ -532,28 +804,47 @@ export const menuOf = ({ packages, settings = NO_SETTINGS }) => [...packages.val
532
804
  template: templateOf(found),
533
805
  preview: found.files.includes("preview.svg") ? packageAddress(found, "preview.svg") : null,
534
806
  state: stateOf(found, settings),
807
+ places: found.places,
808
+ needs: found.needs,
535
809
  markdown: found.markdown,
536
810
  assets: found.assets,
537
811
  tags: packageTags(found),
812
+ pack: { id: found.id.split("/")[0], name: packs.get(found.id.split("/")[0]) ?? null },
538
813
  }));
539
814
 
540
815
  /**
541
816
  * The workspace's packages as who lists them says them, by id: { id, version, name, description (what
542
817
  * a manifest says, null where it does not read), state ("normal", "hidden", "disabled" or "broken":
543
818
  * stateOf), reason ({ zh, en }, why it does not load of itself, or null), template (whether it has one
544
- * a menu can put in: with none, none can), hidden and disabled (what gallery.json says of it) }.
819
+ * a menu can put in: with none, none can), places and needs (what its manifest says, null where it does
820
+ * not read), hidden, shown, disabled and unlisted (what gallery.json says of it), allowed (the hosts of its needs
821
+ * the reader allowed it to reach, gallery.json's components.net: its switch 允许联网 is on while it holds
822
+ * every host its needs name) and menu (whether its switch
823
+ * 在文档菜单里显示 is on: inDocumentMenus, whatever `disabled` and `unlisted` say, which are switches of their
824
+ * own: the row's master switch is on while it is not unlisted, nor its pack's) and pack (the pack it is of: its scope) }.
545
825
  */
546
826
  export const packageStates = ({ packages, problems, settings = NO_SETTINGS }) => [...packages.values(), ...problems.values()].sort(byId).map((found) => ({
547
827
  id: found.id,
828
+ pack: found.id.split("/")[0],
548
829
  version: found.version ?? null,
549
830
  name: found.name ?? null,
550
831
  description: found.description ?? null,
551
832
  state: stateOf(found, settings),
552
833
  reason: found.problem && !found.off ? bothProblems(found.problem) : null,
553
834
  template: Boolean(templateOf(found)),
835
+ places: found.places ?? null,
836
+ needs: found.needs ?? null,
554
837
  hidden: settings.hidden.includes(found.id),
838
+ shown: settings.shown.includes(found.id),
555
839
  disabled: settings.disabled.includes(found.id),
840
+ unlisted: (settings.unlisted ?? []).includes(found.id),
841
+ allowed: (settings.net?.[found.id] ?? []).filter((host) => netHostsOf(found.needs).includes(host)),
842
+ menu: inDocumentMenus(found.places, { ...settings, disabled: [], unlisted: [], packsOff: [] }, found.id),
556
843
  }));
557
844
 
558
- /** The message that tells a page of `settings` on its event stream, its two lists by commas: what it offers follows them, whatever it holds. */
559
- export const settingsLine = ({ hidden, disabled }) => `settings ${hidden.join(",")} ${disabled.join(",")}`;
845
+ /**
846
+ * The message that tells a page of `settings` on its event stream, its six lists by commas (hidden,
847
+ * disabled, shown, the consents, "<id>@<host>" each, unlisted, and the packs off): what it offers follows
848
+ * them, whatever it holds, and an instance whose consent changed starts again (the kit's net.js).
849
+ */
850
+ export const settingsLine = (settings) => `settings ${settings.hidden.join(",")} ${settings.disabled.join(",")} ${settings.shown.join(",")} ${consentPairs(settings).join(",")} ${(settings.unlisted ?? []).join(",")} ${(settings.packsOff ?? []).join(",")}`;