@heycys/gallery 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/THIRD-PARTY-NOTICES.md +1 -1
  2. package/browser/editor/components/columns/page.mjs +4 -4
  3. package/browser/editor/components/embed/index.mjs +2 -2
  4. package/browser/editor/components/flowchart/add.mjs +7 -6
  5. package/browser/editor/components/flowchart/page.mjs +1 -1
  6. package/browser/editor/components/icons.mjs +8 -0
  7. package/browser/editor/components/image/crop.mjs +6 -9
  8. package/browser/editor/components/installed/index.mjs +11 -4
  9. package/browser/editor/components/installed/page.mjs +27 -16
  10. package/browser/editor/components/installed/served.mjs +6 -5
  11. package/browser/editor/components/instances.mjs +18 -0
  12. package/browser/editor/components/list/page.mjs +3 -6
  13. package/browser/editor/components/registry.mjs +13 -4
  14. package/browser/editor/components/table/index.mjs +59 -2
  15. package/browser/editor/components/table/page.mjs +1 -1
  16. package/browser/editor/page/annotate/card.mjs +3 -6
  17. package/browser/editor/page/autosave.mjs +310 -37
  18. package/browser/editor/page/board/client.mjs +57 -0
  19. package/browser/editor/page/board/hint.mjs +145 -0
  20. package/browser/editor/page/board/history.mjs +119 -0
  21. package/browser/editor/page/board/index.mjs +970 -0
  22. package/browser/editor/page/board/settings.mjs +209 -0
  23. package/browser/editor/page/caret.mjs +1 -1
  24. package/browser/editor/page/changes/bar.mjs +6 -14
  25. package/browser/editor/page/changes/index.mjs +2 -2
  26. package/browser/editor/page/clipboard/import/html.mjs +29 -6
  27. package/browser/editor/page/clipboard/import/inline.mjs +32 -0
  28. package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
  29. package/browser/editor/page/clipboard/import/read.mjs +6 -1
  30. package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
  31. package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
  32. package/browser/editor/page/clipboard/index.mjs +124 -13
  33. package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
  34. package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
  35. package/browser/editor/page/clipboard/own/selection.mjs +1 -1
  36. package/browser/editor/page/columns/index.mjs +48 -46
  37. package/browser/editor/page/commit.mjs +80 -22
  38. package/browser/editor/page/conflicts.mjs +7 -7
  39. package/browser/editor/page/core.mjs +31 -15
  40. package/browser/editor/page/custom-page.mjs +106 -0
  41. package/browser/editor/page/editor.mjs +51 -14
  42. package/browser/editor/page/emoji-picker/styles.mjs +7 -12
  43. package/browser/editor/page/emoji-picker/upload.mjs +2 -2
  44. package/browser/editor/page/handle/index.mjs +5 -8
  45. package/browser/editor/page/history.mjs +24 -4
  46. package/browser/editor/page/links/index.mjs +5 -3
  47. package/browser/editor/page/links/pages.mjs +27 -9
  48. package/browser/editor/page/look.mjs +54 -3
  49. package/browser/editor/page/margin.mjs +18 -3
  50. package/browser/editor/page/media-menu/index.mjs +6 -7
  51. package/browser/editor/page/menu.mjs +18 -7
  52. package/browser/editor/page/notices.mjs +2 -2
  53. package/browser/editor/page/page-objects.mjs +13 -2
  54. package/browser/editor/page/panel.mjs +4 -9
  55. package/browser/editor/page/present-links.mjs +11 -2
  56. package/browser/editor/page/resize/index.mjs +42 -29
  57. package/browser/editor/page/served.mjs +14 -0
  58. package/browser/editor/page/session.mjs +48 -4
  59. package/browser/editor/page/slash-menu/index.mjs +3 -3
  60. package/browser/editor/page/table/cells.mjs +102 -46
  61. package/browser/editor/page/table/icons.mjs +2 -0
  62. package/browser/editor/page/table/index.mjs +29 -10
  63. package/browser/editor/page/table/pictures.mjs +299 -0
  64. package/browser/editor/page/table/views.mjs +150 -0
  65. package/browser/editor/page/text-select/index.mjs +26 -12
  66. package/browser/editor/page/todo-writes.mjs +75 -0
  67. package/browser/editor/page/typing/index.mjs +53 -3
  68. package/browser/editor/page/ui.mjs +13 -0
  69. package/browser/shared/block-packs.mjs +404 -0
  70. package/browser/shared/board-places.mjs +224 -0
  71. package/browser/shared/card-size.mjs +59 -0
  72. package/browser/shared/colors.mjs +3 -2
  73. package/browser/shared/component-catalog.mjs +323 -46
  74. package/browser/shared/component-source.mjs +37 -0
  75. package/browser/shared/components/index.mjs +12 -3
  76. package/browser/shared/components/installed.mjs +35 -3
  77. package/browser/shared/components/page.mjs +18 -8
  78. package/browser/shared/components/state.mjs +5 -1
  79. package/browser/shared/document-look.mjs +10 -5
  80. package/browser/shared/drafts.mjs +432 -0
  81. package/browser/shared/extension-sidebar.mjs +126 -0
  82. package/browser/shared/extensions.mjs +79 -0
  83. package/browser/shared/file-table.mjs +101 -0
  84. package/browser/shared/i18n/en.mjs +307 -27
  85. package/browser/shared/json-layout.mjs +47 -0
  86. package/browser/shared/net-cache.mjs +133 -0
  87. package/browser/shared/net-keys.mjs +32 -0
  88. package/browser/shared/net-policy.mjs +177 -0
  89. package/browser/shared/outline-shape.mjs +128 -0
  90. package/browser/shared/outline.mjs +331 -67
  91. package/browser/shared/pack-manifest.mjs +77 -0
  92. package/browser/shared/page-address.mjs +6 -3
  93. package/browser/shared/page-cards.mjs +308 -0
  94. package/browser/shared/page-links.mjs +69 -6
  95. package/browser/shared/pages.mjs +388 -0
  96. package/browser/shared/paths.mjs +6 -0
  97. package/browser/shared/preview-server.mjs +3 -7
  98. package/browser/shared/session.mjs +8 -1
  99. package/browser/shared/shipped-blocks.mjs +299 -0
  100. package/browser/shared/table-chart.mjs +33 -1
  101. package/browser/shared/todos.mjs +83 -0
  102. package/browser/shared/user-directory.mjs +24 -0
  103. package/browser/shared/versions.mjs +14 -0
  104. package/browser/shared/web-links.mjs +134 -0
  105. package/browser/shared/workspace-columns.mjs +120 -0
  106. package/browser/shared/workspace-name.mjs +154 -0
  107. package/browser/shared/workspace-query.mjs +125 -0
  108. package/browser/shared/workspace-registry.mjs +272 -0
  109. package/browser/shared/workspace.mjs +171 -20
  110. package/dist/chunk-2NRBY6AJ.mjs +2 -0
  111. package/dist/chunk-32ZUEFZ7.mjs +2 -0
  112. package/dist/chunk-35NQ4PLQ.mjs +2 -0
  113. package/dist/chunk-3KJHLHUO.mjs +2 -0
  114. package/dist/chunk-3NJACR4W.mjs +2 -0
  115. package/dist/chunk-4AV456RQ.mjs +2 -0
  116. package/dist/chunk-4OZWXZE5.mjs +5 -0
  117. package/dist/chunk-5XYHDBJ5.mjs +2 -0
  118. package/dist/chunk-5ZJA7CXT.mjs +2 -0
  119. package/dist/chunk-6A53MZYZ.mjs +2 -0
  120. package/dist/chunk-6EXH3ORZ.mjs +166 -0
  121. package/dist/chunk-6EZ7FNIK.mjs +3 -0
  122. package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
  123. package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
  124. package/dist/chunk-B4P2CM2J.mjs +3 -0
  125. package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
  126. package/dist/chunk-BVEFGE4B.mjs +2 -0
  127. package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
  128. package/dist/chunk-CRJEG7MT.mjs +2 -0
  129. package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
  130. package/dist/chunk-D4EURAX4.mjs +4 -0
  131. package/dist/chunk-DQCE7TJY.mjs +5 -0
  132. package/dist/chunk-DR73Y2UJ.mjs +3 -0
  133. package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
  134. package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
  135. package/dist/chunk-EURLTOTZ.mjs +4 -0
  136. package/dist/chunk-FJYN7RTH.mjs +4 -0
  137. package/dist/chunk-FWNRPMCJ.mjs +12 -0
  138. package/dist/chunk-FWW4CSAZ.mjs +2 -0
  139. package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
  140. package/dist/chunk-GY3FAUVM.mjs +2 -0
  141. package/dist/chunk-HLYV7RIH.mjs +2 -0
  142. package/dist/chunk-HMYTM3GT.mjs +15 -0
  143. package/dist/chunk-IA4PSIMD.mjs +2 -0
  144. package/dist/chunk-JEUK3ZO7.mjs +2 -0
  145. package/dist/chunk-KTKHRC6H.mjs +2 -0
  146. package/dist/chunk-L7UP52JO.mjs +2 -0
  147. package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
  148. package/dist/chunk-N76HWBA3.mjs +10 -0
  149. package/dist/chunk-N7AZZLGT.mjs +548 -0
  150. package/dist/chunk-NAMN3RAF.mjs +2 -0
  151. package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
  152. package/dist/chunk-NG375ILQ.mjs +2 -0
  153. package/dist/chunk-NVRNLK25.mjs +6 -0
  154. package/dist/chunk-NWFBE2YA.mjs +2 -0
  155. package/dist/chunk-NWR2LT5B.mjs +2 -0
  156. package/dist/chunk-OJKMINIU.mjs +20 -0
  157. package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
  158. package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
  159. package/dist/chunk-PAREWQGB.mjs +4 -0
  160. package/dist/chunk-POQSE4C2.mjs +2 -0
  161. package/dist/chunk-PUOQ65NC.mjs +2 -0
  162. package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
  163. package/dist/chunk-PZDLJJTJ.mjs +3 -0
  164. package/dist/chunk-Q47PLKIJ.mjs +15 -0
  165. package/dist/chunk-Q4UEPUWF.mjs +2 -0
  166. package/dist/chunk-QKXMPUXC.mjs +2 -0
  167. package/dist/chunk-QOUEFUCH.mjs +16 -0
  168. package/dist/chunk-RFDZXFJF.mjs +13 -0
  169. package/dist/chunk-RKPWLGZ3.mjs +4 -0
  170. package/dist/chunk-RMECZA2K.mjs +2 -0
  171. package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
  172. package/dist/chunk-SX4LJYT6.mjs +8 -0
  173. package/dist/chunk-SXHBYZST.mjs +16 -0
  174. package/dist/chunk-TANZRINB.mjs +2 -0
  175. package/dist/chunk-TEZAWDSQ.mjs +2 -0
  176. package/dist/chunk-UBBRTJ2R.mjs +24 -0
  177. package/dist/chunk-UFNYFFKO.mjs +20 -0
  178. package/dist/chunk-UGWYKHBL.mjs +170 -0
  179. package/dist/chunk-UKSHDZZ6.mjs +2 -0
  180. package/dist/chunk-UNOYVIYR.mjs +2 -0
  181. package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
  182. package/dist/chunk-VJPFVNNW.mjs +15 -0
  183. package/dist/chunk-VS3ZMVGX.mjs +5 -0
  184. package/dist/chunk-VTKNEYT3.mjs +6 -0
  185. package/dist/chunk-VXACTLO3.mjs +3 -0
  186. package/dist/chunk-YI43RG4P.mjs +2 -0
  187. package/dist/chunk-YQGT4AB2.mjs +6 -0
  188. package/dist/chunk-ZB4H7ZN4.mjs +2 -0
  189. package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
  190. package/dist/chunk-ZTHICSSF.mjs +2 -0
  191. package/dist/chunk-ZUNVAXLH.mjs +2 -0
  192. package/dist/chunk-ZWPMCK7P.mjs +2 -0
  193. package/dist/cli.mjs +1 -1
  194. package/dist/server.mjs +1 -1
  195. package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
  196. package/dist/worker.mjs +1 -1
  197. package/kit/board.css +95 -0
  198. package/kit/board.js +250 -0
  199. package/kit/charts.js +5 -71
  200. package/kit/code.js +14 -14
  201. package/kit/report.css +255 -17
  202. package/kit/runtime.js +1600 -125
  203. package/kit/state.js +10 -1
  204. package/kit/tables.js +128 -43
  205. package/kit/ui.js +77 -35
  206. package/package.json +2 -1
  207. package/packs/gallery/card/component.json +14 -0
  208. package/packs/gallery/card/example.html +10 -0
  209. package/packs/gallery/card/preview.svg +1 -0
  210. package/packs/gallery/card/style.css +6 -0
  211. package/packs/gallery/card/template.html +6 -0
  212. package/packs/gallery/card/writing.md +14 -0
  213. package/packs/gallery/chart-card/component.json +15 -0
  214. package/packs/gallery/chart-card/example.html +14 -0
  215. package/packs/gallery/chart-card/kit.js +130 -0
  216. package/packs/gallery/chart-card/preview.svg +1 -0
  217. package/packs/gallery/chart-card/style.css +42 -0
  218. package/packs/gallery/chart-card/template.html +13 -0
  219. package/packs/gallery/chart-card/writing.md +20 -0
  220. package/packs/gallery/chart-view/component.json +20 -0
  221. package/packs/gallery/chart-view/example.html +13 -0
  222. package/packs/gallery/chart-view/kit.js +355 -0
  223. package/packs/gallery/chart-view/preview.svg +1 -0
  224. package/packs/gallery/chart-view/style.css +94 -0
  225. package/packs/gallery/chart-view/template.html +3 -0
  226. package/packs/gallery/chart-view/writing.md +22 -0
  227. package/packs/gallery/countdown/component.json +18 -0
  228. package/packs/gallery/countdown/example.html +3 -0
  229. package/packs/gallery/countdown/kit.js +156 -0
  230. package/packs/gallery/countdown/preview.svg +1 -0
  231. package/packs/gallery/countdown/style.css +130 -0
  232. package/packs/gallery/countdown/template.html +3 -0
  233. package/packs/gallery/countdown/writing.md +22 -0
  234. package/packs/gallery/habit/component.json +14 -0
  235. package/packs/gallery/habit/example.html +3 -0
  236. package/packs/gallery/habit/kit.js +295 -0
  237. package/packs/gallery/habit/preview.svg +1 -0
  238. package/packs/gallery/habit/style.css +246 -0
  239. package/packs/gallery/habit/template.html +3 -0
  240. package/packs/gallery/habit/writing.md +21 -0
  241. package/packs/gallery/heatmap/component.json +18 -0
  242. package/packs/gallery/heatmap/example.html +3 -0
  243. package/packs/gallery/heatmap/kit.js +351 -0
  244. package/packs/gallery/heatmap/preview.svg +1 -0
  245. package/packs/gallery/heatmap/style.css +201 -0
  246. package/packs/gallery/heatmap/template.html +3 -0
  247. package/packs/gallery/heatmap/writing.md +21 -0
  248. package/packs/gallery/kanban/component.json +18 -0
  249. package/packs/gallery/kanban/example.html +12 -0
  250. package/packs/gallery/kanban/kit.js +540 -0
  251. package/packs/gallery/kanban/preview.svg +1 -0
  252. package/packs/gallery/kanban/style.css +212 -0
  253. package/packs/gallery/kanban/template.html +3 -0
  254. package/packs/gallery/kanban/writing.md +20 -0
  255. package/packs/gallery/metric/component.json +22 -0
  256. package/packs/gallery/metric/example.html +15 -0
  257. package/packs/gallery/metric/kit.js +248 -0
  258. package/packs/gallery/metric/preview.svg +1 -0
  259. package/packs/gallery/metric/style.css +92 -0
  260. package/packs/gallery/metric/template.html +3 -0
  261. package/packs/gallery/metric/writing.md +23 -0
  262. package/packs/gallery/note/component.json +14 -0
  263. package/packs/gallery/note/example.html +3 -0
  264. package/packs/gallery/note/kit.js +102 -0
  265. package/packs/gallery/note/preview.svg +1 -0
  266. package/packs/gallery/note/style.css +76 -0
  267. package/packs/gallery/note/template.html +3 -0
  268. package/packs/gallery/note/writing.md +18 -0
  269. package/packs/gallery/pack.json +8 -0
  270. package/packs/gallery/recent/component.json +13 -0
  271. package/packs/gallery/recent/example.html +1 -0
  272. package/packs/gallery/recent/kit.js +238 -0
  273. package/packs/gallery/recent/preview.svg +1 -0
  274. package/packs/gallery/recent/style.css +158 -0
  275. package/packs/gallery/recent/template.html +1 -0
  276. package/packs/gallery/recent/writing.md +15 -0
  277. package/packs/gallery/static/component.json +13 -0
  278. package/packs/gallery/static/example.html +11 -0
  279. package/packs/gallery/static/preview.svg +1 -0
  280. package/packs/gallery/static/style.css +138 -0
  281. package/packs/gallery/static/template.html +1 -0
  282. package/packs/gallery/static/writing.md +18 -0
  283. package/packs/gallery/stats/component.json +13 -0
  284. package/packs/gallery/stats/example.html +1 -0
  285. package/packs/gallery/stats/kit.js +166 -0
  286. package/packs/gallery/stats/preview.svg +1 -0
  287. package/packs/gallery/stats/style.css +151 -0
  288. package/packs/gallery/stats/template.html +1 -0
  289. package/packs/gallery/stats/writing.md +11 -0
  290. package/packs/gallery/today/component.json +12 -0
  291. package/packs/gallery/today/example.html +1 -0
  292. package/packs/gallery/today/kit.js +185 -0
  293. package/packs/gallery/today/preview.svg +1 -0
  294. package/packs/gallery/today/style.css +120 -0
  295. package/packs/gallery/today/template.html +1 -0
  296. package/packs/gallery/today/writing.md +14 -0
  297. package/packs/gallery/todos/component.json +19 -0
  298. package/packs/gallery/todos/example.html +3 -0
  299. package/packs/gallery/todos/kit.js +342 -0
  300. package/packs/gallery/todos/preview.svg +1 -0
  301. package/packs/gallery/todos/style.css +267 -0
  302. package/packs/gallery/todos/template.html +3 -0
  303. package/packs/gallery/todos/writing.md +30 -0
  304. package/packs/gallery/weather/component.json +19 -0
  305. package/packs/gallery/weather/example.html +3 -0
  306. package/packs/gallery/weather/kit.js +312 -0
  307. package/packs/gallery/weather/preview.svg +1 -0
  308. package/packs/gallery/weather/style.css +258 -0
  309. package/packs/gallery/weather/template.html +3 -0
  310. package/packs/gallery/weather/writing.md +26 -0
  311. package/packs/gallery-soft/pack.json +8 -0
  312. package/release.json +12 -10
  313. package/sdk/doc-kit.d.ts +396 -5
  314. package/sdk/doc-state.d.ts +2 -0
  315. package/skill/SKILL.md +56 -23
  316. package/skill/references/block-script.md +140 -0
  317. package/skill/references/building-blocks.md +257 -0
  318. package/skill/references/extensions.md +59 -0
  319. package/skill/references/pages-and-data.md +221 -0
  320. package/skill/references/writing-html.md +6 -4
  321. package/skill/sdk/doc-kit.d.ts +396 -5
  322. package/skill/sdk/doc-state.d.ts +2 -0
  323. package/web/assets/index-BK2e_07w.js +13 -0
  324. package/web/assets/index-CWFI83mF.css +1 -0
  325. package/web/index.html +2 -2
  326. package/dist/chunk-3HIRPVLU.mjs +0 -2
  327. package/dist/chunk-5MHMDLZD.mjs +0 -4
  328. package/dist/chunk-64K7IPLO.mjs +0 -255
  329. package/dist/chunk-6QXGZM36.mjs +0 -8
  330. package/dist/chunk-6RGJOMJP.mjs +0 -16
  331. package/dist/chunk-AJ73XJF3.mjs +0 -2
  332. package/dist/chunk-AUZGSZJH.mjs +0 -13
  333. package/dist/chunk-BFE45AKL.mjs +0 -2
  334. package/dist/chunk-BXO2ZY5P.mjs +0 -2
  335. package/dist/chunk-CEH4MRCC.mjs +0 -2
  336. package/dist/chunk-CY2FNNAB.mjs +0 -16
  337. package/dist/chunk-DFUDV4LX.mjs +0 -2
  338. package/dist/chunk-DKXG4XDG.mjs +0 -2
  339. package/dist/chunk-EZQ34M3F.mjs +0 -124
  340. package/dist/chunk-EZY3WC6Z.mjs +0 -15
  341. package/dist/chunk-FUKQNOQS.mjs +0 -9
  342. package/dist/chunk-JPOSLGOC.mjs +0 -6
  343. package/dist/chunk-L3QKH4OK.mjs +0 -26
  344. package/dist/chunk-LAE5NQ4B.mjs +0 -2
  345. package/dist/chunk-LEF6DRCK.mjs +0 -59
  346. package/dist/chunk-NO5NJCHO.mjs +0 -2
  347. package/dist/chunk-NQPVAU63.mjs +0 -2
  348. package/dist/chunk-NSKUTQ5J.mjs +0 -5
  349. package/dist/chunk-O35FIXRB.mjs +0 -2
  350. package/dist/chunk-P2CAZPDP.mjs +0 -3
  351. package/dist/chunk-PL6GHLGD.mjs +0 -15
  352. package/dist/chunk-QJIR2BLP.mjs +0 -6
  353. package/dist/chunk-QUPLVPB2.mjs +0 -7
  354. package/dist/chunk-R7BNENLY.mjs +0 -4
  355. package/dist/chunk-S6O4UHLC.mjs +0 -2
  356. package/dist/chunk-SGJWQKEI.mjs +0 -2
  357. package/dist/chunk-TX6JH7TR.mjs +0 -2
  358. package/dist/chunk-TYK6VNAB.mjs +0 -10
  359. package/dist/chunk-UALRNVSE.mjs +0 -4
  360. package/dist/chunk-V3HXVQEM.mjs +0 -2
  361. package/dist/chunk-V5VXUJKG.mjs +0 -2
  362. package/dist/chunk-VBXMADMP.mjs +0 -2
  363. package/dist/chunk-VNV55RUA.mjs +0 -2
  364. package/dist/chunk-VPD2PS35.mjs +0 -2
  365. package/dist/chunk-W4VKN3GF.mjs +0 -7
  366. package/dist/chunk-W5BXXPDJ.mjs +0 -3
  367. package/dist/chunk-XUNHRCOG.mjs +0 -6
  368. package/skill/references/building-components.md +0 -367
  369. package/web/assets/index-Bh74DfsV.css +0 -1
  370. package/web/assets/index-UW0RyFFt.js +0 -13
@@ -29,11 +29,13 @@ import { unpack } from "./sync-state.mjs";
29
29
  // (settled) and the reader pauses, the page reloads to show it, where the reader was
30
30
  // (reloadView). Only when that write put texts the reader committed back as they were (an Agent
31
31
  // writing the whole file from what it read earlier) does the page ask first: it keeps the texts it
32
- // committed (commits.committed(), and those of its earlier loads in sessionStorage), the blocks it
33
- // made and moved this load (commits.placedUnits()) and what its components wrote (commits.written(),
34
- // those of earlier loads too), and the server says which the file now has as before (POST
35
- // /__gallery/edit/reverted): a text as it was, a block it made gone, a block it moved back in the unit
36
- // it was in, a part as before a write of the page's.
32
+ // committed (commits.committed(), and those of its earlier loads: the journal), the blocks it
33
+ // made and moved this load (commits.placedUnits(), and those earlier loads made) and what its
34
+ // components wrote (commits.written(), those of earlier loads too), and the server says which the
35
+ // file now has as before (POST /__gallery/edit/reverted): a text as it was, a block it made gone, a
36
+ // block it moved back in the unit it was in, a part as before a write of the page's. A page going away
37
+ // keeps all of it in localStorage (journalKey) with the version of the file it last knew; the next
38
+ // page of the document, in this tab or another, opening on another version, asks at once.
37
39
 
38
40
  const IDLE = 1600;
39
41
  const LONGEST = 8000;
@@ -41,6 +43,7 @@ const RETRY = [1000, 2000, 5000, 10000, 30000];
41
43
  const QUIET = 1500; // how long the reader has changed nothing before the page reloads by itself
42
44
  const STORE_EVERY = 1000; // the crash buffer while typing, at most this often
43
45
  const JOURNAL_FOR = 60 * 60 * 1000; // how long a committed text is checked for being put back
46
+ const JOURNAL_SIZE = 100_000; // the most characters one document's journal keeps (the newest entries)
44
47
  const OWN_FOR = 5000; // how long the live-reload channel may still announce a version this page wrote
45
48
  // A page of the Agent's own (its address's ?agent: the Agent 自用 one gallery open prints, gallery
46
49
  // component try's): its commits say so (`agent`), and are no reader's edit to the review of the
@@ -52,6 +55,33 @@ const AGENT_PAGE = typeof location !== "undefined" && new URLSearchParams(locati
52
55
  // names it but the words it had.
53
56
  const sameText = (block) => JSON.stringify(block?.title ? "title" : block?.unit ? [block.unit.id ?? block.unit.blockId ?? [block.unit.key, block.unit.open], block.self ? true : block.path ?? null] : { ...block, before: undefined });
54
57
 
58
+ // The journal's texts (journal()) with those of commits not answered yet (`requests`: on their way, or
59
+ // the crash buffer's) taken as committed, as committedText (commit.mjs) takes a commit's: what each
60
+ // replaced first stays where the text started, what it was between is `earlier`, and one back as it
61
+ // was first and nothing between is none; committed now (`at`).
62
+ function folded(texts, requests) {
63
+ const named = new Map(texts.map((entry) => [sameText(entry.block), entry]));
64
+ let serial = 0;
65
+ for (const request of requests) {
66
+ for (const text of request?.texts ?? []) {
67
+ const mine = text?.parts?.find((part) => part.keep)?.html;
68
+ if (typeof mine !== "string" || !text.block) continue;
69
+ const name = sameText(text.block);
70
+ const was = named.get(name);
71
+ const original = was?.original ?? text.block.before;
72
+ if (typeof original !== "string" || (was && was.mine === mine)) continue;
73
+ const earlier = [...new Set([...(was?.earlier ?? []), ...(was ? [was.mine] : [])])].filter((each) => each !== mine && each !== original).slice(-TEXTS_KEPT);
74
+ if (mine === original && earlier.length === 0) {
75
+ named.delete(name);
76
+ continue;
77
+ }
78
+ const entry = { key: was?.key ?? `f${serial++}`, block: { ...text.block, before: mine }, original, mine, ...(earlier.length > 0 ? { earlier } : {}), ...(was?.time ? { time: was.time } : {}), at: Date.now() };
79
+ named.set(name, entry);
80
+ }
81
+ }
82
+ return [...named.values()];
83
+ }
84
+
55
85
  /**
56
86
  * map: the page's map (slug, version, token); commits: createCommits(); blocked(): commits wait
57
87
  * (an input method is composing); changedAt(): when the reader last changed anything; holding(asked):
@@ -62,14 +92,14 @@ const sameText = (block) => JSON.stringify(block?.title ? "title" : block?.unit
62
92
  * warn("storage"): the crash buffer could not be written; storage: localStorage
63
93
  * (get/set/remove/keys); tab(): this tab's id (a duplicated tab takes a new one); notice(): what the
64
94
  * page says after it reloads by itself (a merge it just made), or null; session: sessionStorage
65
- * (get/set/remove); reverted(entries): texts, blocks and components' writes of the reader's that
95
+ * (get/remove), read once for the journal an earlier Gallery kept there; reverted(entries): texts, blocks and components' writes of the reader's that
66
96
  * someone else's write put back, for the reader to decide on (decide) before the page shows that
67
97
  * write (a component's with `written`: the parts, by name, and the hashes the file holds of them);
68
- * before(): called right
98
+ * listed(): words were added to what the page could not put back (lostKey), to be shown; before(): called right
69
99
  * before a commit goes, a promise it waits for (or null): what goes with the edit elsewhere is told
70
100
  * first (where the words of the reader's 批注 went: annotate/ sync).
71
101
  */
72
- export function createAutosave({ map, commits, blocked, changedAt, holding = () => false, refresh, applied, failed, warn = () => {}, storage, tab, notice = () => null, session = null, reverted = () => {}, before = () => null }) {
102
+ export function createAutosave({ map, commits, blocked, changedAt, holding = () => false, refresh, applied, failed, warn = () => {}, storage, tab, notice = () => null, session = null, reverted = () => {}, listed = () => {}, before = () => null }) {
73
103
  let known = map.version; // the file's version as the page last knew it
74
104
  // Versions this page's commits wrote (and the one it opened on), in order, and when: the
75
105
  // live-reload channel announces them too, a little later, in the order they were written. Only
@@ -100,17 +130,39 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
100
130
  let unstored = false; // the crash buffer could not be written
101
131
  let storedFrom = null; // what the page held (collect()'s result) when the buffer was last written with nothing on its way; null: write it anew
102
132
  const events = []; // [version, when]: what the live-reload channel announced while a commit was on its way
103
- // Texts committed by this page's earlier loads (journalKey), still checked for a while, each under a
104
- // key of its own (`c<n>`: an earlier load's keys name its blocks, as this one's name others); what
105
- // their components wrote is the commits' own again (carry), by instance.
106
- const journalKey = `gallery-edit:${map.slug}:journal`;
133
+ // What earlier pages of the document committed, made and wrote (the journal, journalKey: what each
134
+ // page kept as it went away, by tab), taken by this one as its own: the texts, still checked for an
135
+ // hour, each under a key of its own (`c<n>`: an earlier load's keys name its blocks, as this one's
136
+ // name others); the blocks they made (`m<n>`: by the file's id, with their words), until the file is
137
+ // found without one (listed) or the page no longer shows one it was found with (the reader deleted
138
+ // it); what their components wrote, the commits' own again (carry), by instance. This tab's are taken
139
+ // as the page is made, those of tabs no longer open once it knows which are (opened).
107
140
  let carried = [];
141
+ let carriedMade = [];
142
+ let textSerial = 0;
143
+ let madeSerial = 0;
144
+ // The version of the file the page last knew: every write up to it the page's own or asked of (the
145
+ // revert check answered about it, what it put back held for the reader). What the page keeps as it
146
+ // goes away says it; null while a question about what earlier pages kept is not answered yet.
147
+ let seen = map.version;
148
+ let asking = false; // what earlier pages kept, taken, is to be asked of (the file not the version they last knew)
149
+ const adopt = (departures) => {
150
+ let ask = false;
151
+ for (const { known, entries } of departures) {
152
+ if (known !== map.version) ask = true;
153
+ carried.push(...entries.filter((entry) => entry.instance === undefined && entry.made === undefined && entry.block).map((entry) => ({ ...entry, key: `c${textSerial++}` })));
154
+ carriedMade.push(...entries.filter((entry) => typeof entry.made === "string" && !carriedMade.some((other) => other.id === entry.made))
155
+ .map(({ made, parent, text, time }) => ({ key: `m${madeSerial++}`, id: made, parent: parent ?? null, text: String(text ?? ""), time, confirmed: known === map.version })));
156
+ commits.carry(entries.filter((entry) => entry.instance !== undefined).map(({ instance, parts, time }) => ({ id: instance, parts, time })));
157
+ }
158
+ if (ask) {
159
+ asking = true;
160
+ seen = null;
161
+ }
162
+ };
108
163
  try {
109
- const kept = (JSON.parse(session?.get(journalKey) ?? "[]") ?? []).filter((entry) => entry && Date.now() - entry.time < JOURNAL_FOR);
110
- carried = kept.filter((entry) => entry.instance === undefined).map((entry, index) => ({ ...entry, key: `c${index}` }));
111
- commits.carry(kept.filter((entry) => entry.instance !== undefined).map(({ instance, parts, time }) => ({ id: instance, parts, time })));
164
+ adopt([...legacyJournal(session, map.slug), ...takeJournal(storage, map.slug, (id) => id === tab())]);
112
165
  } catch {}
113
- session?.remove(journalKey);
114
166
  const reverts = new Map(); // key -> entry: put back by someone else, waiting for the reader
115
167
  let checking = false;
116
168
  const waiters = [];
@@ -231,8 +283,11 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
231
283
  retryAt = 0;
232
284
  offline = false;
233
285
  known = receipt.version;
286
+ // Written over the version the page last knew, no one else's write between: the page knows this one.
287
+ if (!receipt.foreign && seen !== null && sent.request.known === seen) seen = receipt.version;
234
288
  if (receipt.wrote !== false) own.push({ version: receipt.version, at: Date.now() }); // one that changed nothing wrote no version
235
289
  if (receipt.foreign) foreign = true;
290
+ deleted(sent.request, receipt);
236
291
  applied(commits.apply(sent.pending, receipt, { alone: !foreign }), receipt);
237
292
  after = commits.collect();
238
293
  recount(after);
@@ -339,7 +394,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
339
394
  if (closed) return;
340
395
  leaving = true;
341
396
  store();
342
- keepJournal();
397
+ keepJournal([flight?.request ?? parked?.request, buffer]); // what is on its way or in the buffer, as the file is to have it
343
398
  if (flight || parked || stopped || waiting() === 0 || !buffer) return; // what is on its way is in the buffer
344
399
  postCommit(map, buffer, { keepalive: true }).catch(() => {});
345
400
  }
@@ -392,18 +447,40 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
392
447
  }
393
448
  const texts = journal();
394
449
  const units = commits.placedUnits();
450
+ // The blocks earlier pages made: those the file was not yet found with, and those the page shows (one
451
+ // found with it and no longer shown, the reader deleted).
452
+ const made = carriedMade.filter((entry) => !entry.confirmed || commits.unitOf(entry.id) !== null);
395
453
  const instances = commits.written();
396
- if (texts.length === 0 && units.length === 0 && instances.length === 0) return true;
454
+ if (texts.length === 0 && units.length === 0 && made.length === 0 && instances.length === 0) return true;
397
455
  checking = true;
398
456
  let answer = null;
399
457
  try {
400
- const response = await postJson("/__gallery/edit/reverted", map.token, { slug: map.slug, opening: map.version, texts: texts.map(({ key, block, original, earlier }) => ({ key, block, original, ...(earlier ? { earlier } : {}) })), units, instances: instances.map(({ key, id, parts }) => ({ key, id, parts })), pageIds: commits.pageIds() });
458
+ const response = await postJson("/__gallery/edit/reverted", map.token, { slug: map.slug, opening: map.version, texts: texts.map(({ key, block, original, earlier }) => ({ key, block, original, ...(earlier ? { earlier } : {}) })), units: [...units, ...made.map(({ key, id, parent }) => ({ key, id, parent }))], instances: instances.map(({ key, id, parts }) => ({ key, id, parts })), pageIds: commits.pageIds() });
401
459
  answer = await response.json();
402
460
  } catch {}
403
461
  checking = false;
404
462
  if (closed) return false;
463
+ if (answer?.ok) {
464
+ // A block an earlier page made that the file no longer has is no block of this load's to hold a
465
+ // card for: its words are listed (lostKey), as what a page could not put back is; the others are
466
+ // found with the file, asked of while the page shows them.
467
+ const gone = made.filter(({ key }) => (answer.units ?? []).some((entry) => entry.key === key && entry.gone));
468
+ // One made inside another gone (a quote's paragraph, a list's item) went with it, its words among that one's.
469
+ const goneIds = new Set(gone.map(({ id }) => id));
470
+ const words = gone.filter(({ text, parent }) => text && !goneIds.has(parent)).map(({ text }) => ({ why: "reverted", text }));
471
+ const kept = words.length === 0 || keepLost(storage, map.slug, words);
472
+ if (kept) carriedMade = carriedMade.filter((entry) => !gone.includes(entry)); // asked again otherwise
473
+ for (const entry of made) if (!gone.includes(entry)) entry.confirmed = true;
474
+ if (kept && words.length > 0) listed();
475
+ // All of it asked of (no question waits to be asked again, which may carry more): the page knows the
476
+ // file as it was answered about.
477
+ if (kept && !recheck) {
478
+ seen = answer.version ?? null;
479
+ asking = false;
480
+ }
481
+ }
405
482
  // Of the blocks it made that the write took out, those inside another such go with it, one card.
406
- const goneUnits = (answer?.units ?? []).filter(({ gone }) => gone).map(({ key }) => key);
483
+ const goneUnits = (answer?.units ?? []).filter(({ gone, key }) => gone && !made.some((entry) => entry.key === key)).map(({ key }) => key);
407
484
  const back = answer?.ok ? [
408
485
  ...answer.reverted.map(({ key, theirs }) => ({ ...texts.find((entry) => entry.key === key), theirs })).filter((entry) => entry.key),
409
486
  // A block put back: by its unit, with what it holds (the reader's words, to copy) and, taken out, the
@@ -457,19 +534,22 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
457
534
  storage.remove(bufferKey());
458
535
  storage.remove(keptKey());
459
536
  storedFrom = null; // written anew if anything stores after all
460
- keepJournal();
537
+ keepJournal(); // a write landing after that answer and before the page shown again is served is asked of there (opened)
461
538
  refresh();
462
539
  reloadView(map.slug, null, notice());
463
540
  }
464
541
 
465
- // The texts committed, this load's and earlier ones', one of each block (sameText): what an earlier
466
- // load committed of a block a later one committed too is where that text started, and what it was
467
- // between (two of one block are no one question: the server's answer would name neither); back as it
468
- // was first, it is asked of what it was between.
542
+ // The texts committed, this load's and earlier ones', one of each block (sameText): what was committed
543
+ // of a block before (`at`, when it was last committed: two tabs' pages are taken in no order of their
544
+ // own) is where that text started, and what it was between (two of one block are no one question: the
545
+ // server's answer would name neither); back as it was first, it is asked of what it was between. Joined
546
+ // with one of this load's, it goes by that one's key and naming (decide lets this load's go by its key).
469
547
  const journal = () => {
470
548
  const now = commits.committed().map((entry) => ({ ...entry, time: Date.now() }));
549
+ const own = new Set(now.map((entry) => entry.key));
471
550
  const texts = new Map();
472
- for (const entry of [...carried, ...now]) {
551
+ const when = (entry) => entry.at ?? entry.time ?? 0;
552
+ for (const entry of [...carried, ...now].sort((a, b) => when(a) - when(b))) {
473
553
  const name = sameText(entry.block);
474
554
  const was = texts.get(name);
475
555
  if (!was) {
@@ -477,7 +557,8 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
477
557
  continue;
478
558
  }
479
559
  const earlier = [...new Set([...(was.earlier ?? []), was.mine, entry.original, ...(entry.earlier ?? [])])].filter((text) => text !== entry.mine && text !== was.original).slice(-TEXTS_KEPT);
480
- const joined = { ...entry, original: was.original, earlier };
560
+ const named = own.has(was.key) && !own.has(entry.key) ? { key: was.key, block: { ...was.block, before: entry.mine } } : {};
561
+ const joined = { ...entry, ...named, original: was.original, earlier, time: Math.max(was.time ?? 0, entry.time ?? 0) || undefined };
481
562
  if (earlier.length === 0) delete joined.earlier;
482
563
  if (entry.mine === was.original && earlier.length === 0) texts.delete(name);
483
564
  else texts.set(name, joined);
@@ -485,16 +566,73 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
485
566
  return [...texts.values()];
486
567
  };
487
568
 
488
- // What the page committed (journal()) and what its components wrote (commits.written()), kept for the
489
- // next load of the page in this tab, which asks of them as its own: when it is shown again, and when
490
- // the page goes away (a reload of the reader's, another document opened in a workbench).
491
- function keepJournal() {
492
- const kept = [
493
- ...journal().map((entry) => ({ ...entry, time: entry.time ?? Date.now() })),
569
+ // A commit of the page's that the file took deleted blocks (`request`'s removals, answered `receipt`): of
570
+ // the blocks earlier pages made, those (and those made inside them) are the reader's own deletion, asked
571
+ // of by no page any more: this one's, and those kept by pages of tabs gone that no page took yet.
572
+ function deleted(request, receipt) {
573
+ const ids = (request.units ?? []).filter((op) => op.remove && !(receipt.units ?? []).some((answer) => answer.key === op.key && answer.status === "conflict"))
574
+ .flatMap((op) => [op.unit?.id, ...(op.items ?? []).map((item) => item?.id)]).filter((id) => typeof id === "string");
575
+ if (ids.length === 0) return;
576
+ const gone = madeWithin(ids, carriedMade.map(({ id, parent }) => ({ id, parent })));
577
+ carriedMade = carriedMade.filter((entry) => !gone.has(entry.id));
578
+ try {
579
+ forgetMade(storage, map.slug, ids);
580
+ } catch {}
581
+ }
582
+
583
+ // What the page committed (journal()), made (its blocks the file has, by their ids, with their words:
584
+ // this load's and those of earlier loads it still shows or has not found the file with yet) and what
585
+ // its components wrote (commits.written()), kept in the journal under this tab with the version of the
586
+ // file it last knew (`known`, null when it does not), for the next page of the document, in this tab
587
+ // or another, which takes them as its own, and asks of them at once if it opens on another version
588
+ // (opened): when the page goes to show the file again, when it goes away (a reload of the reader's,
589
+ // another document opened, the tab closed) and after a write the reader asked for (showOwn). `pending`:
590
+ // commits not answered yet (on their way, or the crash buffer's, which the next page sends), whose
591
+ // texts are taken as committed, the reader's own words (an undo back to what an earlier commit
592
+ // replaced) being no one else's write.
593
+ function keepJournal(pending = []) {
594
+ const time = Date.now();
595
+ const made = new Map();
596
+ for (const { key, id, parent, from } of commits.placedUnits()) if (from === undefined && !reverts.has(`p${key}`)) made.set(id, { made: id, parent, text: commits.unitText(key), time });
597
+ for (const entry of carriedMade) {
598
+ const unit = commits.unitOf(entry.id);
599
+ if (made.has(entry.id) || (entry.confirmed && unit === null)) continue;
600
+ made.set(entry.id, { made: entry.id, parent: entry.parent, text: (unit !== null && commits.unitText(unit)) || entry.text, time: entry.time ?? time });
601
+ }
602
+ const entries = [
603
+ ...folded(journal(), pending.filter(Boolean)).map((entry) => ({ ...entry, time: entry.time ?? time })),
494
604
  ...commits.written().map(({ id, parts, time }) => ({ instance: id, parts, time })),
605
+ ...made.values(),
495
606
  ];
496
- if (kept.length > 0) session?.set(journalKey, JSON.stringify(kept));
497
- else session?.remove(journalKey);
607
+ try {
608
+ keepDeparture(storage, map.slug, tab(), entries.length > 0 ? { known: seen, time, entries } : null);
609
+ } catch {}
610
+ }
611
+
612
+ /**
613
+ * The page opened and edits, the workbench told it is ready (`open`: the ids of the tabs that answered
614
+ * they show the document now). What pages of tabs no longer open kept is taken too (the document
615
+ * opened again in another tab), and where this page opened on another version of the file than an
616
+ * earlier page last knew (someone else's write landing while no page was there to hear of it, or
617
+ * between the last question of a page showing the file again and this page being served, which it
618
+ * never hears of as a write), all it took is asked of at once (checkReverted): a text put back is a
619
+ * card, a block made that the file no longer has is listed. On the very version, nothing is asked.
620
+ * A page done with, or going to be shown again (it kept its journal already), takes nothing: the next
621
+ * page does; one whose write (showOwn) failed takes it then.
622
+ */
623
+ let unopened = null; // opened()'s call while the page was to be shown again by showOwn: { open }
624
+ function opened(open = null) {
625
+ if (closed) return;
626
+ if (reloading) return void (unopened = { open });
627
+ if (open) {
628
+ try {
629
+ adopt(takeJournal(storage, map.slug, (id) => id !== tab() && !open.has(id)));
630
+ } catch {}
631
+ forgetOld(storage);
632
+ }
633
+ if (!asking) return;
634
+ if (checking) recheck = true;
635
+ else checkReverted();
498
636
  }
499
637
 
500
638
  /**
@@ -534,7 +672,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
534
672
 
535
673
  /**
536
674
  * A write the reader asked for that changes the file (撤回 of the Agent's changes and its 撤销:
537
- * changes/): `run()` makes it, resolving with whether it did; then the page shows the file as after a
675
+ * changes/): `run()` makes it, resolving with whether it did (the version it wrote, when known); then the page shows the file as after a
538
676
  * write of its own, at once, asking nothing of what it put back (the revert check: the reader asked
539
677
  * for it), and what is heard meanwhile shows nothing first. The caller saved what waited first
540
678
  * (flush) and made sure nothing waits for the reader. Resolves with whether the page goes.
@@ -547,14 +685,22 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
547
685
  missed = false;
548
686
  clearTimeout(reloadTimer);
549
687
  let done = false;
688
+ let wrote = null; // the version its write left, when run() says
550
689
  try {
551
- done = Boolean(await run());
690
+ const result = await run();
691
+ done = Boolean(result);
692
+ if (typeof result === "string") wrote = result;
552
693
  } catch {}
553
694
  owning = false;
554
695
  if (closed) return done;
555
696
  if (!done) {
556
697
  reloading = false;
557
698
  refresh();
699
+ if (unopened) {
700
+ const { open } = unopened;
701
+ unopened = null;
702
+ opened(open);
703
+ }
558
704
  if (missed) heard(null, true);
559
705
  else if (foreign) maybeReload();
560
706
  return false;
@@ -562,6 +708,10 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
562
708
  storage.remove(bufferKey());
563
709
  storage.remove(keptKey());
564
710
  storedFrom = null;
711
+ // The page knew the file it asked to be written as; a write landing before the page shown again is
712
+ // served is asked of there. Not told which version that is, or with a question of its own not answered
713
+ // yet, the next page asks whatever it opens on.
714
+ seen = asking ? null : wrote;
565
715
  keepJournal();
566
716
  refresh();
567
717
  reloadView(map.slug, null, null);
@@ -597,6 +747,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
597
747
  changed,
598
748
  flush,
599
749
  leave,
750
+ opened,
600
751
  heard,
601
752
  store,
602
753
  decide,
@@ -692,6 +843,128 @@ function keepLost(storage, slug, items) {
692
843
  return storage.set(lostKey(slug), JSON.stringify([...readLost(storage, slug), ...items.map((item) => ({ ...item, time }))].slice(-100))) !== false;
693
844
  }
694
845
 
846
+ /**
847
+ * Where the pages of a document keep, as they go away, what they committed, made and wrote, for the
848
+ * next page of it to ask of (createAutosave: keepJournal, opened), in localStorage: { v: 1, tabs: {
849
+ * <tab>: { known, time, entries } } }, one departure a tab, `known` the version of the file that page
850
+ * last knew (null: it did not), each entry with its `time`: a text ({ key, block, original, mine,
851
+ * earlier?, at }, `at` when it was last committed), a block made ({ made, parent, text }; taken out of
852
+ * every departure once a page's commit deletes it: forgetMade) or what a component wrote ({ instance, parts }).
853
+ * Entries are kept for JOURNAL_FOR, the newest of them JOURNAL_SIZE characters at most.
854
+ */
855
+ export const journalKey = (slug) => `gallery-edit:${slug}:journal`;
856
+ const JOURNAL_KEY = /^gallery-edit:(.+):journal$/;
857
+
858
+ // The departures the journal holds, by tab, their entries still asked of (none left: no departure).
859
+ function readJournal(storage, slug) {
860
+ let record = null;
861
+ try {
862
+ record = JSON.parse(storage.get(journalKey(slug)) ?? "null");
863
+ } catch {}
864
+ const tabs = {};
865
+ const now = Date.now();
866
+ for (const [tab, departure] of Object.entries(record?.tabs && typeof record.tabs === "object" ? record.tabs : {})) {
867
+ const entries = (Array.isArray(departure?.entries) ? departure.entries : []).filter((entry) => entry && typeof entry === "object" && now - entry.time < JOURNAL_FOR);
868
+ if (entries.length > 0) tabs[tab] = { known: typeof departure.known === "string" ? departure.known : null, time: departure.time, entries };
869
+ }
870
+ return tabs;
871
+ }
872
+
873
+ // The journal written as `tabs`, the newest entries first as long as they fit (one too long left out),
874
+ // or taken away when nothing is left. A write that fails leaves it as it was: the crash buffer comes first.
875
+ function writeJournal(storage, slug, tabs) {
876
+ const all = Object.entries(tabs).flatMap(([tab, departure]) => departure.entries.map((entry) => ({ tab, entry, size: JSON.stringify(entry).length })))
877
+ .sort((a, b) => b.entry.time - a.entry.time);
878
+ let room = JOURNAL_SIZE;
879
+ const fits = new Set();
880
+ for (const { entry, size } of all) {
881
+ if (size > room) continue;
882
+ room -= size;
883
+ fits.add(entry);
884
+ }
885
+ const kept = {};
886
+ for (const [tab, departure] of Object.entries(tabs)) {
887
+ const entries = departure.entries.filter((entry) => fits.has(entry));
888
+ if (entries.length > 0) kept[tab] = { ...departure, entries };
889
+ }
890
+ if (Object.keys(kept).length === 0) return void storage.remove(journalKey(slug));
891
+ const text = JSON.stringify({ v: 1, tabs: kept });
892
+ if (storage.get(journalKey(slug)) !== text) storage.set(journalKey(slug), text);
893
+ }
894
+
895
+ // The departures of the tabs `which` picks, taken out of the journal: [{ known, entries }].
896
+ function takeJournal(storage, slug, which) {
897
+ const tabs = readJournal(storage, slug);
898
+ const taken = Object.keys(tabs).filter(which);
899
+ if (taken.length === 0) return [];
900
+ const departures = taken.map((tab) => tabs[tab]);
901
+ for (const tab of taken) delete tabs[tab];
902
+ writeJournal(storage, slug, tabs);
903
+ return departures;
904
+ }
905
+
906
+ // The ids of `ids` and of the blocks made (`made`: [{ id, parent }]) inside one of them, at any depth.
907
+ function madeWithin(ids, made) {
908
+ const gone = new Set(ids);
909
+ for (let more = true; more;) {
910
+ more = false;
911
+ for (const { id, parent } of made) {
912
+ if (gone.has(id) || !gone.has(parent)) continue;
913
+ gone.add(id);
914
+ more = true;
915
+ }
916
+ }
917
+ return gone;
918
+ }
919
+
920
+ // The blocks made that the reader deleted (`ids`, and those made inside them) taken out of every
921
+ // departure the journal holds.
922
+ function forgetMade(storage, slug, ids) {
923
+ const tabs = readJournal(storage, slug);
924
+ let changed = false;
925
+ for (const departure of Object.values(tabs)) {
926
+ const gone = madeWithin(ids, departure.entries.filter((entry) => typeof entry.made === "string").map(({ made, parent }) => ({ id: made, parent })));
927
+ const entries = departure.entries.filter((entry) => typeof entry.made !== "string" || !gone.has(entry.made));
928
+ if (entries.length === departure.entries.length) continue;
929
+ departure.entries = entries;
930
+ changed = true;
931
+ }
932
+ if (changed) writeJournal(storage, slug, tabs);
933
+ }
934
+
935
+ // This tab's departure written (null: none), beside the other tabs'.
936
+ function keepDeparture(storage, slug, tab, departure) {
937
+ const tabs = readJournal(storage, slug);
938
+ if (departure) tabs[tab] = departure;
939
+ else if (tabs[tab]) delete tabs[tab];
940
+ else if (storage.get(journalKey(slug)) === null) return;
941
+ writeJournal(storage, slug, tabs);
942
+ }
943
+
944
+ // What a Gallery before 2026-11-08 kept for the next load in the tab (sessionStorage, under the same
945
+ // key): its entries, or { asked, entries }, read once as this tab's departure, the version it last knew
946
+ // that `asked` (none of "own" or nothing: not known).
947
+ function legacyJournal(session, slug) {
948
+ if (!session) return [];
949
+ let stored = null;
950
+ try {
951
+ stored = JSON.parse(session.get(journalKey(slug)) ?? "null");
952
+ } catch {}
953
+ session.remove(journalKey(slug));
954
+ const entries = (Array.isArray(stored) ? stored : Array.isArray(stored?.entries) ? stored.entries : []).filter((entry) => entry && typeof entry === "object" && Date.now() - entry.time < JOURNAL_FOR);
955
+ const known = !Array.isArray(stored) && typeof stored?.asked === "string" && stored.asked !== "own" ? stored.asked : null;
956
+ return entries.length > 0 ? [{ known, entries }] : [];
957
+ }
958
+
959
+ // The journals of documents whose every entry is older than JOURNAL_FOR, taken away (a document not
960
+ // opened again within the hour leaves none behind).
961
+ function forgetOld(storage) {
962
+ for (const key of storage.keys?.() ?? []) {
963
+ const slug = JOURNAL_KEY.exec(key)?.[1];
964
+ if (slug && Object.keys(readJournal(storage, slug)).length === 0) storage.remove(key);
965
+ }
966
+ }
967
+
695
968
  /**
696
969
  * Reload the page to show the file as it is now, keeping where the reader was (`view`, or now):
697
970
  * the scroll position, the block the caret was in (by its data-block-id) with the caret's offset in
@@ -0,0 +1,57 @@
1
+ import { postAnswer } from "../post.mjs";
2
+
3
+ // What a custom page's board editing sends the page service (POST /__gallery/pages/<slug>/cards:
4
+ // src/editor/page-service.mjs), one task at a time, in the order asked. A task is the whole of what the
5
+ // page does for one gesture or one step of undo: what it sends, built only once its turn comes, and what
6
+ // it does with the answer (each card's cells and base taken in, the undo step pushed or moved), all done
7
+ // before the next task starts. So a write sent while one before it waits for its answer goes with the
8
+ // bases that answer gave (a block's two context.edit calls in a row, two quick Ctrl+Z on one card, a
9
+ // Ctrl+Z pressed before a drop is answered, which undoes the drop), never with those it had when it was
10
+ // asked, which the service would refuse as changed. It knows which versions of the page are its own (the
11
+ // one it was served at, and each its ops wrote), so that the live script's "page <version>" of its own
12
+ // write changes nothing, and it says when an answer was applied to a version that is none of them:
13
+ // someone else wrote the page meanwhile (`others`), which the page then shows again.
14
+
15
+ /**
16
+ * slug, token, version: the page map's. others(): someone else wrote the page (an op's answer was
17
+ * applied to another version than the page's). Returns { run(task) (task(send) run once every task asked
18
+ * before it is done, `send(body)` giving the service's answer: { ok, … }, its refusal, or { ok: false,
19
+ * reason: "offline" } where it cannot be reached; resolves with what the task gives), ours(version), busy,
20
+ * idle() (a promise of the tasks asked so far being done) }.
21
+ */
22
+ export function createClient({ slug, token, version, others = () => {} }) {
23
+ const route = `/__gallery/pages/${slug}/cards`;
24
+ const ours = new Set([version]);
25
+ let current = version;
26
+ let waiting = 0;
27
+ let chain = Promise.resolve();
28
+ const send = async (body) => {
29
+ const answer = await postAnswer(route, token, body);
30
+ if (answer?.ok) {
31
+ const before = answer.before;
32
+ ours.add(answer.version);
33
+ if (before !== current && !ours.has(before)) others();
34
+ current = answer.version;
35
+ }
36
+ return answer;
37
+ };
38
+ const run = (task) => {
39
+ waiting++;
40
+ const done = chain.then(() => task(send)).finally(() => {
41
+ waiting--;
42
+ });
43
+ chain = done.catch(() => {}); // a task that threw is its asker's to hear of; the next goes on
44
+ return done;
45
+ };
46
+ return {
47
+ run,
48
+ /** Whether `version` is the page's own: served at it, or written to it by an op. */
49
+ ours: (version) => ours.has(version),
50
+ /** Whether a task is asked and not yet done (sent, answered and taken in). */
51
+ get busy() {
52
+ return waiting > 0;
53
+ },
54
+ /** Once every task asked so far is done. */
55
+ idle: () => chain,
56
+ };
57
+ }