@heycys/gallery 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/THIRD-PARTY-NOTICES.md +2 -2
  2. package/browser/editor/components/align.mjs +58 -0
  3. package/browser/editor/components/code/index.mjs +1 -1
  4. package/browser/editor/components/columns/page.mjs +4 -4
  5. package/browser/editor/components/embed/index.mjs +2 -2
  6. package/browser/editor/components/embed/page.mjs +1 -1
  7. package/browser/editor/components/file/page.mjs +4 -4
  8. package/browser/editor/components/flowchart/add.mjs +7 -6
  9. package/browser/editor/components/flowchart/page.mjs +1 -1
  10. package/browser/editor/components/heading/index.mjs +7 -6
  11. package/browser/editor/components/icons.mjs +11 -0
  12. package/browser/editor/components/image/crop.mjs +6 -9
  13. package/browser/editor/components/image/index.mjs +2 -0
  14. package/browser/editor/components/image/page.mjs +172 -90
  15. package/browser/editor/components/installed/index.mjs +15 -4
  16. package/browser/editor/components/installed/page.mjs +103 -25
  17. package/browser/editor/components/installed/served.mjs +6 -5
  18. package/browser/editor/components/instances.mjs +18 -0
  19. package/browser/editor/components/list/page.mjs +3 -6
  20. package/browser/editor/components/media/icons.mjs +13 -8
  21. package/browser/editor/components/media/page.mjs +5 -3
  22. package/browser/editor/components/media/toolbar.mjs +12 -3
  23. package/browser/editor/components/paragraph/index.mjs +4 -3
  24. package/browser/editor/components/registry.mjs +30 -9
  25. package/browser/editor/components/table/index.mjs +61 -2
  26. package/browser/editor/components/table/page.mjs +1 -1
  27. package/browser/editor/page/after/index.mjs +288 -0
  28. package/browser/editor/page/align.mjs +91 -0
  29. package/browser/editor/page/annotate/card.mjs +3 -6
  30. package/browser/editor/page/autosave.mjs +310 -37
  31. package/browser/editor/page/beside.mjs +5 -6
  32. package/browser/editor/page/block-menu/index.mjs +6 -3
  33. package/browser/editor/page/blocks.mjs +3 -1
  34. package/browser/editor/page/board/client.mjs +57 -0
  35. package/browser/editor/page/board/hint.mjs +182 -0
  36. package/browser/editor/page/board/history.mjs +119 -0
  37. package/browser/editor/page/board/index.mjs +1059 -0
  38. package/browser/editor/page/board/motion.mjs +177 -0
  39. package/browser/editor/page/board/settings.mjs +210 -0
  40. package/browser/editor/page/caret.mjs +1 -1
  41. package/browser/editor/page/changes/bar.mjs +6 -14
  42. package/browser/editor/page/changes/index.mjs +2 -2
  43. package/browser/editor/page/clipboard/import/html.mjs +29 -6
  44. package/browser/editor/page/clipboard/import/inline.mjs +32 -0
  45. package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
  46. package/browser/editor/page/clipboard/import/read.mjs +6 -1
  47. package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
  48. package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
  49. package/browser/editor/page/clipboard/index.mjs +133 -15
  50. package/browser/editor/page/clipboard/own/blocks.mjs +13 -14
  51. package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
  52. package/browser/editor/page/clipboard/own/describe.mjs +6 -2
  53. package/browser/editor/page/clipboard/own/selection.mjs +1 -1
  54. package/browser/editor/page/columns/index.mjs +48 -46
  55. package/browser/editor/page/commit.mjs +80 -22
  56. package/browser/editor/page/conflicts.mjs +7 -7
  57. package/browser/editor/page/core.mjs +34 -16
  58. package/browser/editor/page/custom-page.mjs +110 -0
  59. package/browser/editor/page/editor.mjs +71 -17
  60. package/browser/editor/page/emoji-picker/styles.mjs +7 -12
  61. package/browser/editor/page/emoji-picker/upload.mjs +2 -2
  62. package/browser/editor/page/handle/index.mjs +5 -8
  63. package/browser/editor/page/history.mjs +41 -6
  64. package/browser/editor/page/insert/index.mjs +20 -22
  65. package/browser/editor/page/links/index.mjs +5 -3
  66. package/browser/editor/page/links/pages.mjs +27 -9
  67. package/browser/editor/page/look.mjs +54 -3
  68. package/browser/editor/page/margin.mjs +18 -3
  69. package/browser/editor/page/media-menu/index.mjs +6 -7
  70. package/browser/editor/page/menu.mjs +33 -14
  71. package/browser/editor/page/notices.mjs +2 -2
  72. package/browser/editor/page/page-objects.mjs +9 -2
  73. package/browser/editor/page/panel.mjs +4 -9
  74. package/browser/editor/page/present-links.mjs +15 -5
  75. package/browser/editor/page/present-shell.mjs +63 -0
  76. package/browser/editor/page/resize/index.mjs +42 -29
  77. package/browser/editor/page/selection/index.mjs +10 -2
  78. package/browser/editor/page/served.mjs +14 -0
  79. package/browser/editor/page/session.mjs +48 -4
  80. package/browser/editor/page/slash-menu/index.mjs +3 -3
  81. package/browser/editor/page/table/binding.mjs +87 -0
  82. package/browser/editor/page/table/cells.mjs +102 -46
  83. package/browser/editor/page/table/index.mjs +24 -9
  84. package/browser/editor/page/table/pictures.mjs +299 -0
  85. package/browser/editor/page/table/views.mjs +103 -0
  86. package/browser/editor/page/table/widths.mjs +82 -10
  87. package/browser/editor/page/text-select/index.mjs +26 -12
  88. package/browser/editor/page/todo-writes.mjs +75 -0
  89. package/browser/editor/page/typing/index.mjs +97 -15
  90. package/browser/editor/page/typing/leave.mjs +19 -5
  91. package/browser/editor/page/ui.mjs +13 -0
  92. package/browser/shared/block-packs.mjs +419 -0
  93. package/browser/shared/board-places.mjs +224 -0
  94. package/browser/shared/card-size.mjs +59 -0
  95. package/browser/shared/colors.mjs +3 -2
  96. package/browser/shared/component-catalog.mjs +330 -46
  97. package/browser/shared/component-source.mjs +39 -0
  98. package/browser/shared/components/heading.mjs +2 -1
  99. package/browser/shared/components/index.mjs +12 -3
  100. package/browser/shared/components/installed.mjs +35 -3
  101. package/browser/shared/components/page.mjs +20 -8
  102. package/browser/shared/components/paragraph.mjs +8 -1
  103. package/browser/shared/components/state.mjs +5 -1
  104. package/browser/shared/components/table.mjs +1 -0
  105. package/browser/shared/document-look.mjs +10 -5
  106. package/browser/shared/drafts.mjs +432 -0
  107. package/browser/shared/emoji/line-icons.mjs +37 -0
  108. package/browser/shared/extension-sidebar.mjs +126 -0
  109. package/browser/shared/extensions.mjs +79 -0
  110. package/browser/shared/file-table.mjs +101 -0
  111. package/browser/shared/http.mjs +9 -0
  112. package/browser/shared/i18n/en.mjs +330 -27
  113. package/browser/shared/json-layout.mjs +47 -0
  114. package/browser/shared/net-cache.mjs +133 -0
  115. package/browser/shared/net-keys.mjs +32 -0
  116. package/browser/shared/net-policy.mjs +177 -0
  117. package/browser/shared/outline-shape.mjs +128 -0
  118. package/browser/shared/outline.mjs +359 -67
  119. package/browser/shared/pack-manifest.mjs +88 -0
  120. package/browser/shared/page-address.mjs +6 -3
  121. package/browser/shared/page-cards.mjs +308 -0
  122. package/browser/shared/page-links.mjs +69 -6
  123. package/browser/shared/pages.mjs +500 -0
  124. package/browser/shared/paths.mjs +6 -0
  125. package/browser/shared/pinyin.mjs +92 -0
  126. package/browser/shared/present-step.mjs +17 -0
  127. package/browser/shared/preview-server.mjs +3 -7
  128. package/browser/shared/session.mjs +8 -1
  129. package/browser/shared/shipped-blocks.mjs +299 -0
  130. package/browser/shared/sidebar-head.mjs +70 -0
  131. package/browser/shared/table-chart.mjs +33 -1
  132. package/browser/shared/todos.mjs +83 -0
  133. package/browser/shared/user-directory.mjs +24 -0
  134. package/browser/shared/versions.mjs +14 -0
  135. package/browser/shared/web-links.mjs +139 -0
  136. package/browser/shared/workspace-columns.mjs +120 -0
  137. package/browser/shared/workspace-name.mjs +154 -0
  138. package/browser/shared/workspace-query.mjs +125 -0
  139. package/browser/shared/workspace-registry.mjs +272 -0
  140. package/browser/shared/workspace.mjs +171 -20
  141. package/dist/chunk-2OHANJ6Q.mjs +2 -0
  142. package/dist/chunk-3MBRL2PM.mjs +16 -0
  143. package/dist/chunk-4OKWZY5E.mjs +2 -0
  144. package/dist/chunk-4OZWXZE5.mjs +5 -0
  145. package/dist/chunk-5K44HLTZ.mjs +2 -0
  146. package/dist/{chunk-5ZKJ3H4P.mjs → chunk-5XA2ZBAE.mjs} +3 -3
  147. package/dist/{chunk-4FS7Z7WR.mjs → chunk-67N2QJYT.mjs} +1 -1
  148. package/dist/chunk-6KBYRA2C.mjs +2 -0
  149. package/dist/chunk-6N3PX4XX.mjs +8 -0
  150. package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
  151. package/dist/chunk-7JDX4E65.mjs +13 -0
  152. package/dist/chunk-7M2BQQGZ.mjs +2 -0
  153. package/dist/chunk-7OG26PGF.mjs +2 -0
  154. package/dist/chunk-AHN3NFKJ.mjs +3 -0
  155. package/dist/chunk-ALZ6ZENZ.mjs +3 -0
  156. package/dist/chunk-AR5D57ZD.mjs +170 -0
  157. package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
  158. package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
  159. package/dist/chunk-CMA72MAG.mjs +2 -0
  160. package/dist/{chunk-LCTHTZIL.mjs → chunk-DAZL73VK.mjs} +91 -91
  161. package/dist/{chunk-Y6RLRPG3.mjs → chunk-DJISHPUL.mjs} +1 -1
  162. package/dist/chunk-DR73Y2UJ.mjs +3 -0
  163. package/dist/{chunk-FDLXYYG4.mjs → chunk-EAIFITAV.mjs} +1 -1
  164. package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
  165. package/dist/chunk-EP25HDAJ.mjs +2 -0
  166. package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
  167. package/dist/chunk-EURLTOTZ.mjs +4 -0
  168. package/dist/chunk-F4ZA6JRZ.mjs +3 -0
  169. package/dist/chunk-F6HVDZR4.mjs +2 -0
  170. package/dist/chunk-FDMAXMQA.mjs +2 -0
  171. package/dist/chunk-FETSREUN.mjs +5 -0
  172. package/dist/chunk-GHOYKMQ3.mjs +4 -0
  173. package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
  174. package/dist/chunk-HDWTZ763.mjs +12 -0
  175. package/dist/chunk-HPOI4ZX7.mjs +6 -0
  176. package/dist/{chunk-BGGDRNYQ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
  177. package/dist/chunk-IA4PSIMD.mjs +2 -0
  178. package/dist/{chunk-E7LWZRXY.mjs → chunk-ITOH5EGO.mjs} +1 -1
  179. package/dist/chunk-IV64FFI4.mjs +2 -0
  180. package/dist/chunk-J43Z6LSS.mjs +2 -0
  181. package/dist/chunk-J5PUIV3Y.mjs +4 -0
  182. package/dist/chunk-JA7G53PC.mjs +2 -0
  183. package/dist/chunk-JEUK3ZO7.mjs +2 -0
  184. package/dist/chunk-JLTZF4LA.mjs +2 -0
  185. package/dist/chunk-JZT7I2OJ.mjs +2 -0
  186. package/dist/chunk-K5XATEOO.mjs +2 -0
  187. package/dist/chunk-L2SUIS5S.mjs +2 -0
  188. package/dist/chunk-L7UP52JO.mjs +2 -0
  189. package/dist/chunk-LM4XFQHZ.mjs +2 -0
  190. package/dist/chunk-MN7BZL4R.mjs +2 -0
  191. package/dist/chunk-MTYYHM2G.mjs +15 -0
  192. package/dist/chunk-MXUBKDRO.mjs +15 -0
  193. package/dist/chunk-NAMN3RAF.mjs +2 -0
  194. package/dist/{chunk-SYGN7GLU.mjs → chunk-OEXWRJME.mjs} +1 -1
  195. package/dist/chunk-OFHXGWOX.mjs +2 -0
  196. package/dist/chunk-ORID3AE7.mjs +166 -0
  197. package/dist/chunk-PAREWQGB.mjs +4 -0
  198. package/dist/chunk-PGDE24N5.mjs +2 -0
  199. package/dist/chunk-POTA2FV4.mjs +2 -0
  200. package/dist/chunk-PUOQ65NC.mjs +2 -0
  201. package/dist/chunk-Q4UEPUWF.mjs +2 -0
  202. package/dist/chunk-QE5RG76S.mjs +549 -0
  203. package/dist/chunk-R2ZDLJYT.mjs +10 -0
  204. package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
  205. package/dist/chunk-SWUTTZCN.mjs +2 -0
  206. package/dist/chunk-TANZRINB.mjs +2 -0
  207. package/dist/chunk-TR7TWGHB.mjs +2 -0
  208. package/dist/chunk-U5XL7D6A.mjs +3 -0
  209. package/dist/{chunk-XNROSH2T.mjs → chunk-U7NZI6N4.mjs} +2 -2
  210. package/dist/chunk-UGWMMEJ5.mjs +15 -0
  211. package/dist/chunk-UJQJKNGK.mjs +2 -0
  212. package/dist/chunk-UYVCQK3H.mjs +22 -0
  213. package/dist/chunk-VFX2KNHP.mjs +4 -0
  214. package/dist/chunk-VS7JIGJW.mjs +24 -0
  215. package/dist/chunk-W7665ZEC.mjs +6 -0
  216. package/dist/chunk-WQDQWCX5.mjs +2 -0
  217. package/dist/chunk-YI43RG4P.mjs +2 -0
  218. package/dist/chunk-YLPSMDIS.mjs +5 -0
  219. package/dist/chunk-YNAWBAYN.mjs +3 -0
  220. package/dist/chunk-YROX2O22.mjs +2 -0
  221. package/dist/chunk-ZDLPUOAG.mjs +6 -0
  222. package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
  223. package/dist/chunk-ZQS6H473.mjs +16 -0
  224. package/dist/chunk-ZTHICSSF.mjs +2 -0
  225. package/dist/chunk-ZWWC4KLF.mjs +20 -0
  226. package/dist/cli.mjs +1 -1
  227. package/dist/server.mjs +1 -1
  228. package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
  229. package/dist/worker.mjs +1 -1
  230. package/kit/board.css +95 -0
  231. package/kit/board.js +250 -0
  232. package/kit/charts.js +5 -71
  233. package/kit/code.js +17 -17
  234. package/kit/report.css +335 -18
  235. package/kit/runtime.js +1638 -125
  236. package/kit/state.js +10 -1
  237. package/kit/tables.js +130 -43
  238. package/kit/ui.js +79 -35
  239. package/package.json +2 -1
  240. package/packs/gallery/card/component.json +14 -0
  241. package/packs/gallery/card/example.html +10 -0
  242. package/packs/gallery/card/preview.svg +1 -0
  243. package/packs/gallery/card/style.css +6 -0
  244. package/packs/gallery/card/template.html +6 -0
  245. package/packs/gallery/card/writing.md +14 -0
  246. package/packs/gallery/chart-card/component.json +15 -0
  247. package/packs/gallery/chart-card/example.html +14 -0
  248. package/packs/gallery/chart-card/kit.js +130 -0
  249. package/packs/gallery/chart-card/preview.svg +1 -0
  250. package/packs/gallery/chart-card/style.css +42 -0
  251. package/packs/gallery/chart-card/template.html +13 -0
  252. package/packs/gallery/chart-card/writing.md +20 -0
  253. package/packs/gallery/chart-view/component.json +20 -0
  254. package/packs/gallery/chart-view/example.html +13 -0
  255. package/packs/gallery/chart-view/kit.js +355 -0
  256. package/packs/gallery/chart-view/preview.svg +1 -0
  257. package/packs/gallery/chart-view/style.css +94 -0
  258. package/packs/gallery/chart-view/template.html +3 -0
  259. package/packs/gallery/chart-view/writing.md +22 -0
  260. package/packs/gallery/countdown/component.json +18 -0
  261. package/packs/gallery/countdown/example.html +3 -0
  262. package/packs/gallery/countdown/kit.js +216 -0
  263. package/packs/gallery/countdown/preview.svg +1 -0
  264. package/packs/gallery/countdown/style.css +118 -0
  265. package/packs/gallery/countdown/template.html +3 -0
  266. package/packs/gallery/countdown/writing.md +22 -0
  267. package/packs/gallery/habit/component.json +14 -0
  268. package/packs/gallery/habit/example.html +3 -0
  269. package/packs/gallery/habit/kit.js +334 -0
  270. package/packs/gallery/habit/preview.svg +1 -0
  271. package/packs/gallery/habit/style.css +174 -0
  272. package/packs/gallery/habit/template.html +3 -0
  273. package/packs/gallery/habit/writing.md +21 -0
  274. package/packs/gallery/heatmap/component.json +18 -0
  275. package/packs/gallery/heatmap/example.html +3 -0
  276. package/packs/gallery/heatmap/kit.js +513 -0
  277. package/packs/gallery/heatmap/preview.svg +1 -0
  278. package/packs/gallery/heatmap/style.css +189 -0
  279. package/packs/gallery/heatmap/template.html +3 -0
  280. package/packs/gallery/heatmap/writing.md +21 -0
  281. package/packs/gallery/kanban/component.json +18 -0
  282. package/packs/gallery/kanban/example.html +12 -0
  283. package/packs/gallery/kanban/kit.js +540 -0
  284. package/packs/gallery/kanban/preview.svg +1 -0
  285. package/packs/gallery/kanban/style.css +212 -0
  286. package/packs/gallery/kanban/template.html +3 -0
  287. package/packs/gallery/kanban/writing.md +20 -0
  288. package/packs/gallery/metric/component.json +22 -0
  289. package/packs/gallery/metric/example.html +15 -0
  290. package/packs/gallery/metric/kit.js +248 -0
  291. package/packs/gallery/metric/preview.svg +1 -0
  292. package/packs/gallery/metric/style.css +92 -0
  293. package/packs/gallery/metric/template.html +3 -0
  294. package/packs/gallery/metric/writing.md +23 -0
  295. package/packs/gallery/note/component.json +14 -0
  296. package/packs/gallery/note/example.html +3 -0
  297. package/packs/gallery/note/kit.js +129 -0
  298. package/packs/gallery/note/preview.svg +1 -0
  299. package/packs/gallery/note/style.css +97 -0
  300. package/packs/gallery/note/template.html +3 -0
  301. package/packs/gallery/note/writing.md +18 -0
  302. package/packs/gallery/pack.json +8 -0
  303. package/packs/gallery/recent/component.json +13 -0
  304. package/packs/gallery/recent/example.html +1 -0
  305. package/packs/gallery/recent/kit.js +293 -0
  306. package/packs/gallery/recent/preview.svg +1 -0
  307. package/packs/gallery/recent/style.css +85 -0
  308. package/packs/gallery/recent/template.html +1 -0
  309. package/packs/gallery/recent/writing.md +15 -0
  310. package/packs/gallery/static/component.json +13 -0
  311. package/packs/gallery/static/example.html +11 -0
  312. package/packs/gallery/static/preview.svg +1 -0
  313. package/packs/gallery/static/style.css +171 -0
  314. package/packs/gallery/static/template.html +1 -0
  315. package/packs/gallery/static/writing.md +18 -0
  316. package/packs/gallery/stats/component.json +13 -0
  317. package/packs/gallery/stats/example.html +1 -0
  318. package/packs/gallery/stats/kit.js +347 -0
  319. package/packs/gallery/stats/preview.svg +1 -0
  320. package/packs/gallery/stats/style.css +73 -0
  321. package/packs/gallery/stats/template.html +1 -0
  322. package/packs/gallery/stats/writing.md +11 -0
  323. package/packs/gallery/today/component.json +12 -0
  324. package/packs/gallery/today/example.html +1 -0
  325. package/packs/gallery/today/kit.js +220 -0
  326. package/packs/gallery/today/preview.svg +1 -0
  327. package/packs/gallery/today/style.css +149 -0
  328. package/packs/gallery/today/template.html +1 -0
  329. package/packs/gallery/today/writing.md +14 -0
  330. package/packs/gallery/todos/component.json +19 -0
  331. package/packs/gallery/todos/example.html +3 -0
  332. package/packs/gallery/todos/kit.js +489 -0
  333. package/packs/gallery/todos/preview.svg +1 -0
  334. package/packs/gallery/todos/style.css +214 -0
  335. package/packs/gallery/todos/template.html +3 -0
  336. package/packs/gallery/todos/writing.md +30 -0
  337. package/packs/gallery/weather/component.json +19 -0
  338. package/packs/gallery/weather/example.html +3 -0
  339. package/packs/gallery/weather/kit.js +387 -0
  340. package/packs/gallery/weather/preview.svg +1 -0
  341. package/packs/gallery/weather/style.css +140 -0
  342. package/packs/gallery/weather/template.html +3 -0
  343. package/packs/gallery/weather/writing.md +26 -0
  344. package/packs/gallery-soft/countdown/component.json +18 -0
  345. package/packs/gallery-soft/countdown/example.html +3 -0
  346. package/packs/gallery-soft/countdown/kit.js +206 -0
  347. package/packs/gallery-soft/countdown/preview.svg +1 -0
  348. package/packs/gallery-soft/countdown/style.css +211 -0
  349. package/packs/gallery-soft/countdown/template.html +3 -0
  350. package/packs/gallery-soft/countdown/writing.md +24 -0
  351. package/packs/gallery-soft/habit/component.json +14 -0
  352. package/packs/gallery-soft/habit/example.html +3 -0
  353. package/packs/gallery-soft/habit/kit.js +366 -0
  354. package/packs/gallery-soft/habit/preview.svg +1 -0
  355. package/packs/gallery-soft/habit/style.css +251 -0
  356. package/packs/gallery-soft/habit/template.html +3 -0
  357. package/packs/gallery-soft/habit/writing.md +23 -0
  358. package/packs/gallery-soft/heatmap/component.json +18 -0
  359. package/packs/gallery-soft/heatmap/example.html +3 -0
  360. package/packs/gallery-soft/heatmap/kit.js +498 -0
  361. package/packs/gallery-soft/heatmap/preview.svg +1 -0
  362. package/packs/gallery-soft/heatmap/style.css +181 -0
  363. package/packs/gallery-soft/heatmap/template.html +3 -0
  364. package/packs/gallery-soft/heatmap/writing.md +23 -0
  365. package/packs/gallery-soft/note/component.json +14 -0
  366. package/packs/gallery-soft/note/example.html +3 -0
  367. package/packs/gallery-soft/note/kit.js +162 -0
  368. package/packs/gallery-soft/note/preview.svg +1 -0
  369. package/packs/gallery-soft/note/style.css +148 -0
  370. package/packs/gallery-soft/note/template.html +3 -0
  371. package/packs/gallery-soft/note/writing.md +20 -0
  372. package/packs/gallery-soft/pack.json +8 -0
  373. package/packs/gallery-soft/recent/component.json +13 -0
  374. package/packs/gallery-soft/recent/example.html +1 -0
  375. package/packs/gallery-soft/recent/kit.js +335 -0
  376. package/packs/gallery-soft/recent/preview.svg +1 -0
  377. package/packs/gallery-soft/recent/style.css +160 -0
  378. package/packs/gallery-soft/recent/template.html +1 -0
  379. package/packs/gallery-soft/recent/writing.md +17 -0
  380. package/packs/gallery-soft/static/component.json +13 -0
  381. package/packs/gallery-soft/static/example.html +12 -0
  382. package/packs/gallery-soft/static/preview.svg +1 -0
  383. package/packs/gallery-soft/static/style.css +186 -0
  384. package/packs/gallery-soft/static/template.html +1 -0
  385. package/packs/gallery-soft/static/writing.md +20 -0
  386. package/packs/gallery-soft/stats/component.json +13 -0
  387. package/packs/gallery-soft/stats/example.html +1 -0
  388. package/packs/gallery-soft/stats/kit.js +341 -0
  389. package/packs/gallery-soft/stats/preview.svg +1 -0
  390. package/packs/gallery-soft/stats/style.css +119 -0
  391. package/packs/gallery-soft/stats/template.html +1 -0
  392. package/packs/gallery-soft/stats/writing.md +13 -0
  393. package/packs/gallery-soft/today/component.json +12 -0
  394. package/packs/gallery-soft/today/example.html +1 -0
  395. package/packs/gallery-soft/today/kit.js +222 -0
  396. package/packs/gallery-soft/today/preview.svg +1 -0
  397. package/packs/gallery-soft/today/style.css +178 -0
  398. package/packs/gallery-soft/today/template.html +1 -0
  399. package/packs/gallery-soft/today/writing.md +16 -0
  400. package/packs/gallery-soft/todos/component.json +19 -0
  401. package/packs/gallery-soft/todos/example.html +3 -0
  402. package/packs/gallery-soft/todos/kit.js +527 -0
  403. package/packs/gallery-soft/todos/preview.svg +1 -0
  404. package/packs/gallery-soft/todos/style.css +212 -0
  405. package/packs/gallery-soft/todos/template.html +3 -0
  406. package/packs/gallery-soft/todos/writing.md +32 -0
  407. package/packs/gallery-soft/weather/component.json +19 -0
  408. package/packs/gallery-soft/weather/example.html +3 -0
  409. package/packs/gallery-soft/weather/kit.js +411 -0
  410. package/packs/gallery-soft/weather/preview.svg +1 -0
  411. package/packs/gallery-soft/weather/style.css +185 -0
  412. package/packs/gallery-soft/weather/template.html +3 -0
  413. package/packs/gallery-soft/weather/writing.md +28 -0
  414. package/release.json +12 -10
  415. package/sdk/doc-kit.d.ts +396 -5
  416. package/sdk/doc-state.d.ts +2 -0
  417. package/skill/SKILL.md +56 -23
  418. package/skill/references/block-script.md +140 -0
  419. package/skill/references/building-blocks.md +257 -0
  420. package/skill/references/extensions.md +59 -0
  421. package/skill/references/pages-and-data.md +225 -0
  422. package/skill/references/writing-html.md +8 -4
  423. package/skill/sdk/doc-kit.d.ts +396 -5
  424. package/skill/sdk/doc-state.d.ts +2 -0
  425. package/web/assets/index-BM_K-Auu.css +1 -0
  426. package/web/assets/index-DRslOBiv.js +15 -0
  427. package/web/index.html +2 -2
  428. package/dist/chunk-3HIRPVLU.mjs +0 -2
  429. package/dist/chunk-5MHMDLZD.mjs +0 -4
  430. package/dist/chunk-64K7IPLO.mjs +0 -255
  431. package/dist/chunk-6QXGZM36.mjs +0 -8
  432. package/dist/chunk-6RGJOMJP.mjs +0 -16
  433. package/dist/chunk-AJ73XJF3.mjs +0 -2
  434. package/dist/chunk-AUZGSZJH.mjs +0 -13
  435. package/dist/chunk-BFE45AKL.mjs +0 -2
  436. package/dist/chunk-BXO2ZY5P.mjs +0 -2
  437. package/dist/chunk-CEH4MRCC.mjs +0 -2
  438. package/dist/chunk-CY2FNNAB.mjs +0 -16
  439. package/dist/chunk-DFUDV4LX.mjs +0 -2
  440. package/dist/chunk-DKXG4XDG.mjs +0 -2
  441. package/dist/chunk-EZQ34M3F.mjs +0 -124
  442. package/dist/chunk-EZY3WC6Z.mjs +0 -15
  443. package/dist/chunk-FUKQNOQS.mjs +0 -9
  444. package/dist/chunk-JPOSLGOC.mjs +0 -6
  445. package/dist/chunk-JVHHHGEA.mjs +0 -3
  446. package/dist/chunk-L3QKH4OK.mjs +0 -26
  447. package/dist/chunk-LAE5NQ4B.mjs +0 -2
  448. package/dist/chunk-LEF6DRCK.mjs +0 -59
  449. package/dist/chunk-NO5NJCHO.mjs +0 -2
  450. package/dist/chunk-NQPVAU63.mjs +0 -2
  451. package/dist/chunk-NSKUTQ5J.mjs +0 -5
  452. package/dist/chunk-O35FIXRB.mjs +0 -2
  453. package/dist/chunk-P2CAZPDP.mjs +0 -3
  454. package/dist/chunk-PL6GHLGD.mjs +0 -15
  455. package/dist/chunk-QJIR2BLP.mjs +0 -6
  456. package/dist/chunk-QUPLVPB2.mjs +0 -7
  457. package/dist/chunk-R7BNENLY.mjs +0 -4
  458. package/dist/chunk-S6O4UHLC.mjs +0 -2
  459. package/dist/chunk-SGJWQKEI.mjs +0 -2
  460. package/dist/chunk-TX6JH7TR.mjs +0 -2
  461. package/dist/chunk-TYK6VNAB.mjs +0 -10
  462. package/dist/chunk-UALRNVSE.mjs +0 -4
  463. package/dist/chunk-V3HXVQEM.mjs +0 -2
  464. package/dist/chunk-V5VXUJKG.mjs +0 -2
  465. package/dist/chunk-VBXMADMP.mjs +0 -2
  466. package/dist/chunk-VNV55RUA.mjs +0 -2
  467. package/dist/chunk-VPD2PS35.mjs +0 -2
  468. package/dist/chunk-W4VKN3GF.mjs +0 -7
  469. package/dist/chunk-W5BXXPDJ.mjs +0 -3
  470. package/dist/chunk-XUNHRCOG.mjs +0 -6
  471. package/skill/references/building-components.md +0 -367
  472. package/web/assets/index-Bh74DfsV.css +0 -1
  473. package/web/assets/index-UW0RyFFt.js +0 -13
@@ -0,0 +1,500 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { isDeepStrictEqual } from "node:util";
6
+ import { escapeMarkup, escapeText } from "./escape.mjs";
7
+ import { writeAtomic } from "./files.mjs";
8
+ import { laidOut } from "./json-layout.mjs";
9
+ import { readStoredHead, withOutlineLock, writeHead } from "./outline.mjs";
10
+ import { newPageId } from "./page-links.mjs";
11
+ import { headOrder, headPage, headWithPage } from "./sidebar-head.mjs";
12
+ import { DOCUMENT_FOLDERS, isSlugName, keptName, layoutOf, listTrash, markerOf, slugPattern, slugTaken } from "./workspace.mjs";
13
+
14
+ // A workspace's custom pages (自定义页: notes/plans/component-pages-plan-2026-11-07.md §5, the spec
15
+ // custom-blocks-spec-2026-11-07.md §5): a page is a folder pages/<slug>/ at the workspace's root, as
16
+ // components/ is, holding page.html (the page an Agent writes, free HTML: its head, its look, its own
17
+ // scripts and frames, and where it wants them one or more boards, <div class="gallery-board">, of
18
+ // cards, each <section class="gallery-card" data-card-id data-x data-y data-w data-h> around one
19
+ // instance of a custom block) and page.json ({ title, icon, defaultCards?, hint? }: pageMetaOf). An Agent
20
+ // makes one (gallery page new), and the workbench's 新建页面 a blank one (blankPageHtml) for an Agent to
21
+ // write; the workbench lists them in the sidebar's head with 新建 and 搜索, in the order the reader drags
22
+ // them to (./sidebar-head.mjs, kept in the outline file), shows one at #@<slug> and renames or trashes
23
+ // it (src/workbench/service.mjs). A page's slug and a document's are one pool (./workspace.mjs
24
+ // slugTaken): no new document takes a page's, nor a new page a document's, and `pages` is a name no new
25
+ // document takes. The blocks' scripts and styles are put in by the server as it shows a page
26
+ // (src/render/page-html.mjs).
27
+ // No page is special by its slug. The default home page (首页, HOME_TITLE, at pages/home, holding a card of
28
+ // each of HOME_CARDS) is a page like any other, made once per workspace by src/editor/home-page.mjs, as
29
+ // the server starts and by gallery init (gallery.json's pages.homeMade says it was: markHomeMade; nothing
30
+ // makes it again), whose page.json says what any page's may: it has the default cards (defaultCards:
31
+ // 恢复默认卡片 in its menu, the note of the default cards it has not got), and its first-open note
32
+ // (hint, until the reader closes it: pageHintClosed).
33
+ // Node's built-ins only, but ./escape.mjs, ./files.mjs, ./json-layout.mjs, ./outline.mjs, ./sidebar-head.mjs
34
+ // and ./workspace.mjs.
35
+
36
+ /** A custom page's two files. */
37
+ export const PAGE_FILES = { html: "page.html", json: "page.json" };
38
+ /** The slug of the default home page (首页), which src/editor/home-page.mjs makes: a page as any other. */
39
+ export const HOME = "home";
40
+ /** The default home page's title. */
41
+ export const HOME_TITLE = "首页";
42
+ /**
43
+ * The blocks of the default home page's cards (the plan's §5.2, laid out as the pack Gallery 风格's home page,
44
+ * notes/plans/block-packs-plan-2026-11-09.md §3: 问候与日期, 天气, 倒计时, 习惯打卡, 工作区统计, 我的待办, 编辑热力图,
45
+ * 最近编辑, 便签, 静态卡片), in the order they are placed, each at the first free spot by its size (HOME_SIZES',
46
+ * else its block's default: shared/board-places.mjs freeSpot), a home of 12 x 12 cells, none left empty;
47
+ * 恢复默认卡片 (the page service's restore-defaults) puts back those of them no card of the home page holds.
48
+ */
49
+ export const HOME_CARDS = Object.freeze(["gallery/today", "gallery/weather", "gallery/countdown", "gallery/habit", "gallery/stats", "gallery/todos", "gallery/heatmap", "gallery/recent", "gallery/note", "gallery/static"]);
50
+ /** The default home page's cards that are not their block's default size, [w, h] each. */
51
+ export const HOME_SIZES = Object.freeze({ "gallery/habit": [2, 2], "gallery/todos": [4, 6], "gallery/note": [4, 4] });
52
+ /**
53
+ * The default cards a home page made before the workspace's data came had (the plan's phase 2: 问候与日期
54
+ * and 便签): what a home whose hints say nothing of its cards was told of, so that it is told once of the
55
+ * others (homeCardsTold, the home page's note of new cards).
56
+ */
57
+ export const FIRST_HOME_CARDS = Object.freeze(["gallery/today", "gallery/note"]);
58
+ /** The board a new page has, and the board gallery page add puts a card on when none is named. */
59
+ export const FIRST_BOARD = "main";
60
+ /** Whether `name` is one of a custom page's own files. */
61
+ export const isPageFile = (name) => name === PAGE_FILES.html || name === PAGE_FILES.json;
62
+
63
+ /**
64
+ * Where custom page `slug` is: <root>/pages/<slug>/, and its two files and its own files' folder
65
+ * (assets/, which the server serves at /pages/<slug>/assets/<file>). Where pages/ has no folder of
66
+ * that name, a scratch copy's of .gallery/try/ that holds a page.html (gallery page try's and gallery
67
+ * page check's, try-<8 hex>: src/cli/page-try.mjs, served and written as any page's, listed nowhere,
68
+ * gone once its command ends), as a document's slug finds one (workspace.mjs documentDirectory).
69
+ */
70
+ export function pageFiles(slug, root) {
71
+ const { customPages, scratch } = layoutOf(root);
72
+ const own = path.join(customPages, slug);
73
+ const copy = path.join(scratch, slug);
74
+ const folder = !existsSync(own) && existsSync(path.join(copy, PAGE_FILES.html)) ? copy : own;
75
+ return { folder, html: path.join(folder, PAGE_FILES.html), json: path.join(folder, PAGE_FILES.json), assets: path.join(folder, PAGE_ASSETS) };
76
+ }
77
+
78
+ /** The folder of a custom page's own files (pictures a card shows), in its folder: served at /pages/<slug>/assets/<file>. */
79
+ export const PAGE_ASSETS = "assets";
80
+
81
+ /** Where the server serves file `value` of custom page `slug`'s assets/ (`value`: assets/<file>, as a page writes it). */
82
+ export const pageAssetAddress = (slug, value) => `/pages/${slug}/${value}`;
83
+
84
+ /** Whether custom page `slug` is there: its folder holds page.html. */
85
+ export const pageExists = (slug, root) => isSlugName(slug) && existsSync(pageFiles(slug, root).html);
86
+
87
+ /**
88
+ * Why `slug` names no new custom page in workspace `root`, or null: not lowercase kebab-case; a name
89
+ * the workspace keeps (exports, components, pages…); assets or materials, which a document called
90
+ * pages keeps in that folder; taken by a document, the 写作偏好 page or another custom page (one pool,
91
+ * ./workspace.mjs slugTaken); a slug the trash holds, whose address would open the new page.
92
+ */
93
+ export async function pageSlugRefused(slug, root) {
94
+ if (typeof slug !== "string" || !slugPattern.test(slug)) return `"${slug}" is no slug: lowercase letters, digits and single hyphens (reading-list).`;
95
+ if (keptName(slug) || DOCUMENT_FOLDERS.includes(slug)) return `"${slug}" is a name the workspace keeps for itself: choose another slug.`;
96
+ if (existsSync(pageFiles(slug, root).folder)) return `A custom page "${slug}" exists already: ${pageFiles(slug, root).folder}`;
97
+ if (slugTaken(slug, root)) return `"${slug}" is a document's slug (documents and custom pages share one set of names): choose another slug.`;
98
+ if ((await listTrash(root)).some((entry) => entry.slug === slug)) return `"${slug}" is the slug of something in the trash: choose another slug.`;
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * A slug for a new custom page no name of workspace `root` holds (pageSlugRefused): page- and a random id,
104
+ * as a new document's is doc- and one (the workbench's 新建页面, whose title is typed after it is made).
105
+ */
106
+ export async function newPageSlug(root, random = undefined) {
107
+ for (;;) {
108
+ const slug = `page-${newPageId(new Set(), random)}`;
109
+ if (!(await pageSlugRefused(slug, root))) return slug;
110
+ }
111
+ }
112
+
113
+ const readJson = (file) => fs.readFile(file, "utf8").then((text) => JSON.parse(text.replace(/^/, ""))).catch(() => null);
114
+
115
+ /**
116
+ * A page.json's fields as a list shows them: { title (the slug when it names none), icon (or null),
117
+ * defaultCards (it says `"defaultCards": true`: the page has Gallery's default cards, HOME_CARDS, which
118
+ * 恢复默认卡片 in its menu puts back where it misses them, and its note tells of those that came after
119
+ * it), hint (`"hint": true`: its first-open note on its cards shows until the reader closes it) }.
120
+ * Any page may say either; the default home page says both.
121
+ */
122
+ export function pageMetaOf(slug, value) {
123
+ const title = typeof value?.title === "string" && value.title.trim() ? value.title.trim() : slug;
124
+ const icon = typeof value?.icon === "string" && value.icon.trim() ? value.icon.trim() : null;
125
+ return { title, icon, defaultCards: value?.defaultCards === true, hint: value?.hint === true };
126
+ }
127
+
128
+ /** Custom page `slug` as a list shows it: { slug, title, icon, defaultCards, hint }, or null when it is not there. */
129
+ export async function readPageMeta(slug, root) {
130
+ if (!pageExists(slug, root)) return null;
131
+ return { slug, ...pageMetaOf(slug, await readJson(pageFiles(slug, root).json)) };
132
+ }
133
+
134
+ /** Custom page `slug`'s meta as its page.json says it now (pageMetaOf), read at once; null when it is not there. */
135
+ export function pageMetaNow(slug, root) {
136
+ if (!pageExists(slug, root)) return null;
137
+ let value = null;
138
+ try {
139
+ value = JSON.parse(readFileSync(pageFiles(slug, root).json, "utf8").replace(/^/, ""));
140
+ } catch {}
141
+ return { slug, ...pageMetaOf(slug, value) };
142
+ }
143
+
144
+ /** Whether custom page `slug` has the default cards (its page.json's defaultCards): 恢复默认卡片 runs on it. */
145
+ export const pageDefaults = (slug, root) => pageMetaNow(slug, root)?.defaultCards === true;
146
+
147
+ /**
148
+ * What gallery.json says of the custom pages (the spec §2): { order: the sidebar's order of their
149
+ * slugs, homeMade: whether the default home page was made }; nothing said, or a gallery.json that does
150
+ * not read: { order: [], homeMade: false }.
151
+ */
152
+ export function pagesSettings(root) {
153
+ let value = null;
154
+ try {
155
+ value = markerOf(readFileSync(layoutOf(root).marker, "utf8")).value;
156
+ } catch {}
157
+ const pages = value?.pages && typeof value.pages === "object" && !Array.isArray(value.pages) ? value.pages : {};
158
+ const order = Array.isArray(pages.order) ? pages.order.filter((slug, at, all) => typeof slug === "string" && all.indexOf(slug) === at) : [];
159
+ return { order, homeMade: pages.homeMade === true };
160
+ }
161
+
162
+ /**
163
+ * Write into workspace `root`'s gallery.json that the default home page was made (the spec §2's
164
+ * pages.homeMade), every other key and value of it kept as it was (its pages.order too), and the file's
165
+ * own layout with them (homeMadeIn: a write no reader asked for, on a workspace's first start of a
166
+ * Gallery that makes the home page, changes no line but those it adds). Resolves to true once it says
167
+ * so; false where gallery.json does not read as Gallery's, which nothing then writes over (the home
168
+ * waits for a gallery.json that reads: made once, and said so). Throws where it cannot be written
169
+ * (writeAtomic's EPERM, EBUSY…: makeDefaultHome says so).
170
+ */
171
+ export async function markHomeMade(root) {
172
+ const file = layoutOf(root).marker;
173
+ for (let tries = 0; tries < 3; tries++) {
174
+ const text = await fs.readFile(file, "utf8").catch(() => null);
175
+ if (text === null) return false;
176
+ const { value, ours } = markerOf(text);
177
+ if (!ours || !value || typeof value !== "object" || Array.isArray(value)) return false;
178
+ const pages = value.pages && typeof value.pages === "object" && !Array.isArray(value.pages) ? value.pages : {};
179
+ if (pages.homeMade === true) return true;
180
+ const wanted = { ...value, pages: { ...pages, homeMade: true } };
181
+ let next = null;
182
+ try {
183
+ next = homeMadeIn(text);
184
+ } catch {}
185
+ // What the text says once written is checked: any doubt (a key twice, say), the whole file written as JSON.stringify lays it out.
186
+ if (next === null || !isDeepStrictEqual(markerOf(next).value, wanted)) next = `${JSON.stringify(wanted, null, 2)}\n`;
187
+ if (await writeAtomic(file, next, { expect: text })) return true;
188
+ }
189
+ return false;
190
+ }
191
+
192
+ // gallery.json's text (`text`, a JSON object that reads: markerOf) with pages.homeMade true, written into
193
+ // it as it stands: its line ends, indentation, BOM, what stands on one line kept, the new member laid out
194
+ // as the one before it is (on a line of its own at its indentation, the file's own indentation inside
195
+ // it, or on its line after it, as tightly as it is), a value that is not true or a pages that is no
196
+ // object (or an empty one) replaced in its place. Never more than that member's text changes.
197
+ function homeMadeIn(text) {
198
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
199
+ const top = objectAt(text, text.indexOf("{"));
200
+ const unit = top.members.map((member) => ownLine(text, top.open, member)).find(Boolean) ?? " ";
201
+ const blanksBefore = (member) => /[ \t]*$/.exec(text.slice(0, member.from))[0];
202
+ // `value`'s text where `member` stands: lines of its own from `indent`, or on one line (spaced as `spaced` says).
203
+ const laid = (value, indent, spaced) => (indent !== null ? JSON.stringify(value, null, unit).split("\n").join(eol + indent) : spaced ? laidOut(value, "") : JSON.stringify(value));
204
+ const added = (object, key, value) => {
205
+ const last = object.members.at(-1);
206
+ const indent = ownLine(text, object.open, last);
207
+ const gap = indent !== null ? eol + indent : blanksBefore(last);
208
+ return `${text.slice(0, last.end)},${gap}${JSON.stringify(key)}:${last.colon}${laid(value, indent, gap !== "")}${text.slice(last.end)}`;
209
+ };
210
+ const replaced = (object, member, value) => `${text.slice(0, member.value)}${laid(value, ownLine(text, object.open, member), blanksBefore(member) !== "")}${text.slice(member.end)}`;
211
+ const pages = top.members.findLast(({ key }) => key === "pages");
212
+ if (!pages) return added(top, "pages", { homeMade: true });
213
+ const inner = text[pages.value] === "{" ? objectAt(text, pages.value) : null;
214
+ if (!inner?.members.length) return replaced(top, pages, { homeMade: true });
215
+ const made = inner.members.findLast(({ key }) => key === "homeMade");
216
+ return made ? replaced(inner, made, true) : added(inner, "homeMade", true);
217
+ }
218
+
219
+ const blank = (char) => char === " " || char === "\t" || char === "\n" || char === "\r";
220
+
221
+ // Where the JSON value starting at `at` in `text` ends (the index past it), in JSON that reads; past the
222
+ // text's end, a throw (homeMadeIn's caller then lays the whole file out), never a loop that does not end.
223
+ function pastValue(text, at) {
224
+ if (text[at] === '"') {
225
+ let next = at + 1;
226
+ while (text[next] !== '"') {
227
+ if (next >= text.length) throw new Error("unended string");
228
+ next += text[next] === "\\" ? 2 : 1;
229
+ }
230
+ return next + 1;
231
+ }
232
+ if (text[at] === "{" || text[at] === "[") {
233
+ for (let next = at, depth = 0; ; next++) {
234
+ if (next >= text.length) throw new Error("unended " + text[at]);
235
+ const char = text[next];
236
+ if (char === '"') next = pastValue(text, next) - 1;
237
+ else if (char === "{" || char === "[") depth++;
238
+ else if ((char === "}" || char === "]") && --depth === 0) return next + 1;
239
+ }
240
+ }
241
+ let next = at;
242
+ while (next < text.length && !blank(text[next]) && !",}]".includes(text[next])) next++;
243
+ return next;
244
+ }
245
+
246
+ /**
247
+ * The object whose "{" is at `open` in `text`: { open, members: [{ key, from (its key's quote), value
248
+ * (where its value starts), end (past it), colon (the blanks after its colon) }] }. Also what
249
+ * ./workspace-name.mjs writes gallery.json's name by.
250
+ */
251
+ export function objectAt(text, open) {
252
+ const members = [];
253
+ let at = open + 1;
254
+ const skip = () => {
255
+ while (blank(text[at])) at++;
256
+ };
257
+ skip();
258
+ while (text[at] !== "}") {
259
+ const from = at;
260
+ at = pastValue(text, at);
261
+ const key = JSON.parse(text.slice(from, at));
262
+ skip();
263
+ const colon = ++at;
264
+ skip();
265
+ const value = at;
266
+ at = pastValue(text, at);
267
+ members.push({ key, from, value, end: at, colon: text.slice(colon, value) });
268
+ skip();
269
+ if (text[at] === ",") at++;
270
+ skip();
271
+ }
272
+ return { open, members };
273
+ }
274
+
275
+ /** The indentation `member` of the object opening at `open` stands at on a line of its own, or null where it shares its line. */
276
+ export function ownLine(text, open, member) {
277
+ const start = text.lastIndexOf("\n", member.from - 1) + 1;
278
+ const before = text.slice(start, member.from);
279
+ return start > open && /^[ \t]*$/.test(before) ? before : null;
280
+ }
281
+
282
+ // .gallery/hints.json: { pages: { <slug>: <when its first-open note was closed> }, homeCards: [the default
283
+ // cards the reader knows of] }, or {} where there is none or it does not read. Its `home` (<when>) is what
284
+ // a Gallery said before every page was alike: the note of pages/home closed, the one page that had one.
285
+ function hintsOf(root) {
286
+ try {
287
+ const value = JSON.parse(readFileSync(layoutOf(root).hints, "utf8"));
288
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
289
+ } catch {
290
+ return {};
291
+ }
292
+ }
293
+ const closedOf = (hints) => (hints.pages && typeof hints.pages === "object" && !Array.isArray(hints.pages) ? hints.pages : {});
294
+
295
+ /** What .gallery/hints.json says under `key` (undefined where it says nothing, or does not read). */
296
+ export const hintOf = (root, key) => hintsOf(root)[key];
297
+
298
+ /** .gallery/hints.json with `patch`'s keys written, its others kept. */
299
+ export async function writeHints(root, patch) {
300
+ const { hints, gallery } = layoutOf(root);
301
+ await fs.mkdir(gallery, { recursive: true });
302
+ await writeAtomic(hints, `${JSON.stringify({ ...hintsOf(root), ...patch }, null, 2)}\n`);
303
+ }
304
+
305
+ /**
306
+ * Whether the reader closed custom page `slug`'s first-open note (the plan's §5.2): once for the workspace,
307
+ * whichever browser or port shows it, kept in .gallery/hints.json (what Gallery keeps of its own, never
308
+ * gallery.json, which is the workspace's settings and wakes every page's watcher as it is written).
309
+ */
310
+ export function pageHintClosed(root, slug) {
311
+ const hints = hintsOf(root);
312
+ return typeof closedOf(hints)[slug] === "string" || (slug === HOME && typeof hints.home === "string");
313
+ }
314
+
315
+ /** Whether custom page `slug`'s first-open note shows: its page.json says `hint`, and the reader has not closed it. */
316
+ export const pageHintShown = (slug, root) => pageMetaNow(slug, root)?.hint === true && !pageHintClosed(root, slug);
317
+
318
+ /** Custom page `slug`'s first-open note closed for good (`at`: when, an ISO time), the file's other notes kept. */
319
+ export async function closePageHint(root, slug, at = new Date().toISOString()) {
320
+ const { hints, gallery } = layoutOf(root);
321
+ await fs.mkdir(gallery, { recursive: true });
322
+ const value = hintsOf(root);
323
+ await writeAtomic(hints, `${JSON.stringify({ ...value, pages: { ...closedOf(value), [slug]: at } }, null, 2)}\n`);
324
+ }
325
+
326
+ /** The first-open note of pages/home closed (closePageHint's), and whether it is (pageHintClosed's): the older words for them. */
327
+ export const closeHomeHint = (root, at) => closePageHint(root, HOME, at);
328
+ export const homeHintClosed = (root) => pageHintClosed(root, HOME);
329
+
330
+ /**
331
+ * The default cards (of HOME_CARDS) the reader knows of in workspace `root`: those its pages with the
332
+ * default cards were made with, or the reader was told of since (a page's note of new cards, closed or
333
+ * taken up, and 恢复默认卡片 run), kept in .gallery/hints.json as `homeCards`; FIRST_HOME_CARDS where it
334
+ * says none, the cards of a home page made before the others came.
335
+ */
336
+ export function homeCardsTold(root) {
337
+ const told = hintsOf(root).homeCards;
338
+ return Array.isArray(told) && told.every((id) => typeof id === "string") ? told : [...FIRST_HOME_CARDS];
339
+ }
340
+
341
+ /**
342
+ * The default cards a page's note tells of, on a page with the default cards (pageMetaOf's defaultCards;
343
+ * the plan's 3.4: a page made before them is not changed, and is told once that 恢复默认卡片 brings them):
344
+ * those of HOME_CARDS the reader was not told of (homeCardsTold) and no card of the page holds (`held`: the
345
+ * blocks its cards are). Empty where there is none to tell.
346
+ */
347
+ export const homeCardsNew = (root, held) => HOME_CARDS.filter((id) => !homeCardsTold(root).includes(id) && !held.includes(id));
348
+
349
+ /** Every default card told of (HOME_CARDS, kept as hints.json's `homeCards`): a page's note of new cards says nothing again. */
350
+ export async function tellHomeCards(root) {
351
+ const { hints, gallery } = layoutOf(root);
352
+ await fs.mkdir(gallery, { recursive: true });
353
+ await writeAtomic(hints, `${JSON.stringify({ ...hintsOf(root), homeCards: [...HOME_CARDS] }, null, 2)}\n`);
354
+ }
355
+
356
+ /**
357
+ * Pages in the order a page goes by where the reader gave it no place in the sidebar's head (the spec
358
+ * §2): those gallery.json's pages.order names, in its order, then the rest by title (then slug). Pure:
359
+ * `pages` [{ slug, title }].
360
+ */
361
+ export function inPageOrder(pages, order = []) {
362
+ const rank = (slug) => (order.includes(slug) ? order.indexOf(slug) : Infinity);
363
+ const collator = new Intl.Collator("zh-CN", { numeric: true });
364
+ return [...pages].sort((a, b) => rank(a.slug) - rank(b.slug) || collator.compare(a.title, b.title) || (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0));
365
+ }
366
+
367
+ /**
368
+ * Pages in the sidebar's order: as the head's rows go (`head`, the stored head: ./sidebar-head.mjs
369
+ * headOrder), those it names not after them in inPageOrder's (`order`: gallery.json's pages.order). Pure.
370
+ */
371
+ export function inHeadOrder(pages, order = [], head = null) {
372
+ const listed = inPageOrder(pages, order);
373
+ const bySlug = new Map(listed.map((page) => [page.slug, page]));
374
+ return headOrder(head, listed.map(({ slug }) => slug)).map(headPage).filter(Boolean).map((slug) => bySlug.get(slug));
375
+ }
376
+
377
+ /** The custom pages of workspace `root`, in the sidebar's order (inHeadOrder): [{ slug, title, icon, defaultCards, hint }]. Folders of pages/ that hold no page.html are passed over. */
378
+ export async function listPages(root) {
379
+ const { customPages: pages } = layoutOf(root);
380
+ const names = await fs.readdir(pages, { withFileTypes: true }).catch(() => []);
381
+ const found = await Promise.all(names.filter((entry) => entry.isDirectory() && isSlugName(entry.name)).map((entry) => readPageMeta(entry.name, root)));
382
+ return inHeadOrder(found.filter(Boolean), pagesSettings(root).order, await readStoredHead(root));
383
+ }
384
+
385
+ /** The sidebar's head rows of workspace `root` in their order (./sidebar-head.mjs headOrder): new, search and @<slug> of each of `pages` (listPages'). */
386
+ export async function headOf(root, pages = null) {
387
+ return headOrder(await readStoredHead(root), (pages ?? await listPages(root)).map(({ slug }) => slug));
388
+ }
389
+
390
+ /**
391
+ * Custom page `slug` given its row in the sidebar's head, in the outline file under its lock: right after
392
+ * page `after`'s row, else last (a page made: createPage's). A head that was never stored is stored as
393
+ * the sidebar showed it, so that the page made last stands last.
394
+ */
395
+ export async function placePageInHead(root, slug, after = null) {
396
+ await withOutlineLock(root, async () => {
397
+ const stored = await readStoredHead(root);
398
+ const shown = stored ? null : await headOf(root, (await listPages(root)).filter((page) => page.slug !== slug));
399
+ await writeHead(root, headWithPage(stored, shown, slug, after));
400
+ });
401
+ }
402
+
403
+ /**
404
+ * A page's version as the event stream says it (`page <version>`, the spec §7): its page.html and
405
+ * page.json as they are, 16 hex digits. The page compares it with the one it was served with.
406
+ */
407
+ export function pageVersion(html, json = "") {
408
+ return createHash("sha256").update(html ?? "").update("\u0000").update(json ?? "").digest("hex").slice(0, 16);
409
+ }
410
+
411
+ /** A page's version as its files are now (pageVersion). */
412
+ export async function pageVersionNow(slug, root) {
413
+ const { html, json } = pageFiles(slug, root);
414
+ const [page, meta] = await Promise.all([fs.readFile(html, "utf8").catch(() => ""), fs.readFile(json, "utf8").catch(() => "")]);
415
+ return pageVersion(page, meta);
416
+ }
417
+
418
+ /**
419
+ * The page.html of a new custom page: its title (in `<title>` only: the page draws no big title, its
420
+ * name is the sidebar's, the tab's and the top bar's) and its <main class="gallery-page">, holding one
421
+ * empty board (`main`, the spec §5's structure: gallery page new's), or nothing (`blank`: the workbench's
422
+ * 新建页面, a page an Agent is to write, isBlankPage). `lang`: its page's lang ("zh-CN" by default).
423
+ */
424
+ export function newPageHtml(slug, title, { lang = "zh-CN", blank = false } = {}) {
425
+ const inside = blank ? "" : `
426
+ <div class="gallery-board" data-board-id="${FIRST_BOARD}">
427
+ </div>`;
428
+ return `<!doctype html>
429
+ <html lang="${escapeMarkup(lang)}">
430
+ <head>
431
+ <meta charset="utf-8">
432
+ <meta name="viewport" content="width=device-width, initial-scale=1">
433
+ <title>${escapeText(title)}</title>
434
+ <style>
435
+ /* This page's own look. Colors of its own are written for both themes:
436
+ :root[data-doc-effective-theme="dark"] { … } holds the dark ones. */
437
+ </style>
438
+ </head>
439
+ <body>
440
+ <main class="gallery-page" data-page-id="${slug}">${inside}
441
+ </main>
442
+ </body>
443
+ </html>
444
+ `;
445
+ }
446
+
447
+ /**
448
+ * Whether page.html `html` is a blank page, one no one has written yet: its <body> holds an empty
449
+ * <main class="gallery-page"> and nothing else (comments and blanks aside), as the workbench's 新建页面
450
+ * writes it (newPageHtml's `blank`). The workbench then shows, in its middle, that an Agent writes it.
451
+ */
452
+ export function isBlankPage(html) {
453
+ const body = /<body\b[^>]*>([\s\S]*?)<\/body>/i.exec(String(html ?? "").replace(/<!--[\s\S]*?-->/g, ""))?.[1];
454
+ return body !== undefined && /^\s*<main\b[^>]*\bclass\s*=\s*["']?gallery-page["']?[^>]*>\s*<\/main>\s*$/i.test(body);
455
+ }
456
+
457
+ /**
458
+ * Make custom page `slug` in workspace `root`: pages/<slug>/ with page.html (newPageHtml: one empty
459
+ * board, or none where `blank`) and page.json ({ title, icon?, defaultCards?, hint? }: pageMetaOf), and its
460
+ * row in the sidebar's head right after page `after`'s, else last (placePageInHead; a head that cannot be
461
+ * written leaves it where a page the head names not goes). Throws (code EEXIST) when pageSlugRefused
462
+ * refuses the slug; resolves to its files.
463
+ */
464
+ export async function createPage(slug, title, { root, icon = null, lang, blank = false, defaultCards = false, hint = false, after = null } = {}) {
465
+ const refused = await pageSlugRefused(slug, root);
466
+ if (refused) throw Object.assign(new Error(refused), { code: "EEXIST" });
467
+ const files = pageFiles(slug, root);
468
+ await fs.mkdir(layoutOf(root).customPages, { recursive: true });
469
+ await fs.mkdir(files.folder, { recursive: false }); // fails when it was made meanwhile
470
+ const name = title.replace(/\s+/g, " ").trim();
471
+ await fs.writeFile(files.html, newPageHtml(slug, name, { lang, blank }));
472
+ const meta = { title: name, ...(icon ? { icon } : {}), ...(defaultCards ? { defaultCards: true } : {}), ...(hint ? { hint: true } : {}) };
473
+ await fs.writeFile(files.json, `${JSON.stringify(meta, null, 2)}\n`);
474
+ await placePageInHead(root, slug, after).catch(() => {});
475
+ return files;
476
+ }
477
+
478
+ /**
479
+ * Custom page `slug`'s title written into its page.json (the workbench's 重命名), every other field as
480
+ * it was; page.html is not touched (its <title> and header are the Agent's). Resolves to its meta, or
481
+ * null when the page is not there; throws for a title that is empty or longer than 200 characters.
482
+ */
483
+ export async function renamePage(slug, title, root) {
484
+ const name = typeof title === "string" ? title.replace(/\s+/g, " ").trim() : "";
485
+ if (!name || name.length > 200) throw Object.assign(new Error("A page's title is 1 to 200 characters."), { code: "title" });
486
+ if (!pageExists(slug, root)) return null;
487
+ const { json } = pageFiles(slug, root);
488
+ for (let tries = 0; tries < 3; tries++) {
489
+ const text = await fs.readFile(json, "utf8").catch(() => null);
490
+ let value = {};
491
+ try {
492
+ value = text === null ? {} : JSON.parse(text.replace(/^/, ""));
493
+ } catch {}
494
+ if (!value || typeof value !== "object" || Array.isArray(value)) value = {};
495
+ const next = `${JSON.stringify({ ...value, title: name }, null, 2)}\n`;
496
+ const written = text === null ? (await fs.writeFile(json, next, { flag: "wx" }).then(() => true, () => false)) : await writeAtomic(json, next, { expect: text });
497
+ if (written) break;
498
+ }
499
+ return readPageMeta(slug, root);
500
+ }
@@ -51,6 +51,12 @@ export const previewLogFile = workspace.serverLog;
51
51
  const inPackage = (built, ...source) => path.join(packageRoot, ...(release ? built : source));
52
52
  export const kitSourceDirectory = path.join(packageRoot, "src", "render", "kit"); // the source checkout's only
53
53
  export const starterDirectory = inPackage(["starter"], "src", "render", "starter");
54
+ /**
55
+ * Gallery's own block packs (自定义块包), a folder each (src/packs/<pack>/: its pack.json and a folder of each
56
+ * of its blocks, <pack>/<name>/, the scope <pack>): what a workspace's first start installs as any shared
57
+ * pack is installed, and gallery block add copies a block of (shared/block-packs.mjs, shared/shipped-blocks.mjs).
58
+ */
59
+ export const packsDirectory = inPackage(["packs"], "src", "packs");
54
60
  export const vendorDirectory = inPackage(["vendor"], "public", "vendor");
55
61
  export const kitPublicDirectory = inPackage(["kit"], "public", "kit");
56
62
  /** The editor's browser modules, at their path under src/ (the preview serves them so: src/server/routes.mjs). */
@@ -0,0 +1,92 @@
1
+ // Pinyin as the "/" menu looks for a block by it (docs/editor.md, "In the menus"; the built-in blocks
2
+ // list their words in registry.mjs: 公式 is gongshi gs). A custom block's words are its manifest's, so
3
+ // that an author writes the pinyin of the whole name there (the check notes a name no word spells:
4
+ // cli/component-check.mjs wantsPinyin); the initials ("djs" for 倒计时, "daojishi") are not asked of
5
+ // anyone: they are made here, from a word that reads as pinyin and from the name itself.
6
+
7
+ // A syllable of pinyin, no tones: an initial or none, then a final. A word that can be read as pinyin
8
+ // is syllables one after another.
9
+ const SYLLABLE = "(?:(?:[zcs]h|[bpmfdtnlgkhjqxrzcsyw])?(?:[iuv]?(?:a(?:i|o|ng?)?|e(?:i|r|ng?)?|o(?:u|ng)?)|i(?:u|ng?)?|u(?:i|n)?|vn?))";
10
+ const ONE_SYLLABLE = new RegExp(`^${SYLLABLE}$`, "i");
11
+ const LONGEST_SYLLABLE = 6; // zhuang
12
+ const HAN = /\p{Script=Han}/u;
13
+
14
+ /**
15
+ * The syllables `word` reads as, fewest first, its apostrophes left out as an input method leaves
16
+ * them, or null for a word that is none. The fewest, since single finals read a word as more syllables
17
+ * than an input method composes it of (fenbiao as fen-bi-a-o, composed fen-biao).
18
+ */
19
+ export function syllablesOf(word) {
20
+ const letters = word.replaceAll("'", "");
21
+ const fewest = [0, ...Array(letters.length).fill(Infinity)];
22
+ const from = Array(letters.length + 1).fill(0);
23
+ for (let end = 1; end <= letters.length; end++) {
24
+ for (let start = Math.max(0, end - LONGEST_SYLLABLE); start < end; start++) {
25
+ if (fewest[start] + 1 < fewest[end] && ONE_SYLLABLE.test(letters.slice(start, end))) {
26
+ fewest[end] = fewest[start] + 1;
27
+ from[end] = start;
28
+ }
29
+ }
30
+ }
31
+ if (!letters || !Number.isFinite(fewest[letters.length])) return null;
32
+ const syllables = [];
33
+ for (let end = letters.length; end > 0; end = from[end]) syllables.unshift(letters.slice(from[end], end));
34
+ return syllables;
35
+ }
36
+
37
+ /** The fewest syllables of pinyin `word` reads as, or null for a word that is none. */
38
+ export const fewestSyllables = (word) => syllablesOf(word)?.length ?? null;
39
+
40
+ /** The first letters of the syllables of a word of pinyin ("daojishi" is "djs"), or null for a word that is none or one syllable. */
41
+ export function initialsOfWord(word) {
42
+ const syllables = syllablesOf(word.toLowerCase());
43
+ return syllables && syllables.length > 1 ? syllables.map((syllable) => syllable[0]).join("") : null;
44
+ }
45
+
46
+ // The characters that open each letter's run in the pinyin order of the platform's collation (阿 a, 八 b …
47
+ // 帀 z: no run for i, u or v), the classic way to find the first letter of a character's reading without a
48
+ // table. Known only where the platform's collation sorts Chinese by pinyin, which it is asked once.
49
+ const OPENERS = [..."阿八嚓哒妸发旮哈讥咔垃妈拿噢妑七呥仨他屲夕丫帀"];
50
+ const LETTERS = [..."abcdefghjklmnopqrstwxyz"];
51
+ let collator;
52
+
53
+ function sorted() {
54
+ if (collator !== undefined) return collator;
55
+ try {
56
+ const found = new Intl.Collator("zh-CN");
57
+ collator = OPENERS.every((each, at) => at === 0 || found.compare(OPENERS[at - 1], each) < 0) && found.compare("计", "时") < 0 ? found : null;
58
+ } catch {
59
+ collator = null;
60
+ }
61
+ return collator;
62
+ }
63
+
64
+ /** The first letter of a Chinese character's pinyin (its commonest reading), or null. */
65
+ function initialOf(character) {
66
+ const found = sorted();
67
+ if (!found || !HAN.test(character)) return null;
68
+ let letter = null;
69
+ for (let at = 0; at < OPENERS.length && found.compare(character, OPENERS[at]) >= 0; at++) letter = LETTERS[at];
70
+ return letter;
71
+ }
72
+
73
+ /** The initials of a name: a Chinese character's first letter of pinyin, a letter or digit as it is ("倒计时" is "djs"), none where the platform cannot tell. */
74
+ export function initialsOfName(name) {
75
+ let initials = "";
76
+ for (const character of name) {
77
+ const letter = initialOf(character) ?? (/[a-z0-9]/i.test(character) ? character.toLowerCase() : null);
78
+ if (letter) initials += letter;
79
+ }
80
+ return initials.length > 1 ? initials : "";
81
+ }
82
+
83
+ /**
84
+ * What "/" matches a custom block by (slash-menu matchScore: each word whole, or by its start): its names,
85
+ * its id and the manifest's words, then the initials of the name and of every word that reads as pinyin,
86
+ * so that 倒计时 is found by djs as 公式 is by gs.
87
+ */
88
+ export function searchWords({ name, id, words }) {
89
+ const given = `${name.zh} ${name.en} ${id} ${words}`.toLowerCase();
90
+ const initials = [initialsOfName(name.zh), ...words.split(/\s+/).filter(Boolean).map(initialsOfWord)].filter(Boolean);
91
+ return [given, ...new Set(initials)].join(" ");
92
+ }
@@ -0,0 +1,17 @@
1
+ // 演示视图 (the workbench's, src/workbench/app/features/present/): the controls the page keeps its keys for (a field to
2
+ // type in, an editable text, a block's instance: the reader's keys in them are theirs, Esc alone is the presentation's, unless the
3
+ // control used it), and 上一节 / 下一节, which the presentation does on a page of its own origin and a draft's page does itself
4
+ // (src/editor/page/present-shell.mjs), for the presentation cannot reach into a page of another origin.
5
+
6
+ export const KEEPS_KEYS = 'input:not([type="checkbox"], [type="radio"], [type="button"], [type="submit"], [type="reset"], [type="file"], [type="color"]), textarea, select, [contenteditable]:not([contenteditable="false"]), [data-doc-component]';
7
+
8
+ /**
9
+ * The report in window `win` scrolled to the next section (`direction` 1) or the one before (-1): the visible h2 and h1 of its main,
10
+ * placed 24px below the window's top; at the last, the page's end, at the first, its top.
11
+ */
12
+ export function stepSection(win, direction) {
13
+ const doc = win.document;
14
+ const tops = [...doc.querySelectorAll("main h2, main h1")].filter((node) => node.getClientRects().length).map((node) => node.getBoundingClientRect().top - 24);
15
+ const target = direction > 0 ? tops.find((top) => top > 4) : tops.filter((top) => top < -4).pop();
16
+ win.scrollTo({ top: target === undefined ? (direction > 0 ? doc.documentElement.scrollHeight : 0) : win.scrollY + target, behavior: "smooth" });
17
+ }
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
4
4
  import net from "node:net";
5
5
  import path from "node:path";
6
6
  import { packageRoot } from "./paths.mjs";
7
+ import { compareVersions } from "./versions.mjs";
7
8
  import { layoutOf } from "./workspace.mjs";
8
9
 
9
10
  // Where a workspace's Gallery server listens (one a gallery command started, kind "cli", or npm run
@@ -168,13 +169,8 @@ export const portIsFree = (port) => new Promise((resolve) => {
168
169
  }));
169
170
  });
170
171
 
171
- /** -1, 0 or 1 as version `a` is older than, the same as or newer than `b` (numbers by dots: 0.1.0 < 0.1.10 < 0.2.0). */
172
- export function compareVersions(a, b) {
173
- const parts = (version) => String(version ?? "0").split(/[.+-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
174
- const [x, y] = [parts(a), parts(b)];
175
- for (let index = 0; index < 3; index++) if ((x[index] ?? 0) !== (y[index] ?? 0)) return (x[index] ?? 0) < (y[index] ?? 0) ? -1 : 1;
176
- return 0;
177
- }
172
+ /** -1, 0 or 1 as version `a` is older than, the same as or newer than `b` (./versions.mjs, which gallery --version reads too). */
173
+ export { compareVersions };
178
174
 
179
175
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
180
176
  // Whether process `pid` is gone within `ms` (asked every 100 ms).