@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,110 @@
1
+ import { startBoard } from "./board/index.mjs";
2
+ import { relayTo } from "./notices.mjs";
3
+ import { connectWorkbench } from "./session.mjs";
4
+
5
+ // A custom page inside a workbench (src/server/routes.mjs serves it at /pages/<slug> with this module;
6
+ // docs/workbench.md "Custom pages"): no document editor, the session, by which the workbench shows it
7
+ // as it shows a document's page, and its boards' editing (./board/: the cards moved, resized, added,
8
+ // taken out, their settings and their blocks' writes, through the page service, by the page map the
9
+ // server writes in its head), which shows the page again only once nothing of it waits. Its session's
10
+ // slug is "@<slug>", the page's address in the workbench (#@<slug>), which no document's slug can be.
11
+ // It answers the session's own commands (theme, keys, scroll-top: src/editor/page/session.mjs),
12
+ // prepare-close (once the board's ops sent are answered: each is written at once, nothing else waits),
13
+ // add-card where it has a board (the top bar's 添加自定义块: the library of the page's first board, under the
14
+ // button's place that the workbench sends; a page with none, a blank one or one an Agent wrote with no
15
+ // board, offers no 添加自定义块) and, on a page with the default cards (its page map's `defaults`: its
16
+ // page.json's defaultCards, the default home page's), restore-defaults (恢复默认卡片, its menu's: the board's,
17
+ // one step of its undo; its answer { ok, added, skipped } the workbench says) and undo-restore ({ cards }:
18
+ // that step undone while it is the board's last, the workbench's 撤销),
19
+ // and sends ready, reload (its live script heard that page.html or page.json changed, or a block it
20
+ // holds: the workbench loads it again in a hidden frame and swaps it in once drawn), open (a link to a
21
+ // document or another page, or a block's context.workspace.open: doc-open-request) and key. Opened on its own (not in a frame of the workbench), it has no
22
+ // session: its board edits all the same, and the live script reloads it.
23
+
24
+ const slug = decodeURIComponent(location.pathname.split("/")[2] ?? "");
25
+ const IDLE = { editing: false, pending: false, saving: false, offline: false, conflicts: 0, unfit: 0, changed: false };
26
+ // Nothing stands over a custom page as a modal.
27
+ const NO_MODALS = { open: () => [], listen() {}, closeTop: () => false };
28
+
29
+ // The page map (routes.mjs: its slug, its token, the version it was served at, its blocks): the boards edit.
30
+ let map = null;
31
+ try {
32
+ map = JSON.parse(document.querySelector("script[data-ge-page]")?.textContent ?? "null");
33
+ } catch {}
34
+ const hasBoard = Boolean(document.querySelector(".gallery-board"));
35
+
36
+ let board = null;
37
+ const session = connectWorkbench({
38
+ slug: `@${slug}`,
39
+ snapshot: () => ({ title: document.title, icon: null, state: IDLE }),
40
+ commands: {
41
+ "prepare-close": async () => {
42
+ await board?.idle();
43
+ return { ok: true };
44
+ },
45
+ // 添加自定义块 of the workbench's top bar: the library opens in the page, at `anchor` (the button's place in
46
+ // this frame's coordinates; none: in the middle of the window), the first board's.
47
+ ...(hasBoard ? { "add-card": async ({ anchor } = {}) => (board ? board.addCard({ anchor }) : { ok: false, reason: "board" }) } : {}),
48
+ ...(map?.defaults ? {
49
+ "restore-defaults": async () => (board ? board.restoreDefaults() : { ok: false, reason: "board" }),
50
+ "undo-restore": async ({ cards }) => (board ? board.undoRestore(cards) : { ok: false, reason: "board" }),
51
+ } : {}),
52
+ },
53
+ modals: NO_MODALS,
54
+ });
55
+
56
+ board = map?.token ? startBoard({ map, reload: () => (session ? session.reload() : location.reload()), hosted: Boolean(session) }) : null;
57
+ // The live script (routes.mjs) shows the page again through the workbench, which swaps a new page in
58
+ // once drawn: no reload seen; once the board's own writes, a drag, a form or a field typed in are done.
59
+ if (board) window.__galleryEditReload = () => board.swap();
60
+
61
+ if (session) {
62
+ // Its notices (a card someone else changed) are the workbench's toasts, as a document's page's are.
63
+ relayTo(session.notice, session.noticeRoom);
64
+ if (!board) window.__galleryEditReload = () => session.reload();
65
+
66
+ // A block's context.workspace.open (the kit's runtime/workspace.js: 我的待办's item, 最近编辑's
67
+ // document): a document, at its block where it names one, or a custom page, opened in the workbench.
68
+ addEventListener("doc-open-request", (event) => {
69
+ const { slug = null, block = null, page = null } = event.detail ?? {};
70
+ event.preventDefault();
71
+ session.open(page ? { hash: `@${page}` } : { target: slug, block });
72
+ });
73
+
74
+ // A link to a document or a page of the workbench goes there in the workbench, never in this frame:
75
+ // a workbench's address (#<link>, #@<slug>, /#…), a document's page (/generated/<slug>.html) or its
76
+ // file (../<slug>/doc.html), a custom page's served path (/pages/<slug>, the page alone that gallery
77
+ // page open gives, or written relative to this one: "reading" from /pages/home). Another site's
78
+ // opens in a tab of its own.
79
+ addEventListener("click", (event) => {
80
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
81
+ const link = event.target instanceof Element ? event.target.closest("a[href]") : null;
82
+ if (!link || link.hasAttribute("download")) return;
83
+ const raw = link.getAttribute("href") ?? "";
84
+ // A place in this page (an element of that id) is the browser's to go to.
85
+ if (raw.startsWith("#") && raw.length > 1 && document.getElementById(decodeURIComponent(raw.slice(1)))) return;
86
+ let url;
87
+ try {
88
+ url = new URL(raw, location.href);
89
+ } catch {
90
+ return;
91
+ }
92
+ if (url.origin !== location.origin) {
93
+ event.preventDefault();
94
+ window.open(url.href, "_blank", "noopener");
95
+ return;
96
+ }
97
+ const custom = /^\/pages\/([a-z0-9-]+)\/?$/.exec(url.pathname)?.[1];
98
+ const page = /^\/generated\/([a-z0-9-]+)\.html$/.exec(url.pathname)?.[1] ?? /^\/([a-z0-9-]+)\/(?:doc|report)\.html$/.exec(url.pathname)?.[1];
99
+ if (custom) {
100
+ event.preventDefault();
101
+ session.open({ hash: `@${custom}` });
102
+ } else if (page) {
103
+ event.preventDefault();
104
+ session.open({ target: page, block: url.hash.slice(1) || null });
105
+ } else if ((url.pathname === "/" || raw.startsWith("#")) && url.hash.length > 1) {
106
+ event.preventDefault();
107
+ session.open({ hash: decodeURIComponent(url.hash.slice(1)) });
108
+ }
109
+ });
110
+ }
@@ -15,14 +15,17 @@ import { createCommits, kindOf } from "./commit.mjs";
15
15
  import { createConflicts, packageChanged, unfit } from "./conflicts.mjs";
16
16
  import { checkLeft, createAutosave, keepWith, lostKey, pendingKey, readLost, recover, reloadView, setReloader, takeView, viewNow } from "./autosave.mjs";
17
17
  import { SOLO, connectWorkbench } from "./session.mjs";
18
+ import { OUTLINE_OPEN_KEY, onOutlineOpen, outlineOpen, setOutlineOpen } from "../../shared/preferences.mjs";
18
19
  import { lookOfElement } from "../../shared/document-look.mjs";
19
20
  import { belowTargets, convertBlock, convertGroup, groupParents, groupTargets, noTextToCarry, targetsOf, touchedBy, turnInto, turnParents, turnUnit, typeOf } from "./convert.mjs";
20
21
  import { joinCode, rangeAt, rangeOffsets } from "./marks.mjs";
21
- import { addStyle, button, copyAddress, element, layer } from "./ui.mjs";
22
+ import { addStyle, copyAddress, element, layer, textButton } from "./ui.mjs";
22
23
  import { createClipboard } from "./clipboard/index.mjs";
24
+ import { serveFiles } from "./served.mjs";
23
25
  import { createFormatting } from "./format-bar/index.mjs";
24
26
  import { connectHints, hideHint, readOnlyReason, scheduleHint, trackPointer } from "./hints/index.mjs";
25
27
  import { createInsert } from "./insert/index.mjs";
28
+ import { createAfter } from "./after/index.mjs";
26
29
  import { connectLinks, dismissLink, drawPasted, followLink, leadsToPage, linkBoxNode, linkHolds, linkNodes, linkOf, linkPending, openLink, pageLinkOf } from "./links/index.mjs";
27
30
  import { bookmarkItems, connectViews, linkTitle, offerViews } from "./links/views.mjs";
28
31
  import { connectChipFills } from "./chip-fills.mjs";
@@ -35,6 +38,7 @@ import { createResize } from "./resize/index.mjs";
35
38
  import { connectPages, fieldOpen, keptByComponent, menuRow, modalOps, pageAt, pageIcon, pageObjects, pageOf, pageOfHost, pageOfType, pageOfUnit, panelOpen, typedAsIs } from "./page-objects.mjs";
36
39
  import { preloading } from "./type-grid.mjs";
37
40
  import { createColumnGaps } from "./columns/index.mjs";
41
+ import { viewFrames } from "./table/binding.mjs";
38
42
  import { createTableEditing } from "./table/index.mjs";
39
43
  import { createAtMenu } from "./at-menu/index.mjs";
40
44
  import { createSlashMenu } from "./slash-menu/index.mjs";
@@ -58,6 +62,7 @@ import { goBackTo, placeIn, unmoved } from "../../shared/reading-place.mjs";
58
62
  import { heldList, holdPages } from "./links/pages.mjs";
59
63
  import { hideBlocks } from "./offered.mjs";
60
64
  import { local, storageOf } from "./storage.mjs";
65
+ import { createTodoWrites } from "./todo-writes.mjs";
61
66
  import { language, t } from "../../shared/i18n/index.mjs";
62
67
 
63
68
  // In-place editing for the live preview (`npm run dev:local` only; never exported).
@@ -70,6 +75,7 @@ import { language, t } from "../../shared/i18n/index.mjs";
70
75
  //
71
76
  // typing/ Enter, Backspace/Delete at block edges, auto spacing, placeholders
72
77
  // insert/ new blocks: below a block (its menu), a click below the last block, a paragraph before or after
78
+ // after/ the caret after a block of no text (a click right of it): Backspace deletes it, Enter or a key a paragraph after it
73
79
  // format-bar/ the bar over selected text and its keys (marks.mjs does the marks)
74
80
  // links/ the link box (Ctrl+K), following links; hints/: why something is read-only
75
81
  // formula/ the formula box: selected text made an inline formula (Ctrl+Shift+E)
@@ -259,7 +265,9 @@ function showLanded(node) {
259
265
  modalOps().show(node);
260
266
  }
261
267
 
262
- const insert = createInsert({ struct, blocks: () => blocks });
268
+ // The caret after a block of no text, where a click right of one puts it (insert/ clickBlank).
269
+ const after = createAfter({ struct, blocks: () => blocks, keepText: (node) => blocks.keepText(node), removeBlocks: (units) => blocks.remove(units), say, undo: () => undo(), redo: () => redo() });
270
+ const insert = createInsert({ struct, blocks: () => blocks, after });
263
271
  const { newParagraph } = insert;
264
272
 
265
273
  // What a component does on the page beyond what every block does is its own page module
@@ -268,7 +276,12 @@ const { newParagraph } = insert;
268
276
 
269
277
  // The icon picker's images go into the report's folder (editor/media-store.mjs), where the server serves them.
270
278
  const assetUrl = (file) => mediaUrl("asset", MAP.slug, file);
279
+ serveFiles(assetUrl); // a table cell's pictures (单元格里的图片) show where the server serves them
271
280
  connectPicker({ slug: MAP.slug, token: MAP.token, notify: say });
281
+ // A block of the page checking a to-do of a document (context.workspace.setTodo: ./todo-writes.mjs): written
282
+ // by the workspace's service, this one's too as any write it did not make, which the page merges; no step of
283
+ // its undo (only a custom page keeps one: board/).
284
+ createTodoWrites({ token: MAP.token }).listen();
272
285
  // Whether a block tool may act: the page edits, no reload is on its way and no field of a component is being edited.
273
286
  const active = () => editing && !autosave?.state.reloading && !fieldOpen();
274
287
  // A picture or file sent to the report's folder (kind: "image", "file" or "embed"): the server's answer.
@@ -319,6 +332,8 @@ const typing = createTyping({
319
332
  adding: () => slash?.adding ?? null,
320
333
  leave: createLeaving({ struct, touchedBy, convertHost, typeOf, targetsOf }),
321
334
  showHidden: (node) => blocks?.folds.showAround(node), // a folded heading's section opened before a join takes in its words (fold/)
335
+ look: lookStep, // a block's alignment and indent taken off by Backspace (align.mjs)
336
+ afterAbove: (host) => after.back(host), // Backspace right after a block of no text: the caret after it (after/)
322
337
  });
323
338
  // Selecting text across blocks: its listeners go before the editor's own keys and clipboard.
324
339
  const crossText = createTextSelection({
@@ -372,6 +387,7 @@ const clipboard = createClipboard({
372
387
  pageLink: pageLinkOf,
373
388
  titleBold: () => formatting?.armed ?? false, // a node's empty title: what is pasted there bold, as typed
374
389
  showCaret,
390
+ pictures: () => tables?.pictures ?? null, // a table cell's (table/pictures.mjs)
375
391
  });
376
392
 
377
393
  // ---------- editing ----------
@@ -440,17 +456,19 @@ function enter() {
440
456
  conflicts.add(entries.map((entry) => ({ ...entry, key: `r:${entry.key}`, revert: entry.key, kind: entry.written ? "reverted-source" : entry.unit === undefined ? "reverted" : "reverted-unit" })));
441
457
  say(t("AI 的写入把你之前保存的 {0} 处修改改回去了:在左下方选择保留哪一边。", entries.length), "error");
442
458
  },
459
+ listed: () => showLost(), // a block the page before this one made, which someone else's write took out
443
460
  });
444
461
  conflicts = createConflicts({ act: decide, nodeOf: conflictNode, restorable, copyBlock: (node) => clipboard.copyBlocks([node]), say });
445
462
  editing = true;
446
463
  document.documentElement.setAttribute("data-ge-editing", "");
464
+ typing.untitled(); // an empty title says 未命名文档 from the start
447
465
  blocks?.folds.start(); // what was folded, folded again in this task: no frame shows it open (fold/)
448
466
  refresh();
449
467
  for (const page of pageObjects()) page.entered?.();
450
468
  }
451
469
 
452
470
  // What the page says when what a component wrote went by a manifest that changed: given up, the page shown again.
453
- const outdatedNotice = () => t("组件包刚刚变了,这次改动没有保存,页面已按新的组件重新显示。");
471
+ const outdatedNotice = () => t("自定义块刚刚变了,这次改动没有保存,页面已按新的块重新显示。");
454
472
 
455
473
  // What 仍然调整 wrote over someone else's change of what a component writes, by page unit: the step that
456
474
  // gives theirs back (installed/ overwrite), and when its write went in, all of it (commit.mjs `forced`).
@@ -465,9 +483,10 @@ const keepsOverwrite = (asked) => !asked && [...overwrites.values()].some(({ lan
465
483
 
466
484
  // A commit was taken in (commit.mjs): a block merged with someone else's words shows the merge;
467
485
  // what the file refused is flagged and held back, the rest goes on.
468
- function committed({ conflicts: held, merged, outdated, forced = [] }, receipt) {
486
+ function committed({ conflicts: held, merged, outdated, forced = [], rekeyed = [] }, receipt) {
469
487
  agentChanges?.committed(receipt);
470
488
  for (const host of merged) history.merged(host);
489
+ history.rekeyed(rekeyed); // the keys of tables the file gave otherwise, in the steps that recorded them
471
490
  for (const unit of forced) {
472
491
  const written = overwrites.get(unit);
473
492
  if (!written || written.landed > 0) continue;
@@ -485,7 +504,7 @@ function committed({ conflicts: held, merged, outdated, forced = [] }, receipt)
485
504
  conflicts.add(held);
486
505
  const conflicted = held.filter((entry) => !unfit(entry) && !packageChanged(entry)).length;
487
506
  const packaged = held.filter(packageChanged).length;
488
- say(conflicted > 0 ? t("{0} 处修改和文件的最新版本冲突,没有保存:在左下方选择怎么处理。", conflicted) : packaged > 0 ? t("组件包刚刚变了,{0} 处修改没有保存:在左下方选择怎么处理。", packaged) : t("{0} 处内容文档存不下,没有保存:在左下方选择怎么处理。", held.length), "error");
507
+ say(conflicted > 0 ? t("{0} 处修改和文件的最新版本冲突,没有保存:在左下方选择怎么处理。", conflicted) : packaged > 0 ? t("自定义块刚刚变了,{0} 处修改没有保存:在左下方选择怎么处理。", packaged) : t("{0} 处内容文档存不下,没有保存:在左下方选择怎么处理。", held.length), "error");
489
508
  }
490
509
  else if (outdated || merged.length > 0 || receipt.texts?.some((entry) => entry.status === "merged")) {
491
510
  const count = receipt.texts.filter((entry) => entry.status === "merged").length;
@@ -612,6 +631,7 @@ function markChanged(now = false) {
612
631
  conflicts?.reflag();
613
632
  }
614
633
  if (messageKind() === "error") settle(); // the reader is dealing with it
634
+ typing.untitled(); // a title emptied (or filled) by any change says 未命名文档 (or nothing) at once
615
635
  lastChange = Date.now();
616
636
  discrete ||= now;
617
637
  const counting = checkPending;
@@ -661,7 +681,7 @@ function convertHost(host, type, typed = null, after = null) {
661
681
  if (!focus && !typed) return null;
662
682
  const more = focus && after ? after(focus) : [];
663
683
  return { touched: [focus ?? host], focus: focus ?? host, created: [...created, ...more] };
664
- });
684
+ }, { attributes: viewFrames(type) });
665
685
  if (result) afterConvert(result.focus, type, result.focus !== host ? t("已转换为{0}", menuRow(type).label) : null);
666
686
  }
667
687
 
@@ -691,6 +711,8 @@ function afterConvert(focus, type, said) {
691
711
  * heading) and the new block turns into the type; below any other block, an empty paragraph of its
692
712
  * box does (the first box out that takes one). One undo step.
693
713
  */
714
+ // A view of a table (a custom block that reads tables) is bound to the table above it as it is made
715
+ // (installed/page.mjs create), the tables' keys recorded in the same step (viewFrames).
694
716
  function insertBelow(target, type, typed = null) {
695
717
  const splits = isHost(target) && can(target, "split");
696
718
  let after = target;
@@ -712,7 +734,7 @@ function insertBelow(target, type, typed = null) {
712
734
  const turned = type === "paragraph" && fresh.localName === "p" ? { focus: null, created: [] } : convertBlock(fresh, type, { fresh: true });
713
735
  const focus = turned.focus ?? fresh;
714
736
  return { touched: splits ? [target, focus] : [focus], focus, created: turned.created };
715
- });
737
+ }, { attributes: viewFrames(type) });
716
738
  if (result) afterConvert(result.focus, type, t("已插入{0}", menuRow(type).label));
717
739
  }
718
740
 
@@ -765,7 +787,7 @@ function convertUnit(unit, type, { loaded = false } = {}) {
765
787
  const turned = turnUnit(unit, type);
766
788
  emptied = Boolean(turned.emptied);
767
789
  return turned.focus ? { touched: [turned.focus], focus: turned.focus, created: turned.created, select: emptied ? null : unit } : null;
768
- });
790
+ }, { attributes: viewFrames(type) });
769
791
  if (!result) return;
770
792
  if (!made || !onCreated(type, result.focus)) {
771
793
  if (emptied && isHost(result.focus)) caretAt(result.focus, "start");
@@ -806,6 +828,7 @@ function convertAll(group, type, { select = true } = {}) {
806
828
  function onKeydown(event) {
807
829
  compositionOver(event);
808
830
  if (!editing) return;
831
+ if (after.key(event)) return; // the caret after a block: its keys are its own (after/)
809
832
  // A 批注's card is a field of its own: its keys are its own, but for Ctrl+S, which saves it and the report.
810
833
  if (annotate?.holds(event.target)) {
811
834
  const mod = event.ctrlKey || event.metaKey;
@@ -911,7 +934,8 @@ function onKeydown(event) {
911
934
  moveHostBlock(host, event.key === "ArrowUp" ? -1 : 1);
912
935
  } else if (event.key.startsWith("Arrow") && !mod && !event.altKey && !event.shiftKey && !renamed) {
913
936
  // ←/→ stop on both sides of inline code's edges (typing/code-edges.mjs); a tab's caret stays in its name.
914
- if ((textFilters(host) && codeArrow(host, event.key)) || arrow(host, event.key)) event.preventDefault();
937
+ // ← or ↑ at the start of the text right after a block of no text: the caret after that block (after/).
938
+ if ((textFilters(host) && codeArrow(host, event.key)) || after.up(host, event.key) || arrow(host, event.key)) event.preventDefault();
915
939
  }
916
940
  }
917
941
 
@@ -1160,7 +1184,8 @@ async function newSubpage(title) {
1160
1184
  }
1161
1185
 
1162
1186
  // The report's title as the reader sees it now (while editing, the title block's text).
1163
- const currentTitle = () => (document.querySelector('[data-ge-kind="title"]') ?? document.querySelector("h1.doc-title, .doc-header h1"))?.textContent.replace(/\s+/g, " ").trim() ?? "";
1187
+ const titleNode = () => document.querySelector('[data-ge-kind="title"]') ?? document.querySelector("h1.doc-title, .doc-header h1");
1188
+ const currentTitle = () => titleNode()?.textContent.replace(/\s+/g, " ").trim() ?? "";
1164
1189
 
1165
1190
  // This page, as the list of pages takes it (links/pages.mjs).
1166
1191
  const thisPage = () => ({ slug: MAP.slug, title: currentTitle() });
@@ -1225,6 +1250,15 @@ const COMMANDS = {
1225
1250
  if (!editing || !annotate) return { ok: false, reason: "editing" };
1226
1251
  return annotate.action(message);
1227
1252
  },
1253
+ // The outline (目录) switched by the workbench's 更多: a draft of another workspace is shown from that workspace's own origin,
1254
+ // whose storage the workbench cannot write (the switch is kept per origin: shared/preferences.mjs outlineOpen), so it asks and
1255
+ // this page keeps it, shown as another tab's change is (the storage event the outline listens for). Answers the switch as kept.
1256
+ async outline({ open }) {
1257
+ if (typeof open !== "boolean") return { ok: false, reason: "open" };
1258
+ setOutlineOpen(MAP.slug, open);
1259
+ dispatchEvent(new StorageEvent("storage", { key: OUTLINE_OPEN_KEY }));
1260
+ return { ok: true, outline: outlineOpen(MAP.slug) };
1261
+ },
1228
1262
  // Rename the report: its title, selected for typing (saving it updates <title> and report.json).
1229
1263
  async "edit-title"() {
1230
1264
  const title = document.querySelector('[data-ge-kind="title"]');
@@ -1454,8 +1488,21 @@ function handOff() {
1454
1488
  function connect() {
1455
1489
  // The look the page shows (字体与布局: its <html>'s, src/shared/document-look.mjs), which the workbench's
1456
1490
  // 更多 marks as chosen; told again when it changes (the kit's ui.js, doc-look-change).
1457
- workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: currentTitle(), icon: pageIcon(), look: lookOfElement(document.documentElement), state: sessionState(), annotations: annotate?.count() }), commands: COMMANDS, modals: modalOps() });
1491
+ // `title`: null for a page with no title at all (the workbench names it by its doc.json); "" an untitled one (未命名文档).
1492
+ workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: titleNode() ? currentTitle() : null, icon: pageIcon(), look: lookOfElement(document.documentElement), state: sessionState(), annotations: annotate?.count(), outline: outlineOpen(MAP.slug) }), commands: COMMANDS, modals: modalOps() });
1458
1493
  addEventListener("doc-look-change", () => workbench?.update());
1494
+ onOutlineOpen(() => workbench?.update()); // the switch changed in another tab of this origin: the workbench's 目录 follows it (a draft's)
1495
+ // A block's context.workspace.open (the kit's runtime/workspace.js): a document, or a block of one, goes
1496
+ // where a link to it goes (goTo: in the workbench, its address; on its own, the page once saved); a custom
1497
+ // page, the workbench's #@<slug>, or /pages/<slug> once saved.
1498
+ addEventListener("doc-open-request", (event) => {
1499
+ const { slug = null, block = null, page = null } = event.detail ?? {};
1500
+ event.preventDefault();
1501
+ if (page) {
1502
+ if (window.parent !== window && !SOLO) workbench?.open({ hash: `@${page}` });
1503
+ else COMMANDS["prepare-close"]().then(() => location.assign(`/pages/${page}`));
1504
+ } else goTo({ slug, block }, `/generated/${slug}.html${block ? `#${encodeURIComponent(block)}` : ""}`);
1505
+ });
1459
1506
  relayTo(workbench ? workbench.notice : null, workbench ? workbench.noticeRoom : null); // the workbench shows the page's notices, above the page's bottom panels
1460
1507
  handOff();
1461
1508
  // A page on its own in a frame (?solo: the 写作偏好 dialog's, which no session asks to save) is saved
@@ -1514,6 +1561,10 @@ async function start() {
1514
1561
  TAB = newTab();
1515
1562
  session.set(keys.tab, TAB);
1516
1563
  }
1564
+ // What earlier pages of the document committed, made and wrote (this tab's, and those of tabs no longer
1565
+ // open), asked of at once where this page opened on another version of the file than they last knew
1566
+ // (its cards and list said through the workbench).
1567
+ autosave?.opened(open);
1517
1568
  const prefix = pendingKey(MAP.slug, "");
1518
1569
  const left = local.keys().filter((key) => key.startsWith(prefix) && key !== pendingKey(MAP.slug, TAB) && !open.has(key.slice(prefix.length)));
1519
1570
  if (left.length === 0) return;
@@ -1573,7 +1624,7 @@ function showLost() {
1573
1624
  if (block) {
1574
1625
  panelItem(
1575
1626
  element("p", { text: t("文件里的这一块已被修改:复制你的版本,再粘贴到页面上。") }),
1576
- element("div", { class: "ge-actions" }, button(t("复制这一块"), () => { if (!clipboard.copyBlocks([block])) say(t("没能完成,请再试一次。"), "error"); })),
1627
+ element("div", { class: "ge-actions" }, textButton(t("复制这一块"), () => { if (!clipboard.copyBlocks([block])) say(t("没能完成,请再试一次。"), "error"); })),
1577
1628
  );
1578
1629
  continue;
1579
1630
  }
@@ -1581,10 +1632,10 @@ function showLost() {
1581
1632
  panelItem(
1582
1633
  element("p", { text: LOST[item.why] ?? LOST.refused }),
1583
1634
  element("div", { class: "ge-version", text }),
1584
- element("div", { class: "ge-actions" }, button(t("复制文字"), () => navigator.clipboard?.writeText(text).then(() => say(t("已复制。"))))),
1635
+ element("div", { class: "ge-actions" }, textButton(t("复制文字"), () => navigator.clipboard?.writeText(text).then(() => say(t("已复制。"))))),
1585
1636
  );
1586
1637
  }
1587
- panelItem(element("div", { class: "ge-actions" }, button(t("都处理好了,不再保留"), () => {
1638
+ panelItem(element("div", { class: "ge-actions" }, textButton(t("都处理好了,不再保留"), () => {
1588
1639
  local.remove(lostKey(MAP.slug));
1589
1640
  closePanel();
1590
1641
  }, { primary: true })));
@@ -1617,10 +1668,13 @@ function boot() {
1617
1668
  copyLink: SOLO ? null : copyBlockLink, // what ?solo shows (写作偏好) only its dialog opens: no address leads there
1618
1669
  linkAhead: () => holdPages(thisPage),
1619
1670
  });
1620
- resize = createResize({ units, active, look: lookStep, say, owns: (node) => tables?.owns(node) ?? false });
1621
- gaps = createColumnGaps({ active, struct, look: lookStep, say });
1671
+ resize = createResize({ units, active, look: lookStep, say, settle, owns: (node) => tables?.owns(node) ?? false });
1672
+ gaps = createColumnGaps({ active, struct, look: lookStep, say, settle });
1622
1673
  for (const page of pageObjects()) page.install?.(); // the round "+" at a flowchart node's right, renaming Sheet tabs
1623
- tables = createTableEditing({ active, struct, look: lookStep, selectBlock: (unit) => blocks.select(unit), resizeTable: (event, unit, axis) => resize.startTable(event, unit, axis), say, ownCells: (data) => clipboard.ownCells(data), typedKey });
1674
+ tables = createTableEditing({
1675
+ active, struct, look: lookStep, selectBlock: (unit) => blocks.select(unit), resizeTable: (event, unit, axis) => resize.startTable(event, unit, axis), say, settle, cellsOf: (data) => clipboard.cellsOf(data), upload: uploadTo, typedKey,
1676
+ recordStep,
1677
+ });
1624
1678
  selectAll = createSelectAll({ blocks, say, cells: { table: () => tables.selectedTable, clear: () => tables.clear() } });
1625
1679
  createFrame(() => editing);
1626
1680
  createFlags(() => editing);
@@ -64,14 +64,14 @@ export const PICKER_CSS = `
64
64
  .nmm-cell.focus-visible { background: var(--ge-press); }
65
65
  .nmm-cell img { display: block; }
66
66
  .nmm-cell img.notion-custom-emoji { object-fit: contain; color: transparent; }
67
- /* 添加表情符号: the custom group's first cell, a round plus (26, 3 around, a 1px --ge-edge inside, the plus
68
- --ge-ico3); with no result, a button (28 high, a 1px --ge-edge inside, 13px / 500) */
69
- .nmm-cell.nmm-add-cell { width: 26px; height: 26px; margin: 3px; box-sizing: border-box; border-radius: 50%; box-shadow: inset 0 0 0 1px var(--ge-edge); --nmm-icon: var(--ge-ico3); }
67
+ /* 添加表情符号: the custom group's first cell, a round plus (26, 3 around, the plus --ge-ico3, no line round
68
+ it: gray under the pointer as a cell); with no result, a button (28 high, 13px / 500, no line either) */
69
+ .nmm-cell.nmm-add-cell { width: 26px; height: 26px; margin: 3px; box-sizing: border-box; border-radius: 50%; --nmm-icon: var(--ge-ico3); }
70
70
  .nmm-cell.nmm-add-cell.focused { --nmm-icon: var(--ge-ico); }
71
71
  .nmm-empty { padding: 8px 12px; }
72
72
  .nmm-empty-caption { display: flex; align-items: center; height: 20px; padding: 0 4px; font-size: 12px; font-weight: 500; color: var(--ge-tx3); }
73
73
  .nmm-empty.with-add { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
74
- .nmm-cell.nmm-add-button { justify-content: flex-start; gap: 6px; width: max-content; height: 28px; padding: 0 10px 0 8px; box-sizing: border-box; border-radius: 6px; box-shadow: inset 0 0 0 1px var(--ge-edge); font-size: 13px; font-weight: 500; line-height: 1; color: var(--ge-tx); white-space: nowrap; --nmm-icon: var(--ge-ico2); }
74
+ .nmm-cell.nmm-add-button { justify-content: flex-start; gap: 6px; width: max-content; height: 28px; padding: 0 10px 0 8px; box-sizing: border-box; border-radius: 6px; font-size: 13px; font-weight: 500; line-height: 1; color: var(--ge-tx); white-space: nowrap; --nmm-icon: var(--ge-ico2); }
75
75
 
76
76
  /* The category bar: a --ge-line above it, 6 12 8; buttons 28 x 28, icons 18 in --ge-ico3; the group scrolled
77
77
  to on --ge-hover, its icon --ge-ico; a group with no emoji faded to .35 and not to be clicked (自定义 and
@@ -106,8 +106,8 @@ export const PICKER_CSS = `
106
106
 
107
107
  /* The 上传 tab and the 添加表情符号 popup (upload.mjs): the zone an image goes in on --ge-field, a 1px
108
108
  --ge-edge inside it, rounded 8, deeper while a file is dragged over it (--ge-hover, --ge-ico3), never blue;
109
- 保存 a text button (--ge-tx, 500, --ge-hover under the pointer, --ge-tx3 while it cannot be clicked), 取消 /
110
- 返回 --ge-tx2; the checkbox checked --ge-tx2 with a white tick; an error --ge-red */
109
+ 保存 the pale blue primary and 取消 / 返回 the plain text button (look.mjs BUTTONS: .ge-btn, dimmed while it cannot be
110
+ clicked); the checkbox checked --ge-tx2 with a white tick; an error --ge-red */
111
111
  .nmm-upload { display: flex; flex-direction: column; padding: 0 16px; font-size: 14px; color: var(--ge-tx); }
112
112
  .nmm-upload-title { margin-top: 14px; font-size: 14px; font-weight: 600; }
113
113
  .nmm-upload-description { margin-top: 2px; font-size: 12px; color: var(--ge-tx2); }
@@ -142,12 +142,7 @@ export const PICKER_CSS = `
142
142
  .nmm-upload-name-error { margin-top: 10px; font-size: 12px; color: var(--ge-red); }
143
143
  .nmm-upload-name-error[hidden], .nmm-upload [hidden] { display: none; }
144
144
  .nmm-upload-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 16px 0 12px; }
145
- .nmm-upload-secondary, .nmm-upload-save { display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 0 10px; border-radius: 6px; font-size: 14px; line-height: 1.2; white-space: nowrap; cursor: pointer; user-select: none; }
146
- .nmm-upload-secondary { color: var(--ge-tx2); }
147
- .nmm-upload-secondary:hover { background: var(--ge-hover); color: var(--ge-tx); }
148
- .nmm-upload-save { color: var(--ge-tx); font-weight: 500; }
149
- .nmm-upload-save:not(.disabled):hover { background: var(--ge-hover); }
150
- .nmm-upload-save.disabled { color: var(--ge-tx3); cursor: default; }
145
+ .nmm-upload-secondary, .nmm-upload-save { font-size: 14px; }
151
146
  .nmm-custom-emoji-popup { min-width: 280px; border-radius: 10px; background: var(--ge-bg); box-shadow: var(--ge-shadow-pop); }
152
147
 
153
148
  /* A button reached by Tab: a gray fill, not the browser's dark or blue ring; the checkbox a gray ring */
@@ -76,8 +76,8 @@ export function createUploadForm({ showTitle = false, largeEmoji = false, allowU
76
76
  `<label class="nmm-upload-name" hidden><div class="nmm-upload-name-label">${T.nameLabel}</div>` +
77
77
  `<div class="nmm-upload-input"><input type="text" placeholder="${T.namePlaceholder}" autocomplete="off" spellcheck="false"></div>` +
78
78
  `<div class="nmm-upload-name-error" hidden></div></label>` +
79
- `<div class="nmm-upload-footer"><div role="button" tabindex="0" class="nmm-upload-secondary" data-act="secondary"></div>` +
80
- `<div role="button" tabindex="0" class="nmm-upload-save" data-act="save">${T.save}</div></div>` +
79
+ `<div class="nmm-upload-footer"><div role="button" tabindex="0" class="ge-btn nmm-upload-secondary" data-act="secondary"></div>` +
80
+ `<div role="button" tabindex="0" class="ge-btn nmm-upload-save" data-primary data-act="save">${T.save}</div></div>` +
81
81
  `<input type="file" accept="${ACCEPT}" hidden>`;
82
82
 
83
83
  const zone = el.querySelector('.nmm-upload-zone'), wrap = el.querySelector('.nmm-upload-zone-wrap');
@@ -44,13 +44,12 @@ import { t } from "../../../shared/i18n/index.mjs";
44
44
  // (lineAt, blocks.mjs). The handle and the bar are drawn in the editor's own layer (overlay-layer.mjs:
45
45
  // the one of the block they stand beside), never inside a block, each belonging to the block it stands
46
46
  // beside (overlays.mjs). A press ends when the pointer comes up, is cancelled, or the handle loses the
47
- // pointer capture (then a drag under way is cancelled, never dropped). While the page is edited, its
47
+ // pointer capture (then a drag under way is cancelled, never dropped). On a page the editor edits, its
48
48
  // side gutters (the kit's --doc-page-gutter, --doc-wide-gutter) are at least the room the
49
- // handle takes left of a block, and a little (EDGE): on a narrow window the handle stands in the
50
- // margin, never over the text, as Feishu's and Notion's do; the section bar has the same room on
51
- // the right. A wide window, whose column leaves more than that, does not change.
52
-
53
- const EDGE = ADD.width + HANDLE.width + HANDLE.gap + 12; // the page's gutter while editing: the bar's square, the grip, its gap, and room to breathe (64)
49
+ // handle takes left of a block, and a little (margin.mjs MARGIN.edge, from the page's first paint: the
50
+ // head's MARGIN_STYLE): on a narrow window the handle stands in the margin, never over the text, as
51
+ // Feishu's and Notion's do; the section bar has the same room on the right. A wide window, whose column
52
+ // leaves more than that, does not change.
54
53
 
55
54
  const STYLE = `
56
55
  .ge-handle { position: absolute; left: 0; top: 0; z-index: ${IN_LAYER.handle}; display: flex; align-items: center; height: ${HANDLE.height}px; touch-action: none; }
@@ -84,8 +83,6 @@ const STYLE = `
84
83
  .ge-section-bar::before { content: ""; position: absolute; top: 0; bottom: 0; left: 6px; width: 4px; border-radius: 2px; background: light-dark(rgb(55 53 47 / 14%), rgb(255 255 255 / 14%)); transition: background .08s ease-in; }
85
84
  .ge-section-bar:hover::before { background: light-dark(rgb(55 53 47 / 28%), rgb(255 255 255 / 26%)); }
86
85
  .ge-section-bar[data-active]::before { background: color-mix(in srgb, var(--doc-accent, #2678d8) 60%, transparent); }
87
- /* Room for the handle beside the page's blocks: the page's gutters while editing (resize/ reads --ge-edge-room). */
88
- :root[data-ge-editing] { --ge-edge-room: ${EDGE}px; --doc-page-gutter: var(--ge-edge-room); --doc-wide-gutter: var(--ge-edge-room); }
89
86
  `;
90
87
 
91
88
  // Notion's grip in size (its dragHandle icon at its icons' 20px): 2.5px dots 5px apart across and 6px down (notion-lite's were 5px down, the grip 2px shorter than Notion's: the user, 2026-10-26).
@@ -69,7 +69,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
69
69
  typing.time = at;
70
70
  } else {
71
71
  closeTyping();
72
- typing = { type: "text", element: host, before, after, time: at };
72
+ typing = { type: "text", element: host, before, after, time: at, began: at };
73
73
  }
74
74
  changed(now);
75
75
  }
@@ -83,8 +83,14 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
83
83
  changed(true);
84
84
  }
85
85
 
86
+ // A run of typing closed: a step of its own, unless it went into the block the last step made for
87
+ // what was typed (struct's `typedInto`: a paragraph after a block, after/), begun within a second of it:
88
+ // then it is part of that step, which undo takes away whole, redo putting the words back with it.
86
89
  function closeTyping() {
87
- if (typingChanged()) push(typing);
90
+ const last = undoStack.at(-1);
91
+ if (typingChanged() && last?.typedInto === typing.element && typing.began - last.madeAt < 1000) last.htmlAfter.set(typing.element, typing.after);
92
+ else if (typingChanged()) push(typing);
93
+ if (typing && last?.typedInto) last.typedInto = null;
88
94
  typing = null;
89
95
  }
90
96
 
@@ -148,6 +154,31 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
148
154
  for (const stack of [undoStack, redoStack]) if (stack.includes(step)) stack.splice(stack.indexOf(step), 1);
149
155
  }
150
156
 
157
+ // An attribute the file gave another value than the page had (`changes`: [element, name, from, to],
158
+ // `to` null for none; a key of a table a commit gave a pair anew: page/commit.mjs apply), the page's
159
+ // element set so: the steps that recorded `from` of it record `to` instead, so that an undo or a redo
160
+ // puts back what the file has, never the value it changed (a view made from "/" redone with the key its view
161
+ // no longer carries).
162
+ function rekeyed(changes) {
163
+ if (changes.length === 0) return;
164
+ for (const step of [...undoStack, ...redoStack]) {
165
+ for (const recorded of [step.attributesBefore, step.attributesAfter]) {
166
+ for (const [element, name, from, to] of changes) {
167
+ const values = recorded?.get(element);
168
+ if (values?.some(([each, value]) => each === name && value === from)) recorded.set(element, values.map(([each, value]) => [each, each === name && value === from ? to : value]));
169
+ }
170
+ }
171
+ // A view's table chosen in its 设置 is a step of look (installed/page.mjs readTable): its attributes by element.
172
+ if (step.type === "look") {
173
+ for (const recorded of [step.before, step.after]) {
174
+ step.elements.forEach((each, index) => {
175
+ for (const [element, name, from, to] of changes) if (each === element && recorded[index]?.attributes?.[name] === from) recorded[index].attributes[name] = to;
176
+ });
177
+ }
178
+ }
179
+ }
180
+ }
181
+
151
182
  // What a step puts into a block (`from`: what the step left there, `to`: what it puts back): as
152
183
  // recorded, unless someone else's words were merged into the block since the step; then the step's
153
184
  // change three ways against what the block holds now (null: the two touch the same place).
@@ -174,8 +205,10 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
174
205
  // after undo and redo (a deleted Sheet tab switches the Sheet shown). A step that names no text block
175
206
  // it came from keeps where the caret stood, which undo puts back, as notion-lite's selectionBefore.
176
207
  // `attributes`: [element, [names]] whose attributes the step records too (a table's cells: their
177
- // spans and widths, which its operations change: components/table/page.mjs).
178
- function struct(parents, touched, mutate, { attributes = [] } = {}) {
208
+ // spans and widths, which its operations change: components/table/page.mjs). `caret`: where the caret
209
+ // stood in the first text block it touched is kept too, which undo puts back (notion-lite's Enter in the
210
+ // page's title, enter.js: "撤销后光标回到回车前的位置"; typing/ enterBody).
211
+ function struct(parents, touched, mutate, { attributes = [], caret = false, typedInto = null } = {}) {
179
212
  closeTyping();
180
213
  // What the change may leave untidy is tidied after it by the components that know (rows of
181
214
  // columns with a column emptied, lists set side by side: tidy), so what they name is recorded
@@ -189,7 +222,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
189
222
  }
190
223
  // A text block among the parents (a list item whose lists change) keeps its text through undo too.
191
224
  touched = [...new Set([...touched, ...parents.filter(isHost)])];
192
- const place = touched[0] ? null : caretPlace(parents[0]);
225
+ const place = touched[0] && !caret ? null : caretPlace(parents[0]);
193
226
  const before = parents.map(contentOf);
194
227
  const htmlBefore = new Map(touched.map((node) => [node, ownHTML(node)]));
195
228
  const classesBefore = new Map(touched.map((node) => [node, new Set(node.classList)]));
@@ -219,7 +252,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
219
252
  .filter(({ added, removed }) => added.length > 0 || removed.length > 0),
220
253
  attributesBefore,
221
254
  attributesAfter: attributesOf(),
222
- focusBefore: touched[0] ?? place?.host,
255
+ focusBefore: (caret ? place?.host : null) ?? touched[0] ?? place?.host,
223
256
  caretBefore: place?.offsets ?? null,
224
257
  focusAfter: result.focus,
225
258
  select: result.select ?? null,
@@ -227,6 +260,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
227
260
  selectAfter: result.selectAfter ?? null, // what redo selects instead
228
261
  caret: result.caret ?? null, // where redo puts the caret when it selects nothing (tidyEmptied)
229
262
  settle: result.settle ?? null,
263
+ typedInto, madeAt: Date.now(), // the block made for what is typed, whose first run of typing is part of this step (closeTyping)
230
264
  });
231
265
  result.touched.forEach((node) => lastKnown.set(node, ownHTML(node)));
232
266
  note("history", "change");
@@ -381,6 +415,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
381
415
  sourceStep,
382
416
  fold,
383
417
  drop,
418
+ rekeyed,
384
419
  closeTyping,
385
420
  struct,
386
421
  finish,