@heycys/gallery 0.0.0-stage → 0.1.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 (785) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -2
  3. package/THIRD-PARTY-NOTICES.md +3253 -0
  4. package/assets/writing-preferences.md +107 -0
  5. package/bin/gallery.mjs +10 -0
  6. package/browser/editor/components/bookmark/icons.mjs +6 -0
  7. package/browser/editor/components/bookmark/index.mjs +80 -0
  8. package/browser/editor/components/bookmark/page.mjs +147 -0
  9. package/browser/editor/components/builders.mjs +19 -0
  10. package/browser/editor/components/callout/index.mjs +39 -0
  11. package/browser/editor/components/callout/page.mjs +13 -0
  12. package/browser/editor/components/code/index.mjs +327 -0
  13. package/browser/editor/components/code/page.mjs +561 -0
  14. package/browser/editor/components/colors.mjs +50 -0
  15. package/browser/editor/components/columns/index.mjs +104 -0
  16. package/browser/editor/components/columns/page.mjs +297 -0
  17. package/browser/editor/components/details/index.mjs +72 -0
  18. package/browser/editor/components/details/page.mjs +86 -0
  19. package/browser/editor/components/dialog/index.mjs +83 -0
  20. package/browser/editor/components/dialog/page.mjs +219 -0
  21. package/browser/editor/components/divider/index.mjs +48 -0
  22. package/browser/editor/components/embed/icons.mjs +11 -0
  23. package/browser/editor/components/embed/index.mjs +123 -0
  24. package/browser/editor/components/embed/page.mjs +309 -0
  25. package/browser/editor/components/figure/index.mjs +87 -0
  26. package/browser/editor/components/figure/page.mjs +33 -0
  27. package/browser/editor/components/file/icons.mjs +14 -0
  28. package/browser/editor/components/file/index.mjs +77 -0
  29. package/browser/editor/components/file/page.mjs +114 -0
  30. package/browser/editor/components/flowchart/add.mjs +123 -0
  31. package/browser/editor/components/flowchart/index.mjs +246 -0
  32. package/browser/editor/components/flowchart/page.mjs +453 -0
  33. package/browser/editor/components/heading/index.mjs +51 -0
  34. package/browser/editor/components/highlight/index.mjs +79 -0
  35. package/browser/editor/components/icon/index.mjs +57 -0
  36. package/browser/editor/components/icon/page.mjs +286 -0
  37. package/browser/editor/components/icons.mjs +45 -0
  38. package/browser/editor/components/image/crop.mjs +189 -0
  39. package/browser/editor/components/image/index.mjs +104 -0
  40. package/browser/editor/components/image/page.mjs +385 -0
  41. package/browser/editor/components/image/viewer.mjs +213 -0
  42. package/browser/editor/components/installed/index.mjs +133 -0
  43. package/browser/editor/components/installed/page.mjs +422 -0
  44. package/browser/editor/components/installed/served.mjs +35 -0
  45. package/browser/editor/components/kit.mjs +64 -0
  46. package/browser/editor/components/list/index.mjs +194 -0
  47. package/browser/editor/components/list/join.mjs +68 -0
  48. package/browser/editor/components/list/numbers.mjs +107 -0
  49. package/browser/editor/components/list/page.mjs +632 -0
  50. package/browser/editor/components/math/index.mjs +88 -0
  51. package/browser/editor/components/math/page.mjs +236 -0
  52. package/browser/editor/components/math/tex-logo.mjs +6 -0
  53. package/browser/editor/components/media/icons.mjs +19 -0
  54. package/browser/editor/components/media/page.mjs +162 -0
  55. package/browser/editor/components/media/parts.mjs +60 -0
  56. package/browser/editor/components/media/size.mjs +20 -0
  57. package/browser/editor/components/media/toolbar.mjs +37 -0
  58. package/browser/editor/components/media/write.mjs +7 -0
  59. package/browser/editor/components/pages.mjs +46 -0
  60. package/browser/editor/components/paragraph/index.mjs +43 -0
  61. package/browser/editor/components/paragraph/page.mjs +52 -0
  62. package/browser/editor/components/placeholder-bar.mjs +17 -0
  63. package/browser/editor/components/placeholder.mjs +128 -0
  64. package/browser/editor/components/quote/index.mjs +33 -0
  65. package/browser/editor/components/registry.mjs +833 -0
  66. package/browser/editor/components/rows.mjs +16 -0
  67. package/browser/editor/components/section/index.mjs +41 -0
  68. package/browser/editor/components/section/page.mjs +141 -0
  69. package/browser/editor/components/sheets/index.mjs +137 -0
  70. package/browser/editor/components/sheets/page.mjs +227 -0
  71. package/browser/editor/components/sheets/pills.mjs +226 -0
  72. package/browser/editor/components/table/grid.mjs +73 -0
  73. package/browser/editor/components/table/index.mjs +306 -0
  74. package/browser/editor/components/table/page.mjs +672 -0
  75. package/browser/editor/components/table/slots.mjs +41 -0
  76. package/browser/editor/components/widget/index.mjs +50 -0
  77. package/browser/editor/page/annotate/anchors.mjs +229 -0
  78. package/browser/editor/page/annotate/card.mjs +102 -0
  79. package/browser/editor/page/annotate/index.mjs +1629 -0
  80. package/browser/editor/page/at-menu/index.mjs +317 -0
  81. package/browser/editor/page/autosave.mjs +977 -0
  82. package/browser/editor/page/beside.mjs +21 -0
  83. package/browser/editor/page/block-info.mjs +63 -0
  84. package/browser/editor/page/block-menu/colors.mjs +82 -0
  85. package/browser/editor/page/block-menu/index.mjs +215 -0
  86. package/browser/editor/page/blocks.mjs +246 -0
  87. package/browser/editor/page/caret.mjs +679 -0
  88. package/browser/editor/page/chip-fills.mjs +126 -0
  89. package/browser/editor/page/clipboard/import/colors.mjs +192 -0
  90. package/browser/editor/page/clipboard/import/html.mjs +792 -0
  91. package/browser/editor/page/clipboard/import/inline.mjs +232 -0
  92. package/browser/editor/page/clipboard/import/languages.mjs +42 -0
  93. package/browser/editor/page/clipboard/import/markdown.mjs +760 -0
  94. package/browser/editor/page/clipboard/import/math.mjs +100 -0
  95. package/browser/editor/page/clipboard/import/office.mjs +245 -0
  96. package/browser/editor/page/clipboard/import/read.mjs +120 -0
  97. package/browser/editor/page/clipboard/import/rich.mjs +29 -0
  98. package/browser/editor/page/clipboard/import/to-blocks.mjs +290 -0
  99. package/browser/editor/page/clipboard/index.mjs +797 -0
  100. package/browser/editor/page/clipboard/own/blocks.mjs +202 -0
  101. package/browser/editor/page/clipboard/own/chart-keys.mjs +58 -0
  102. package/browser/editor/page/clipboard/own/describe.mjs +115 -0
  103. package/browser/editor/page/clipboard/own/export.mjs +167 -0
  104. package/browser/editor/page/clipboard/own/ids.mjs +39 -0
  105. package/browser/editor/page/clipboard/own/index.mjs +13 -0
  106. package/browser/editor/page/clipboard/own/rules.mjs +44 -0
  107. package/browser/editor/page/clipboard/own/selection.mjs +159 -0
  108. package/browser/editor/page/clipboard/own/writers.mjs +326 -0
  109. package/browser/editor/page/columns/index.mjs +360 -0
  110. package/browser/editor/page/commit.mjs +1798 -0
  111. package/browser/editor/page/conflicts.mjs +269 -0
  112. package/browser/editor/page/convert.mjs +830 -0
  113. package/browser/editor/page/core.mjs +1655 -0
  114. package/browser/editor/page/drag/drop.mjs +307 -0
  115. package/browser/editor/page/drag/index.mjs +335 -0
  116. package/browser/editor/page/drag/preview.mjs +121 -0
  117. package/browser/editor/page/edge-scroll.mjs +50 -0
  118. package/browser/editor/page/editor.mjs +1725 -0
  119. package/browser/editor/page/emoji-picker/emoji-tab.mjs +278 -0
  120. package/browser/editor/page/emoji-picker/glyph-html.mjs +26 -0
  121. package/browser/editor/page/emoji-picker/grid.mjs +79 -0
  122. package/browser/editor/page/emoji-picker/icon-tab.mjs +217 -0
  123. package/browser/editor/page/emoji-picker/icons.mjs +32 -0
  124. package/browser/editor/page/emoji-picker/index.mjs +164 -0
  125. package/browser/editor/page/emoji-picker/styles.mjs +2 -0
  126. package/browser/editor/page/emoji-picker/support.mjs +202 -0
  127. package/browser/editor/page/emoji-picker/upload.mjs +235 -0
  128. package/browser/editor/page/export-report.mjs +23 -0
  129. package/browser/editor/page/fills.mjs +60 -0
  130. package/browser/editor/page/find/index.mjs +107 -0
  131. package/browser/editor/page/flags.mjs +70 -0
  132. package/browser/editor/page/format-bar/bar.mjs +448 -0
  133. package/browser/editor/page/format-bar/brush.mjs +77 -0
  134. package/browser/editor/page/format-bar/colors.mjs +46 -0
  135. package/browser/editor/page/format-bar/index.mjs +404 -0
  136. package/browser/editor/page/formula/index.mjs +236 -0
  137. package/browser/editor/page/formula/panel.mjs +336 -0
  138. package/browser/editor/page/formula/symbols.mjs +156 -0
  139. package/browser/editor/page/frame.mjs +74 -0
  140. package/browser/editor/page/frames.mjs +196 -0
  141. package/browser/editor/page/geometry.mjs +400 -0
  142. package/browser/editor/page/gesture.mjs +45 -0
  143. package/browser/editor/page/handle/index.mjs +639 -0
  144. package/browser/editor/page/handle-sizes.mjs +14 -0
  145. package/browser/editor/page/hints/index.mjs +84 -0
  146. package/browser/editor/page/history.mjs +401 -0
  147. package/browser/editor/page/insert/index.mjs +243 -0
  148. package/browser/editor/page/kept.mjs +38 -0
  149. package/browser/editor/page/links/address.mjs +40 -0
  150. package/browser/editor/page/links/index.mjs +866 -0
  151. package/browser/editor/page/links/pages.mjs +131 -0
  152. package/browser/editor/page/links/views.mjs +369 -0
  153. package/browser/editor/page/look.mjs +65 -0
  154. package/browser/editor/page/made.mjs +24 -0
  155. package/browser/editor/page/margin.mjs +352 -0
  156. package/browser/editor/page/markdown/index.mjs +88 -0
  157. package/browser/editor/page/markdown/rules.mjs +33 -0
  158. package/browser/editor/page/marks.mjs +377 -0
  159. package/browser/editor/page/media-menu/index.mjs +158 -0
  160. package/browser/editor/page/menu.mjs +500 -0
  161. package/browser/editor/page/merge.mjs +100 -0
  162. package/browser/editor/page/notices.mjs +142 -0
  163. package/browser/editor/page/offered.mjs +32 -0
  164. package/browser/editor/page/outline/folds.mjs +52 -0
  165. package/browser/editor/page/outline/index.mjs +697 -0
  166. package/browser/editor/page/outline/list.mjs +172 -0
  167. package/browser/editor/page/outline/read.mjs +201 -0
  168. package/browser/editor/page/outline/strip.mjs +281 -0
  169. package/browser/editor/page/outline/tree.mjs +248 -0
  170. package/browser/editor/page/overlay-layer.mjs +227 -0
  171. package/browser/editor/page/overlays.mjs +205 -0
  172. package/browser/editor/page/own.mjs +28 -0
  173. package/browser/editor/page/pads.mjs +71 -0
  174. package/browser/editor/page/page-objects.mjs +105 -0
  175. package/browser/editor/page/panel.mjs +64 -0
  176. package/browser/editor/page/placement.mjs +58 -0
  177. package/browser/editor/page/post.mjs +41 -0
  178. package/browser/editor/page/present-links.mjs +50 -0
  179. package/browser/editor/page/quiet.mjs +48 -0
  180. package/browser/editor/page/resize/index.mjs +327 -0
  181. package/browser/editor/page/select-all/index.mjs +262 -0
  182. package/browser/editor/page/selection/delete.mjs +59 -0
  183. package/browser/editor/page/selection/index.mjs +157 -0
  184. package/browser/editor/page/selection/marquee.mjs +174 -0
  185. package/browser/editor/page/session.mjs +117 -0
  186. package/browser/editor/page/slash-menu/index.mjs +291 -0
  187. package/browser/editor/page/storage.mjs +48 -0
  188. package/browser/editor/page/sync-state.mjs +267 -0
  189. package/browser/editor/page/table/aligns.mjs +29 -0
  190. package/browser/editor/page/table/cell-bar.mjs +138 -0
  191. package/browser/editor/page/table/cells.mjs +527 -0
  192. package/browser/editor/page/table/icons.mjs +40 -0
  193. package/browser/editor/page/table/index.mjs +985 -0
  194. package/browser/editor/page/table/lists.mjs +187 -0
  195. package/browser/editor/page/table/widths.mjs +308 -0
  196. package/browser/editor/page/tex.mjs +32 -0
  197. package/browser/editor/page/text-select/index.mjs +656 -0
  198. package/browser/editor/page/tooltip.mjs +98 -0
  199. package/browser/editor/page/type-grid.mjs +82 -0
  200. package/browser/editor/page/typing/code-edges.mjs +228 -0
  201. package/browser/editor/page/typing/index.mjs +276 -0
  202. package/browser/editor/page/typing/leave.mjs +68 -0
  203. package/browser/editor/page/ui.mjs +160 -0
  204. package/browser/shared/annotations.mjs +571 -0
  205. package/browser/shared/code-folds.mjs +10 -0
  206. package/browser/shared/code-languages.mjs +70 -0
  207. package/browser/shared/code-lines.mjs +30 -0
  208. package/browser/shared/colors.mjs +28 -0
  209. package/browser/shared/component-catalog.mjs +559 -0
  210. package/browser/shared/components/bookmark.mjs +26 -0
  211. package/browser/shared/components/callout.mjs +7 -0
  212. package/browser/shared/components/chart.mjs +20 -0
  213. package/browser/shared/components/code.mjs +37 -0
  214. package/browser/shared/components/columns.mjs +16 -0
  215. package/browser/shared/components/details.mjs +15 -0
  216. package/browser/shared/components/dialog.mjs +28 -0
  217. package/browser/shared/components/diff.mjs +18 -0
  218. package/browser/shared/components/divider.mjs +6 -0
  219. package/browser/shared/components/embed.mjs +25 -0
  220. package/browser/shared/components/figure.mjs +14 -0
  221. package/browser/shared/components/file.mjs +23 -0
  222. package/browser/shared/components/heading.mjs +6 -0
  223. package/browser/shared/components/highlight.mjs +9 -0
  224. package/browser/shared/components/icon.mjs +18 -0
  225. package/browser/shared/components/image.mjs +39 -0
  226. package/browser/shared/components/index.mjs +342 -0
  227. package/browser/shared/components/installed.mjs +162 -0
  228. package/browser/shared/components/list.mjs +146 -0
  229. package/browser/shared/components/math.mjs +49 -0
  230. package/browser/shared/components/mention.mjs +33 -0
  231. package/browser/shared/components/page.mjs +37 -0
  232. package/browser/shared/components/paragraph.mjs +9 -0
  233. package/browser/shared/components/quote.mjs +6 -0
  234. package/browser/shared/components/section.mjs +8 -0
  235. package/browser/shared/components/sheets.mjs +80 -0
  236. package/browser/shared/components/state.mjs +20 -0
  237. package/browser/shared/components/table.mjs +49 -0
  238. package/browser/shared/components/text.mjs +14 -0
  239. package/browser/shared/components/widget.mjs +10 -0
  240. package/browser/shared/copy-ids.mjs +72 -0
  241. package/browser/shared/css-references.mjs +110 -0
  242. package/browser/shared/emoji/custom.mjs +91 -0
  243. package/browser/shared/emoji/data/LICENSE-mingcute.txt +202 -0
  244. package/browser/shared/emoji/data/LICENSE-tabler.txt +21 -0
  245. package/browser/shared/emoji/data/README.md +26 -0
  246. package/browser/shared/emoji/data/categories.json +1 -0
  247. package/browser/shared/emoji/data/icons.json +1 -0
  248. package/browser/shared/emoji/data/keywords-en.json +1 -0
  249. package/browser/shared/emoji/data/keywords-zh.json +1 -0
  250. package/browser/shared/emoji/data/names.json +1 -0
  251. package/browser/shared/emoji/data/positions.json +1 -0
  252. package/browser/shared/emoji/data/twemoji-index.json +1 -0
  253. package/browser/shared/emoji/data/twemoji-svg.bin +0 -0
  254. package/browser/shared/emoji/data.mjs +196 -0
  255. package/browser/shared/emoji/emoji.mjs +37 -0
  256. package/browser/shared/emoji/glyphs.mjs +134 -0
  257. package/browser/shared/emoji/icon-value.mjs +90 -0
  258. package/browser/shared/escape.mjs +20 -0
  259. package/browser/shared/export-wrap.mjs +89 -0
  260. package/browser/shared/files.mjs +33 -0
  261. package/browser/shared/html-tree.mjs +53 -0
  262. package/browser/shared/http.mjs +22 -0
  263. package/browser/shared/i18n/en.mjs +1164 -0
  264. package/browser/shared/i18n/index.mjs +132 -0
  265. package/browser/shared/ids.mjs +24 -0
  266. package/browser/shared/import-types.mjs +23 -0
  267. package/browser/shared/inline-spacing.mjs +97 -0
  268. package/browser/shared/layers.mjs +42 -0
  269. package/browser/shared/media.mjs +162 -0
  270. package/browser/shared/new-names.mjs +64 -0
  271. package/browser/shared/number-reading.mjs +92 -0
  272. package/browser/shared/old-names.mjs +143 -0
  273. package/browser/shared/page-address.mjs +140 -0
  274. package/browser/shared/page-links.mjs +86 -0
  275. package/browser/shared/paths.mjs +73 -0
  276. package/browser/shared/preferences.mjs +110 -0
  277. package/browser/shared/preview-server.mjs +247 -0
  278. package/browser/shared/qa-levels.mjs +93 -0
  279. package/browser/shared/reading-place.mjs +91 -0
  280. package/browser/shared/renamed-storage.mjs +220 -0
  281. package/browser/shared/reports.mjs +46 -0
  282. package/browser/shared/search.mjs +85 -0
  283. package/browser/shared/session.mjs +76 -0
  284. package/browser/shared/table-chart.mjs +113 -0
  285. package/browser/shared/table-hides.mjs +120 -0
  286. package/browser/shared/table-slots.mjs +75 -0
  287. package/browser/shared/words.mjs +23 -0
  288. package/browser/shared/workspace.mjs +535 -0
  289. package/browser/shared/writing-preferences.mjs +68 -0
  290. package/browser/shared/zoom.mjs +57 -0
  291. package/dist/chunk-2B5E4CDJ.mjs +2 -0
  292. package/dist/chunk-2PBALTR3.mjs +57 -0
  293. package/dist/chunk-342HXE3Q.mjs +2 -0
  294. package/dist/chunk-3LA3EMDE.mjs +15 -0
  295. package/dist/chunk-43B75J5T.mjs +2 -0
  296. package/dist/chunk-4K5KC6V2.mjs +2 -0
  297. package/dist/chunk-5FRMCDPY.mjs +16 -0
  298. package/dist/chunk-5L7RYZCL.mjs +2 -0
  299. package/dist/chunk-5LEK2CBS.mjs +2 -0
  300. package/dist/chunk-5OFN5AHC.mjs +2 -0
  301. package/dist/chunk-5SIYS5HN.mjs +2 -0
  302. package/dist/chunk-5TEK3RAD.mjs +2 -0
  303. package/dist/chunk-6CYG3Z4V.mjs +2 -0
  304. package/dist/chunk-6U2SLSDN.mjs +3 -0
  305. package/dist/chunk-742D2WWJ.mjs +19 -0
  306. package/dist/chunk-7GHT2UCA.mjs +15 -0
  307. package/dist/chunk-7QQBR7PB.mjs +2 -0
  308. package/dist/chunk-A55HN5OK.mjs +2 -0
  309. package/dist/chunk-ACOPDCNB.mjs +2 -0
  310. package/dist/chunk-AGDILNWB.mjs +2 -0
  311. package/dist/chunk-ATCJYAR4.mjs +2 -0
  312. package/dist/chunk-B3VTYBYR.mjs +960 -0
  313. package/dist/chunk-C564NU2H.mjs +77 -0
  314. package/dist/chunk-C7HWOBIF.mjs +2 -0
  315. package/dist/chunk-DB4WKGYF.mjs +7 -0
  316. package/dist/chunk-E7VYDG2X.mjs +2 -0
  317. package/dist/chunk-EUX7IC5J.mjs +48 -0
  318. package/dist/chunk-EVJ3BX3M.mjs +2 -0
  319. package/dist/chunk-EZY3WC6Z.mjs +15 -0
  320. package/dist/chunk-FQGQH4KM.mjs +2 -0
  321. package/dist/chunk-G7TOXONE.mjs +6 -0
  322. package/dist/chunk-GPF4YWWB.mjs +9 -0
  323. package/dist/chunk-GRAXKKG4.mjs +2 -0
  324. package/dist/chunk-GYQV7Y4J.mjs +120 -0
  325. package/dist/chunk-HMHK3EQ6.mjs +2 -0
  326. package/dist/chunk-HQQMJCQJ.mjs +10 -0
  327. package/dist/chunk-HYMEEXKN.mjs +4 -0
  328. package/dist/chunk-IERUOGC3.mjs +6 -0
  329. package/dist/chunk-ITNLYXXV.mjs +3 -0
  330. package/dist/chunk-L4DVONOG.mjs +2 -0
  331. package/dist/chunk-LAE5NQ4B.mjs +2 -0
  332. package/dist/chunk-LC7YNQFG.mjs +2 -0
  333. package/dist/chunk-LHTFLRB6.mjs +4 -0
  334. package/dist/chunk-LKX55H76.mjs +2 -0
  335. package/dist/chunk-MAFIGVXY.mjs +2 -0
  336. package/dist/chunk-NFIBI4IP.mjs +26 -0
  337. package/dist/chunk-NSKUTQ5J.mjs +5 -0
  338. package/dist/chunk-O52JMOCZ.mjs +2 -0
  339. package/dist/chunk-P2CAZPDP.mjs +3 -0
  340. package/dist/chunk-QAIHRH3I.mjs +13 -0
  341. package/dist/chunk-QMQUXTU4.mjs +12 -0
  342. package/dist/chunk-QR6A54KJ.mjs +3 -0
  343. package/dist/chunk-QVWVFC5K.mjs +6 -0
  344. package/dist/chunk-QYKCJ7FG.mjs +8 -0
  345. package/dist/chunk-QZY6Q64C.mjs +2 -0
  346. package/dist/chunk-TDQP3DKQ.mjs +10 -0
  347. package/dist/chunk-TMYZB6AV.mjs +2 -0
  348. package/dist/chunk-TX6JH7TR.mjs +2 -0
  349. package/dist/chunk-U3SSPL4K.mjs +8 -0
  350. package/dist/chunk-UH4SIXTR.mjs +2 -0
  351. package/dist/chunk-UNUEWH4S.mjs +2 -0
  352. package/dist/chunk-UXUK4PMC.mjs +27 -0
  353. package/dist/chunk-V4CPWKMZ.mjs +2 -0
  354. package/dist/chunk-V6XZ4VIR.mjs +2 -0
  355. package/dist/chunk-VACRB2NE.mjs +3 -0
  356. package/dist/chunk-W63L7367.mjs +7 -0
  357. package/dist/chunk-W7CXPCRV.mjs +6 -0
  358. package/dist/chunk-W7XHE4CT.mjs +2 -0
  359. package/dist/chunk-XINNG4F2.mjs +4 -0
  360. package/dist/chunk-Y34NR5JL.mjs +2 -0
  361. package/dist/chunk-YEY3RSFQ.mjs +2 -0
  362. package/dist/chunk-YL2DPWVS.mjs +4 -0
  363. package/dist/chunk-YXAUQPVK.mjs +8 -0
  364. package/dist/chunk-YZ5Z6H4W.mjs +4 -0
  365. package/dist/chunk-Z52HTUW7.mjs +4 -0
  366. package/dist/chunk-ZBIM6ARY.mjs +216 -0
  367. package/dist/cli.mjs +2 -0
  368. package/dist/server.mjs +2 -0
  369. package/dist/worker.mjs +5 -0
  370. package/kit/charts.js +355 -0
  371. package/kit/code.js +164 -0
  372. package/kit/dialogs.js +157 -0
  373. package/kit/math.js +1988 -0
  374. package/kit/report.css +2821 -0
  375. package/kit/runtime.js +1707 -0
  376. package/kit/sheets.js +123 -0
  377. package/kit/state.js +217 -0
  378. package/kit/tables.js +1449 -0
  379. package/kit/tokens.js +29 -0
  380. package/kit/ui.js +561 -0
  381. package/package.json +41 -4
  382. package/release.json +25 -0
  383. package/sdk/column-widths.d.ts +56 -0
  384. package/sdk/doc-charts.d.ts +82 -0
  385. package/sdk/doc-code.d.ts +28 -0
  386. package/sdk/doc-dialogs.d.ts +4 -0
  387. package/sdk/doc-kit.d.ts +141 -0
  388. package/sdk/doc-math.d.ts +3 -0
  389. package/sdk/doc-sheets.d.ts +15 -0
  390. package/sdk/doc-state.d.ts +41 -0
  391. package/sdk/doc-tables.d.ts +24 -0
  392. package/sdk/doc-tokens.d.ts +22 -0
  393. package/sdk/index.d.ts +30 -0
  394. package/skill/SKILL.md +138 -0
  395. package/skill/agents/openai.yaml +5 -0
  396. package/skill/references/building-components.md +327 -0
  397. package/skill/references/example-report.html +356 -0
  398. package/skill/references/writing-html.md +187 -0
  399. package/skill/sdk/column-widths.d.ts +56 -0
  400. package/skill/sdk/doc-charts.d.ts +82 -0
  401. package/skill/sdk/doc-code.d.ts +28 -0
  402. package/skill/sdk/doc-dialogs.d.ts +4 -0
  403. package/skill/sdk/doc-kit.d.ts +141 -0
  404. package/skill/sdk/doc-math.d.ts +3 -0
  405. package/skill/sdk/doc-sheets.d.ts +15 -0
  406. package/skill/sdk/doc-state.d.ts +41 -0
  407. package/skill/sdk/doc-tables.d.ts +24 -0
  408. package/skill/sdk/doc-tokens.d.ts +22 -0
  409. package/skill/sdk/index.d.ts +30 -0
  410. package/starter/doc.html +21 -0
  411. package/starter/doc.json +5 -0
  412. package/vendor/d3.v7.9.0.min.js +2 -0
  413. package/vendor/katex/auto-render.min.js +1 -0
  414. package/vendor/katex/contrib/mhchem.min.js +1 -0
  415. package/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  416. package/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  417. package/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  418. package/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  419. package/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  420. package/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  421. package/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  422. package/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  423. package/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  424. package/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  425. package/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  426. package/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  427. package/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  428. package/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  429. package/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  430. package/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  431. package/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  432. package/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  433. package/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  434. package/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  435. package/vendor/katex/katex.min.css +1 -0
  436. package/vendor/katex/katex.min.js +1 -0
  437. package/vendor/pdfjs/LICENSE +177 -0
  438. package/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  439. package/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  440. package/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  441. package/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  442. package/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  443. package/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  444. package/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  445. package/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  446. package/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  447. package/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  448. package/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  449. package/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  450. package/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  451. package/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  452. package/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  453. package/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  454. package/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  455. package/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  456. package/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  457. package/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  458. package/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  459. package/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  460. package/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  461. package/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  462. package/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  463. package/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  464. package/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  465. package/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  466. package/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  467. package/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  468. package/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  469. package/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  470. package/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  471. package/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  472. package/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  473. package/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  474. package/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  475. package/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  476. package/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  477. package/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  478. package/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  479. package/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  480. package/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  481. package/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  482. package/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  483. package/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  484. package/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  485. package/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  486. package/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  487. package/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  488. package/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  489. package/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  490. package/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  491. package/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  492. package/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  493. package/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  494. package/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  495. package/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  496. package/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  497. package/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  498. package/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  499. package/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  500. package/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  501. package/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  502. package/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  503. package/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  504. package/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  505. package/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  506. package/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  507. package/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  508. package/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  509. package/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  510. package/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  511. package/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  512. package/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  513. package/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  514. package/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  515. package/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  516. package/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  517. package/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  518. package/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  519. package/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  520. package/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  521. package/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  522. package/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  523. package/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  524. package/vendor/pdfjs/cmaps/H.bcmap +0 -0
  525. package/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  526. package/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  527. package/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  528. package/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  529. package/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  530. package/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  531. package/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  532. package/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  533. package/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  534. package/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  535. package/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  536. package/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  537. package/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  538. package/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  539. package/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  540. package/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  541. package/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  542. package/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  543. package/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  544. package/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  545. package/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  546. package/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  547. package/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  548. package/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  549. package/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  550. package/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  551. package/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  552. package/vendor/pdfjs/cmaps/LICENSE +36 -0
  553. package/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  554. package/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  555. package/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  556. package/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  557. package/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  558. package/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  559. package/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  560. package/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  561. package/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  562. package/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  563. package/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  564. package/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  565. package/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  566. package/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  567. package/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  568. package/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  569. package/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  570. package/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  571. package/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  572. package/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  573. package/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  574. package/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  575. package/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  576. package/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  577. package/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  578. package/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  579. package/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  580. package/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  581. package/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  582. package/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  583. package/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  584. package/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  585. package/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  586. package/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  587. package/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  588. package/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  589. package/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  590. package/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  591. package/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  592. package/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  593. package/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  594. package/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  595. package/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  596. package/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  597. package/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  598. package/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  599. package/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  600. package/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  601. package/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  602. package/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  603. package/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  604. package/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  605. package/vendor/pdfjs/cmaps/V.bcmap +0 -0
  606. package/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  607. package/vendor/pdfjs/legacy/build/pdf.min.mjs +27 -0
  608. package/vendor/pdfjs/legacy/build/pdf.worker.min.mjs +27 -0
  609. package/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  610. package/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  611. package/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  612. package/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  613. package/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  614. package/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  615. package/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  616. package/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  617. package/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  618. package/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  619. package/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  620. package/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
  621. package/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  622. package/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  623. package/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  624. package/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  625. package/vendor/types/@types/d3/LICENSE +21 -0
  626. package/vendor/types/@types/d3/index.d.ts +39 -0
  627. package/vendor/types/@types/d3/package.json +81 -0
  628. package/vendor/types/@types/d3-array/LICENSE +21 -0
  629. package/vendor/types/@types/d3-array/index.d.ts +1118 -0
  630. package/vendor/types/@types/d3-array/package.json +56 -0
  631. package/vendor/types/@types/d3-axis/LICENSE +21 -0
  632. package/vendor/types/@types/d3-axis/index.d.ts +293 -0
  633. package/vendor/types/@types/d3-axis/package.json +47 -0
  634. package/vendor/types/@types/d3-brush/LICENSE +21 -0
  635. package/vendor/types/@types/d3-brush/index.d.ts +255 -0
  636. package/vendor/types/@types/d3-brush/package.json +42 -0
  637. package/vendor/types/@types/d3-chord/LICENSE +21 -0
  638. package/vendor/types/@types/d3-chord/index.d.ts +471 -0
  639. package/vendor/types/@types/d3-chord/package.json +40 -0
  640. package/vendor/types/@types/d3-color/LICENSE +21 -0
  641. package/vendor/types/@types/d3-color/index.d.ts +669 -0
  642. package/vendor/types/@types/d3-color/package.json +55 -0
  643. package/vendor/types/@types/d3-contour/LICENSE +21 -0
  644. package/vendor/types/@types/d3-contour/index.d.ts +222 -0
  645. package/vendor/types/@types/d3-contour/package.json +38 -0
  646. package/vendor/types/@types/d3-delaunay/LICENSE +21 -0
  647. package/vendor/types/@types/d3-delaunay/index.d.ts +316 -0
  648. package/vendor/types/@types/d3-delaunay/package.json +30 -0
  649. package/vendor/types/@types/d3-dispatch/LICENSE +21 -0
  650. package/vendor/types/@types/d3-dispatch/index.d.ts +96 -0
  651. package/vendor/types/@types/d3-dispatch/package.json +51 -0
  652. package/vendor/types/@types/d3-drag/LICENSE +21 -0
  653. package/vendor/types/@types/d3-drag/index.d.ts +395 -0
  654. package/vendor/types/@types/d3-drag/package.json +42 -0
  655. package/vendor/types/@types/d3-dsv/LICENSE +21 -0
  656. package/vendor/types/@types/d3-dsv/index.d.ts +482 -0
  657. package/vendor/types/@types/d3-dsv/package.json +45 -0
  658. package/vendor/types/@types/d3-ease/LICENSE +21 -0
  659. package/vendor/types/@types/d3-ease/index.d.ts +316 -0
  660. package/vendor/types/@types/d3-ease/package.json +40 -0
  661. package/vendor/types/@types/d3-fetch/LICENSE +21 -0
  662. package/vendor/types/@types/d3-fetch/index.d.ts +282 -0
  663. package/vendor/types/@types/d3-fetch/package.json +37 -0
  664. package/vendor/types/@types/d3-force/LICENSE +21 -0
  665. package/vendor/types/@types/d3-force/index.d.ts +899 -0
  666. package/vendor/types/@types/d3-force/package.json +45 -0
  667. package/vendor/types/@types/d3-format/LICENSE +21 -0
  668. package/vendor/types/@types/d3-format/index.d.ts +337 -0
  669. package/vendor/types/@types/d3-format/package.json +45 -0
  670. package/vendor/types/@types/d3-geo/LICENSE +21 -0
  671. package/vendor/types/@types/d3-geo/index.d.ts +1371 -0
  672. package/vendor/types/@types/d3-geo/package.json +48 -0
  673. package/vendor/types/@types/d3-hierarchy/LICENSE +21 -0
  674. package/vendor/types/@types/d3-hierarchy/index.d.ts +928 -0
  675. package/vendor/types/@types/d3-hierarchy/package.json +50 -0
  676. package/vendor/types/@types/d3-interpolate/LICENSE +21 -0
  677. package/vendor/types/@types/d3-interpolate/index.d.ts +387 -0
  678. package/vendor/types/@types/d3-interpolate/package.json +47 -0
  679. package/vendor/types/@types/d3-path/LICENSE +21 -0
  680. package/vendor/types/@types/d3-path/index.d.ts +108 -0
  681. package/vendor/types/@types/d3-path/package.json +41 -0
  682. package/vendor/types/@types/d3-polygon/LICENSE +21 -0
  683. package/vendor/types/@types/d3-polygon/index.d.ts +41 -0
  684. package/vendor/types/@types/d3-polygon/package.json +40 -0
  685. package/vendor/types/@types/d3-quadtree/LICENSE +21 -0
  686. package/vendor/types/@types/d3-quadtree/index.d.ts +204 -0
  687. package/vendor/types/@types/d3-quadtree/package.json +45 -0
  688. package/vendor/types/@types/d3-random/LICENSE +21 -0
  689. package/vendor/types/@types/d3-random/index.d.ts +313 -0
  690. package/vendor/types/@types/d3-random/package.json +41 -0
  691. package/vendor/types/@types/d3-scale/LICENSE +21 -0
  692. package/vendor/types/@types/d3-scale/index.d.ts +2669 -0
  693. package/vendor/types/@types/d3-scale/package.json +53 -0
  694. package/vendor/types/@types/d3-scale-chromatic/LICENSE +21 -0
  695. package/vendor/types/@types/d3-scale-chromatic/index.d.ts +520 -0
  696. package/vendor/types/@types/d3-scale-chromatic/package.json +46 -0
  697. package/vendor/types/@types/d3-selection/LICENSE +21 -0
  698. package/vendor/types/@types/d3-selection/index.d.ts +1210 -0
  699. package/vendor/types/@types/d3-selection/package.json +56 -0
  700. package/vendor/types/@types/d3-shape/LICENSE +21 -0
  701. package/vendor/types/@types/d3-shape/index.d.ts +2792 -0
  702. package/vendor/types/@types/d3-shape/package.json +53 -0
  703. package/vendor/types/@types/d3-time/LICENSE +21 -0
  704. package/vendor/types/@types/d3-time/index.d.ts +697 -0
  705. package/vendor/types/@types/d3-time/package.json +46 -0
  706. package/vendor/types/@types/d3-time-format/LICENSE +21 -0
  707. package/vendor/types/@types/d3-time-format/index.d.ts +202 -0
  708. package/vendor/types/@types/d3-time-format/package.json +40 -0
  709. package/vendor/types/@types/d3-timer/LICENSE +21 -0
  710. package/vendor/types/@types/d3-timer/index.d.ts +68 -0
  711. package/vendor/types/@types/d3-timer/package.json +45 -0
  712. package/vendor/types/@types/d3-transition/LICENSE +21 -0
  713. package/vendor/types/@types/d3-transition/index.d.ts +664 -0
  714. package/vendor/types/@types/d3-transition/package.json +47 -0
  715. package/vendor/types/@types/d3-zoom/LICENSE +21 -0
  716. package/vendor/types/@types/d3-zoom/index.d.ts +611 -0
  717. package/vendor/types/@types/d3-zoom/package.json +48 -0
  718. package/vendor/types/@types/geojson/LICENSE +21 -0
  719. package/vendor/types/@types/geojson/index.d.ts +202 -0
  720. package/vendor/types/@types/geojson/package.json +46 -0
  721. package/vendor/typescript/LICENSE.txt +55 -0
  722. package/vendor/typescript/lib.decorators.d.ts +384 -0
  723. package/vendor/typescript/lib.decorators.legacy.d.ts +22 -0
  724. package/vendor/typescript/lib.dom.d.ts +39429 -0
  725. package/vendor/typescript/lib.dom.iterable.d.ts +571 -0
  726. package/vendor/typescript/lib.es2015.collection.d.ts +147 -0
  727. package/vendor/typescript/lib.es2015.core.d.ts +597 -0
  728. package/vendor/typescript/lib.es2015.d.ts +28 -0
  729. package/vendor/typescript/lib.es2015.generator.d.ts +77 -0
  730. package/vendor/typescript/lib.es2015.iterable.d.ts +605 -0
  731. package/vendor/typescript/lib.es2015.promise.d.ts +81 -0
  732. package/vendor/typescript/lib.es2015.proxy.d.ts +128 -0
  733. package/vendor/typescript/lib.es2015.reflect.d.ts +144 -0
  734. package/vendor/typescript/lib.es2015.symbol.d.ts +46 -0
  735. package/vendor/typescript/lib.es2015.symbol.wellknown.d.ts +326 -0
  736. package/vendor/typescript/lib.es2016.array.include.d.ts +116 -0
  737. package/vendor/typescript/lib.es2016.d.ts +21 -0
  738. package/vendor/typescript/lib.es2016.intl.d.ts +31 -0
  739. package/vendor/typescript/lib.es2017.arraybuffer.d.ts +21 -0
  740. package/vendor/typescript/lib.es2017.d.ts +26 -0
  741. package/vendor/typescript/lib.es2017.date.d.ts +31 -0
  742. package/vendor/typescript/lib.es2017.intl.d.ts +44 -0
  743. package/vendor/typescript/lib.es2017.object.d.ts +49 -0
  744. package/vendor/typescript/lib.es2017.sharedmemory.d.ts +135 -0
  745. package/vendor/typescript/lib.es2017.string.d.ts +45 -0
  746. package/vendor/typescript/lib.es2017.typedarrays.d.ts +53 -0
  747. package/vendor/typescript/lib.es2018.asyncgenerator.d.ts +77 -0
  748. package/vendor/typescript/lib.es2018.asynciterable.d.ts +53 -0
  749. package/vendor/typescript/lib.es2018.d.ts +24 -0
  750. package/vendor/typescript/lib.es2018.intl.d.ts +83 -0
  751. package/vendor/typescript/lib.es2018.promise.d.ts +30 -0
  752. package/vendor/typescript/lib.es2018.regexp.d.ts +37 -0
  753. package/vendor/typescript/lib.es2019.array.d.ts +79 -0
  754. package/vendor/typescript/lib.es2019.d.ts +24 -0
  755. package/vendor/typescript/lib.es2019.intl.d.ts +23 -0
  756. package/vendor/typescript/lib.es2019.object.d.ts +33 -0
  757. package/vendor/typescript/lib.es2019.string.d.ts +37 -0
  758. package/vendor/typescript/lib.es2019.symbol.d.ts +24 -0
  759. package/vendor/typescript/lib.es2020.bigint.d.ts +765 -0
  760. package/vendor/typescript/lib.es2020.d.ts +27 -0
  761. package/vendor/typescript/lib.es2020.date.d.ts +42 -0
  762. package/vendor/typescript/lib.es2020.intl.d.ts +474 -0
  763. package/vendor/typescript/lib.es2020.number.d.ts +28 -0
  764. package/vendor/typescript/lib.es2020.promise.d.ts +47 -0
  765. package/vendor/typescript/lib.es2020.sharedmemory.d.ts +99 -0
  766. package/vendor/typescript/lib.es2020.string.d.ts +44 -0
  767. package/vendor/typescript/lib.es2020.symbol.wellknown.d.ts +41 -0
  768. package/vendor/typescript/lib.es2021.d.ts +23 -0
  769. package/vendor/typescript/lib.es2021.intl.d.ts +166 -0
  770. package/vendor/typescript/lib.es2021.promise.d.ts +48 -0
  771. package/vendor/typescript/lib.es2021.string.d.ts +33 -0
  772. package/vendor/typescript/lib.es2021.weakref.d.ts +78 -0
  773. package/vendor/typescript/lib.es2022.array.d.ts +121 -0
  774. package/vendor/typescript/lib.es2022.d.ts +25 -0
  775. package/vendor/typescript/lib.es2022.error.d.ts +75 -0
  776. package/vendor/typescript/lib.es2022.intl.d.ts +145 -0
  777. package/vendor/typescript/lib.es2022.object.d.ts +26 -0
  778. package/vendor/typescript/lib.es2022.regexp.d.ts +39 -0
  779. package/vendor/typescript/lib.es2022.string.d.ts +25 -0
  780. package/vendor/typescript/lib.es5.d.ts +4601 -0
  781. package/vendor/typescript/package.json +4 -0
  782. package/vendor/typescript/typescript.js +446 -0
  783. package/web/assets/index-B6Ai3wsa.js +13 -0
  784. package/web/assets/index-SYPC3EZy.css +1 -0
  785. package/web/index.html +14 -0
@@ -0,0 +1,1798 @@
1
+ import { ELEMENT_ID, blocksText, ownHTML, ownText, captureLayout, collectEdits, firstByAttribute, identityOf, knownElements, lockHost, madeFrom, matchesSource, prepareBlock, serializeInner, setOwnHTML, shownAs, snapshotLayout } from "./core.mjs";
2
+ import { BLOCK_TYPES, SEALED, accepts, blockTypeOf, boxIn, boxOwner, boxRole, capsFor, holdsStraight, takesBox } from "../components/registry.mjs";
3
+ import { COPY_KEYS } from "../components/figure/index.mjs";
4
+ import { select } from "./caret.mjs";
5
+ import { ownTypeOf } from "./convert.mjs";
6
+ import { rangeAt, rangeOffsets } from "./marks.mjs";
7
+ import { pageOfKind, pageOfUnit } from "./page-objects.mjs";
8
+ import { containerTypeOf, pack, project } from "./sync-state.mjs";
9
+ import { outsideKey, randomBlockId } from "../../shared/ids.mjs";
10
+ import { CHART_DATA, CHART_SOURCE } from "../../shared/table-chart.mjs";
11
+
12
+ // The page's commits (autosave; docs/editor.md, "Commits"). A commit is what changed on the page
13
+ // since the last one, as collectEdits finds it against the layout and baselines the last commit
14
+ // left, sent to POST /__gallery/edit/commit with everything named by identity instead of by where
15
+ // it was: a unit by its data-block-id, or, untouched since the page opened and without one yet, by
16
+ // its number in the page's map and its hash; a text block by its unit (the unit's own text, or a
17
+ // field of it by the path the map gives) and the source it had at the last commit (`before`).
18
+ // Every unit whose text the commit changes, and the units around it, is named too (`touch`), so
19
+ // that it has an id afterwards; so is a unit something of the page's own asked to name (idFor: the
20
+ // blocks a 批注 is about, annotate/), with the next commit, alone when nothing else waits.
21
+ //
22
+ // What of the file each element is lives in a table here, under the element's identity (core.mjs
23
+ // ELEMENT_ID), never on the element: the unit, text block and box it is, what the file dropped
24
+ // that the page still shows, the ids blocks made on the page propose. The marks the rest of the
25
+ // editor reads (data-ge-unit, data-ge-new…) are written from it (sync-state.mjs project) before
26
+ // every look at the page and after every answer, so an element undo brings back is marked from
27
+ // what the file has now, not from what it had when the element went.
28
+ //
29
+ // The answer is taken in without reloading: text blocks start from what was committed (a block
30
+ // merged with someone else's change shows the merge), blocks and containers made on the page join
31
+ // the map as ordinary ones, ids and hashes are kept, and the next layout is the one the commit was
32
+ // built from (snapshotLayout), so what the reader did while it was on its way goes with the next.
33
+ // A block or operation the file refused (a conflict) is held back from later commits until the
34
+ // reader decides (resolve). A block made or moved on the page whose place the file no longer has
35
+ // goes to the end of its box instead, once; only then is it a conflict. The texts committed are
36
+ // kept (the journal), and so is what components wrote (`writes`), so that the page can ask whether
37
+ // someone else's write put them back as they were (revertedTexts, revertedParts) before it shows
38
+ // that write.
39
+ //
40
+ // Undo reaches across commits: what it puts back is a change like any other. A unit deleted whole
41
+ // comes back from the source the file gave for it (a restore); a container deleted while its blocks
42
+ // went elsewhere (a merged section, a list turned into paragraphs) comes back around them, as made
43
+ // on the page, with the start tag that source has: the same unit, its id and all it carried in the
44
+ // file (around); the element a conversion replaced is the same unit, of the type it was, and converts back.
45
+
46
+ // How many writes of a part of an instance the revert check asks of, the newest (noteWrite), and how
47
+ // many texts a block's commits left it as between its first and its last (committedText).
48
+ const WRITES_KEPT = 100;
49
+ export const TEXTS_KEPT = 20;
50
+
51
+ /**
52
+ * root: the document; map: the page's map (instrument), extended here as blocks are committed;
53
+ * baselines: text block id -> the html the page compares with; units, boxes: the editor's maps of
54
+ * the map's units and boxes; getLayout, setLayout: the layout collectEdits works against;
55
+ * restore: what a page that is gone knew (save), to take up where it was (autosave.mjs recover).
56
+ */
57
+ export function createCommits({ root, map, baselines, units, boxes, getLayout, setLayout, restore = null }) {
58
+ const openingUnits = restore?.openingUnits ?? map.units.length;
59
+ const openingBlocks = restore?.openingBlocks ?? map.blocks.length;
60
+ // Each text block's source as the file has it since the last commit: its next edit's `before`.
61
+ const sources = restore?.sources ?? new Map(map.blocks.map((block) => [block.id, block.inner]));
62
+ // What the file did not take, until the reader decides: by issueKey (a text of a block, an
63
+ // operation of a unit, a block made or put back on the page by the id it proposes) -> the
64
+ // conflict, with the reader's words (`mine`) where there are any.
65
+ const issues = restore?.issues ?? new Map();
66
+ const refused = restore?.refused ?? new Map(); // identity of a block made (or put back) on the page the file did not take -> its key in issues
67
+ // unit id -> { source, seal, type, types, blocks: [[id, source, baseline]], element }: deleted
68
+ // whole by a commit, with the text blocks that went with it and the types it and the units that
69
+ // went with it showed as; undo puts it back.
70
+ const removed = restore?.removed ?? new Map();
71
+ const removedWith = restore?.removedWith ?? new Map(); // unit id -> the unit deleted whole it went with (in removed)
72
+ // Held keys the reader gave up, never sent again (the page shows the file's once nothing else waits): by
73
+ // issueKey -> what was given up, of a unit's change { kind: of that change (kindOf); change: that change as
74
+ // the card held it, the one never sent again, the unit's other changes going as any, another of that kind
75
+ // too; reason: the conflict's, a unit the file has no one of (`lacks`) giving up all of it; words: whether
76
+ // the texts in it went with it (holdsWords) }. A page of an earlier Gallery kept a set of keys, each giving
77
+ // up all there was, or no `change`, giving up every change of that kind: read so.
78
+ const discarded = restore?.discarded instanceof Map ? restore.discarded : new Map([...(restore?.discarded ?? [])].map((key) => [key, { words: true }]));
79
+ const unwritten = restore?.unwritten ?? new Set(); // units whose component's writes are never sent again: by a manifest that is no more (the page is shown again)
80
+ // Unit id -> the reader's word to make a change of it whatever the file did to it (仍然删除, 仍然转换类型,
81
+ // 仍然调整), until that change goes in: { kind } of the change it was given for (`kindOf`), with the hash of
82
+ // the unit the refusal they were shown gave (`hash`), or for what its component writes the parts still to
83
+ // go (`parts`). A set of units, as a page of an earlier Gallery kept it, says no kind or hash: no word,
84
+ // each change asking again.
85
+ const forced = restore?.forced instanceof Map ? restore.forced : new Map();
86
+ const elsewhere = restore?.elsewhere ?? new Set(); // identities of blocks made or put back on the page, sent to the end of their box
87
+ const movedElsewhere = restore?.movedElsewhere ?? new Set(); // unit ids likewise, for a move
88
+ const removedElsewhere = restore?.removedElsewhere ?? new Set(); // unit ids likewise, for a deletion that would have taken along blocks with no place yet
89
+ const journal = restore?.journal ?? new Map(); // "b<block>" -> { key, block, original, mine, earlier? }: texts committed (committedText)
90
+ // What components wrote that went in, by unit: { parts, time }, each part written by name { before,
91
+ // after }: every hash it had in the file as the page wrote it but the one it has now, and that one
92
+ // (noteWrite); `time`, when it was last written.
93
+ const writes = restore?.writes ?? new Map();
94
+ // Units this load made or moved, as the commits left them: unit id -> { parent, from, around } (the
95
+ // file's id of the unit it is in, null for none; for one it moved, of the one it was in before; for
96
+ // one put back around its blocks, the source it came back with: dropped's `around`).
97
+ const placed = restore?.placed ?? new Map();
98
+ const keyedBlocks = restore?.keyedBlocks ?? new Map(); // id -> the element of a keyed block (a detail card) made on the page and saved (adoptKeyed)
99
+ // Units the page opened on standing where their box takes none of their kind from the reader (a
100
+ // section in a section): unit id -> the kind of that box. The reader can put none there, so undo alone
101
+ // brings one back, and every commit says so of it (`stood`), or the file would refuse what it held.
102
+ const stood = restore?.stood ?? new Map();
103
+ let serial = restore?.serial ?? 0;
104
+ // Units of the file without an id yet that the page asked to name (idFor), until a commit has.
105
+ const naming = new Set();
106
+
107
+ // ---------- the table ----------
108
+ // identity -> { unit, block, box }: what of the file the element of that identity is (a paragraph
109
+ // is a unit and its own text block, a list a unit and its box). The map's entries know their
110
+ // element's identity back (`cid`).
111
+ const table = restore?.table ?? new Map();
112
+ // identity -> { unit, block, box, around }: what of the file it was, which the file dropped while the
113
+ // page still had it: made on the page from now on (sync-state.mjs asMade). `around`: a container
114
+ // deleted while its blocks went elsewhere, the source the file gave ({ source, seal, type }).
115
+ const dropped = restore?.dropped ?? new Map();
116
+ const keyedIds = restore?.keyedIds ?? new Set(); // identities of keyed blocks (detail cards) made on the page that the file has
117
+ const proposals = restore?.proposals ?? new Map(); // identity of a block made (or put back) on the page -> the id it proposes
118
+ const LISTS = { unit: map.units, block: map.blocks, box: map.boxes };
119
+
120
+ const entryOf = (node) => {
121
+ const key = node?.getAttribute?.(ELEMENT_ID);
122
+ return key ? table.get(key) : undefined;
123
+ };
124
+ const idOf = (node) => entryOf(node)?.unit ?? null;
125
+ const blockOf = (node) => entryOf(node)?.block ?? null;
126
+ const boxOf = (node) => entryOf(node)?.box ?? null;
127
+ // The element of `field` `id` (a unit, block or box of the map) is `element` from now on.
128
+ const bind = (element, field, id) => {
129
+ const key = identityOf(element);
130
+ const entry = LISTS[field][id];
131
+ const before = entry.cid !== undefined && entry.cid !== key ? table.get(entry.cid) : undefined;
132
+ if (before?.[field] === id) delete before[field];
133
+ table.set(key, { ...table.get(key), [field]: id });
134
+ entry.cid = key;
135
+ dropped.delete(key);
136
+ };
137
+ // No element is `field` `id` any more; `drop`: the one that was is made on the page from now on.
138
+ const unbind = (field, id, drop = false) => {
139
+ const key = LISTS[field][id]?.cid;
140
+ const entry = key === undefined ? undefined : table.get(key);
141
+ if (entry?.[field] !== id) return;
142
+ delete entry[field];
143
+ if (drop) {
144
+ dropped.set(key, { ...dropped.get(key), [field]: id });
145
+ proposals.delete(key);
146
+ }
147
+ };
148
+ const onPage = (key) => key === undefined ? null : root.querySelector(`[${ELEMENT_ID}="${key}"]`);
149
+ const unitElement = (id) => entryOf(onPage(map.units[id]?.cid))?.unit === id ? onPage(map.units[id].cid) : null;
150
+ const hostOf = (id) => {
151
+ const key = map.blocks[id]?.cid;
152
+ return key !== undefined && table.get(key)?.block === id ? onPage(key) : null;
153
+ };
154
+ // The unit an element is or is in.
155
+ const unitAround = (node) => {
156
+ for (let at = node; at?.nodeType === 1; at = at.parentElement) {
157
+ const id = idOf(at);
158
+ if (id !== null) return id;
159
+ }
160
+ return null;
161
+ };
162
+ // The id the file has for a unit of the page, or the one a block made on the page proposes.
163
+ const fileIdOf = (node) => {
164
+ const unit = idOf(node);
165
+ return unit !== null ? map.units[unit].blockId : proposals.get(node?.getAttribute?.(ELEMENT_ID));
166
+ };
167
+
168
+ // Elements not seen yet get their identity; one that carries the marks of a block of the file
169
+ // (the page as it was served; a code block opened for editing, components/code/) is that block. One
170
+ // given an identity before the commits saw it is not seen yet either: a table's cells, which the
171
+ // layout's grid names by identity before the page's first marks (components/table/grid.mjs).
172
+ function learn() {
173
+ for (const node of knownElements(root)) {
174
+ if (table.has(node.getAttribute(ELEMENT_ID))) continue;
175
+ identityOf(node);
176
+ for (const [field, name] of [["unit", "data-ge-unit"], ["block", "data-ge-block"], ["box", "data-ge-box"]]) {
177
+ const value = node.getAttribute(name) ?? "";
178
+ if (value !== "" && LISTS[field][Number(value)]) bind(node, field, Number(value));
179
+ }
180
+ }
181
+ }
182
+
183
+ // Identities more than one element on the page carries: none ever should (a copy has none of
184
+ // its own, core.mjs); tests look.
185
+ let doubled = [];
186
+ const view = {
187
+ entry: (key) => table.get(key),
188
+ dropped: (key) => dropped.get(key),
189
+ keyed: (key) => keyedIds.has(key),
190
+ proposal: (key) => proposals.get(key),
191
+ units: map.units,
192
+ blocks: map.blocks,
193
+ boxes: map.boxes,
194
+ shown: (unit) => getLayout()?.types?.get(unit),
195
+ element: (unit) => unitElement(unit),
196
+ };
197
+ /** Write the page's marks from the table (sync-state.mjs): before every look at the page, after every answer. */
198
+ function marks() {
199
+ learn();
200
+ // What is sealed goes by its id from the moment it is on the page (the kit keeps a component's
201
+ // state under it): one made there proposes it now, where any other block does with its commit.
202
+ if (SEALED) for (const node of root.querySelectorAll(`:is(${SEALED})[data-ge-new]`)) proposedId(node);
203
+ const nodes = [...root.querySelectorAll(`[${ELEMENT_ID}]`)];
204
+ const index = new Map();
205
+ doubled = [];
206
+ for (const node of nodes) {
207
+ const key = node.getAttribute(ELEMENT_ID);
208
+ if (index.has(key)) doubled.push(key);
209
+ else index.set(key, node);
210
+ }
211
+ const shown = (id) => {
212
+ const key = map.units[id]?.cid;
213
+ return key !== undefined && table.get(key)?.unit === id ? index.get(key) ?? null : null;
214
+ };
215
+ // A unit a commit deleted that the page shows again as another type than it had (an item put
216
+ // back by undo, then its list turned into paragraphs): its source would put back what it was,
217
+ // where that cannot stand, so it is a block made on the page instead, as is what went with it.
218
+ for (const [id, entry] of removed) {
219
+ const element = shown(id);
220
+ if (!element || entry.type === undefined || ownTypeOf(element) === entry.type) continue;
221
+ touched(); // its text blocks leave the baselines
222
+ removed.delete(id);
223
+ for (const [other, owner] of removedWith) {
224
+ if (owner !== id) continue;
225
+ removedWith.delete(other);
226
+ drop(other);
227
+ }
228
+ drop(id);
229
+ }
230
+ project(nodes, { ...view, element: shown });
231
+ // Words no commit will send any more, given up for the file's (保留 AI 的, 使用最新版本; the page shows
232
+ // the file's once nothing else waits), and what the reader would write beside them: read-only until
233
+ // then, whatever element stands for them (the block converted, or put back by undo), as the server's
234
+ // locks are (a hover says why).
235
+ if (discarded.size > 0) {
236
+ for (const host of root.querySelectorAll("[contenteditable='true']")) if (discarded.has(issueKey("text", blockOf(host))) || givenWords(unitAround(host))) lockHost(host, "given-up");
237
+ }
238
+ }
239
+
240
+ // ---------- naming ----------
241
+ const openingHashes = restore?.openingHashes ?? map.units.map((unit) => unit.hash);
242
+ const unitRef = (id) => {
243
+ const unit = map.units[id];
244
+ // With its id, what it was when the page opened too: found by that if a file written from an
245
+ // earlier read has lost the id.
246
+ if (unit.blockId) return { key: id, id: unit.blockId, hash: unit.hash, ...(id < openingUnits ? { open: id, openHash: openingHashes[id] } : {}) };
247
+ unit.proposed ??= randomBlockId(); // the id it is to get: the same in every commit until it has one
248
+ return { key: id, open: id < openingUnits ? id : -1, hash: unit.hash, blockId: unit.proposed };
249
+ };
250
+ const blockRef = (id) => {
251
+ const block = map.blocks[id];
252
+ const before = sources.get(id) ?? "";
253
+ if (block.kind === "title") return { title: true, before };
254
+ if (block.unit === null || block.unit === undefined) return id < openingBlocks ? { open: id, before } : { outside: true, before };
255
+ return block.self ? { unit: unitRef(block.unit), self: true, before } : { unit: unitRef(block.unit), path: block.path, before };
256
+ };
257
+ const boxRef = (id) => {
258
+ const box = map.boxes[id];
259
+ if (box.kind === "main") return { main: true };
260
+ if (box.unit === null || box.unit === undefined) return { kind: box.kind, ...(box.key !== undefined && box.key !== null ? { key: box.key } : {}) }; // in no unit: the report's header, a detail card's content (by its key)
261
+ return { unit: unitRef(box.unit), kind: box.kind, ...(box.key !== undefined && box.key !== null ? { key: box.key } : {}) };
262
+ };
263
+ const placeRef = (place) => place.into !== undefined ? { into: boxRef(place.into) }
264
+ : place.after !== undefined ? { after: unitRef(pastWaiting(place.after)) } : { before: unitRef(place.before) };
265
+ // The box of the layout a unit stands in, or undefined.
266
+ const layoutBox = (id) => [...(getLayout()?.orders ?? [])].find(([, list]) => list.includes(id))?.[0];
267
+ // The units the layout has right after a unit in its box that wait off the page where the file has them
268
+ // still (a deletion held for the reader, or given up, of a unit the file has one of: not `lacks`). What is
269
+ // placed after the unit goes after them, as the page shows none of them, and what stood after them stays
270
+ // after them (a section dissolved after a paragraph whose deletion waits: its blocks go after that
271
+ // paragraph, not before it: pastWaiting); where the unit leaves a container the commit deletes, they go
272
+ // with it (a section dissolved around such a paragraph: build).
273
+ const waitingAfter = (id) => {
274
+ const order = getLayout()?.orders.get(layoutBox(id)) ?? [];
275
+ const units = [];
276
+ for (const other of order.slice(order.indexOf(id) + 1)) {
277
+ const key = issueKey("unit", other);
278
+ const waits = issues.get(key) ?? discarded.get(key);
279
+ if (unitElement(other) || !waits || lacks(waits)) break;
280
+ units.push(other);
281
+ }
282
+ return units;
283
+ };
284
+ const pastWaiting = (id) => waitingAfter(id).at(-1) ?? id;
285
+ // The end of the box an element is in, when the box is one the file has.
286
+ const endOf = (element) => {
287
+ const value = element?.parentElement?.closest("[data-ge-box]")?.getAttribute("data-ge-box") ?? "";
288
+ return value === "" ? null : { into: Number(value) };
289
+ };
290
+ // The id a block made on the page is to have in the file, proposed by the page and kept for its
291
+ // identity, so every commit that carries the block proposes the same one: sent twice (its answer
292
+ // lost, or again from the crash buffer), a commit makes it once (commitEdits). A unit put back
293
+ // proposes the id it had, and so does one the file dropped, written anew (a column of a row put back).
294
+ const proposedId = (element) => {
295
+ if (!element) return undefined;
296
+ const unit = idOf(element);
297
+ if (unit !== null && map.units[unit].blockId) return map.units[unit].blockId;
298
+ const key = identityOf(element);
299
+ if (!proposals.has(key)) proposals.set(key, map.units[dropped.get(key)?.unit]?.blockId ?? randomBlockId()); // never one an element carries: a copy's would be its original's
300
+ return proposals.get(key);
301
+ };
302
+ const identity = (element) => element?.getAttribute?.(ELEMENT_ID) ?? null;
303
+ // The ids the page knows its blocks by: its units' (the file's, as far as the page knows) and those
304
+ // it proposed for the blocks it made. Every commit carries them, so that a unit the page names by
305
+ // what it said when it opened is never one of these (commitEdits, `pageIds`): a block's hash
306
+ // leaves its id out, and an empty paragraph made on the page says what an empty one of the file says.
307
+ const pageIds = () => [...new Set([...map.units.map((unit) => unit.blockId), ...proposals.values()].filter(Boolean))];
308
+ // The units a unit is in, as the map has them (the file's, as far as the page knows): the unit
309
+ // itself first, then the one around it, outward.
310
+ function* chain(id) {
311
+ for (let at = id; at !== null && at !== undefined && map.units[at]; at = map.units[at].parent) yield at;
312
+ }
313
+ const isInside = (unit, id) => { // unit is somewhere in unit `id`
314
+ for (const at of chain(unit.parent)) if (at === id) return true;
315
+ return false;
316
+ };
317
+
318
+ // What a commit leaves out: a block or operation held back (unless `all`), a block that must keep
319
+ // some text and has none (a new section's title), a block made on the page the file refused.
320
+ // A unit (or one around it) whose change is held back or given up, where that holds its words
321
+ // (holdsWords, the `words` of what was given up; a deletion those of the blocks it took off the page,
322
+ // `off`): so are the texts in it. Any other holds none of them: they go as words do, the change held or
323
+ // given up alone.
324
+ const givenWords = (id, off = false) => [...chain(id)].some((at) => {
325
+ const given = discarded.get(issueKey("unit", at));
326
+ return given?.words || (off && given?.kind === "remove");
327
+ });
328
+ const unitHeld = (id, all, off = false) => givenWords(id, off) || (!all && [...chain(id)].map((at) => issues.get(issueKey("unit", at))).some((entry) => entry && holdsWords(entry, off)));
329
+ // Whether a unit's change is one the reader gave up: the one its card was about, as it was (of what a
330
+ // component writes, each part as it was: givenParts), or any where the file has no one unit for it. Another
331
+ // change of that kind made since (the block moved elsewhere, converted to another type, its table's rows
332
+ // changed too) goes as any does, and asks where the file has the unit otherwise.
333
+ const givenUp = (change) => {
334
+ const given = discarded.get(issueKey("unit", change.id));
335
+ if (!given?.kind || lacks(given)) return Boolean(given);
336
+ if (given.kind !== kindOf(change)) return false;
337
+ if (change.source) return Object.keys(change.source).every((name) => givenParts(change).has(name));
338
+ return !given.change || JSON.stringify({ ...given.change, id: undefined }) === JSON.stringify({ ...change, id: undefined });
339
+ };
340
+ // Of what a unit's component writes (`change.source`), the parts given up as they are now: none other goes.
341
+ const givenParts = (change) => {
342
+ const given = discarded.get(issueKey("unit", change.id))?.change?.source;
343
+ return new Set(Object.keys(change.source).filter((name) => given?.[name] === change.source[name]));
344
+ };
345
+ // A text block in the content of a keyed block (a detail card) made on the page and saved,
346
+ // while the block is off the page (its making undone): the file keeps it, and it is no unit to
347
+ // delete, so nothing in it is taken out either (adoptKeyed).
348
+ const keyedAway = (edit) => {
349
+ const key = map.blocks[edit.id]?.cid;
350
+ return key !== undefined && [...keyedBlocks.values()].some((block) => !block.isConnected && block.querySelector(`[${ELEMENT_ID}="${key}"]`));
351
+ };
352
+ // A text block off the page of a kind that goes with its block alone (a part a component's drawing
353
+ // took out, the block still there): the file keeps it, as nothing can take it out.
354
+ const stays = (edit) => edit.parts.length === 0 && !capsFor(map.blocks[edit.id].kind).split(" ").includes("remove");
355
+ const skipText = (edit, blanks, all) => {
356
+ const host = hostOf(edit.id);
357
+ return (issues.has(issueKey("text", edit.id)) && !all) || discarded.has(issueKey("text", edit.id)) || (host && blanks.has(host)) || !map.blocks[edit.id] || unitHeld(map.blocks[edit.id].unit, all, !host) || keyedAway(edit) || stays(edit);
358
+ };
359
+ // Of what a unit's component wrote (`change.source`), the parts a write held back for the reader holds,
360
+ // with those one edit wrote together with any of them: the rest goes, as the write of another part
361
+ // does where someone else changed one (source/commit.mjs). Null where no such write is held.
362
+ const heldParts = (change) => {
363
+ const entry = issues.get(issueKey("unit", change.id));
364
+ if (!change.source || !entry?.change?.source) return null;
365
+ const held = new Set(Object.keys(entry.change.source));
366
+ const element = unitElement(change.id);
367
+ const groups = element ? pageOfUnit(element)?.together?.(element, [...new Set([...held, ...Object.keys(change.source)])]) ?? [] : [];
368
+ for (const group of groups) if (group.some((name) => held.has(name))) group.forEach((name) => held.add(name));
369
+ return held;
370
+ };
371
+ const skipUnit = (change, blanks, all) => {
372
+ if (change.insert) {
373
+ const element = madeFrom.get(change);
374
+ return !element || (refused.has(identity(element)) && !all) || [...blanks].some((node) => element.contains(node));
375
+ }
376
+ const held = all ? null : heldParts(change);
377
+ const apart = Boolean(held) && Object.keys(change.source).some((name) => !held.has(name));
378
+ if ((issues.has(issueKey("unit", change.id)) && !all && !apart) || givenUp(change) || (change.source && unwritten.has(change.id))) return true;
379
+ // A unit in a block not finished (a new row of columns one of which is still empty): it stays as
380
+ // the file has it, its move and its conversion going with that block once it is written.
381
+ if (blanks.size > 0) {
382
+ const element = unitElement(change.id);
383
+ if (element && unfinished(blanks).some((top) => top.contains(element))) return true;
384
+ }
385
+ if (change.move && removed.has(change.id)) {
386
+ const element = unitElement(change.id);
387
+ return !element || (refused.has(identity(element)) && !all);
388
+ }
389
+ return false;
390
+ };
391
+
392
+ // ---------- what changed since ----------
393
+ // collect() reads the page and what the commits know of it (the baselines, the layout, the table
394
+ // the marks are written from), and nothing else. Asked again with neither changed, it gives the
395
+ // answer it gave: a page that is left asked twelve to seventeen times (the last keystrokes
396
+ // counted, the flush and the crash buffer in prepare-close, then all of it again in pagehide and
397
+ // visibilitychange as its frame went: 0.3-1 s on a large report before the next one was even
398
+ // asked for), and so does a commit after the pause that was counted, each a pass over every block
399
+ // of the report. The page changing is any mutation of the document (a MutationObserver, its
400
+ // records taken at once when asked: takeRecords) but the attributes of <html> itself (data-ge-save
401
+ // and the like, which no block is in); what the commits know changes
402
+ // with an answer taken in (apply), a decision on a conflict (resolve), a block put back (putBack)
403
+ // and a unit to name (idFor), which count as a change too (touched). The marks collect writes
404
+ // first are a change like any other: they are counted after they are written. A document of its
405
+ // own with no window (recover's) has no observer: there every collect is anew. Watched from once
406
+ // the page has its first marks (the end of createCommits).
407
+ const Observer = root.defaultView?.MutationObserver ?? null;
408
+ let changes = 0;
409
+ const matters = (records) => records.some((record) => record.type !== "attributes" || record.target !== root.documentElement);
410
+ const observer = Observer ? new Observer((records) => { if (matters(records)) changes += 1; }) : null;
411
+ const touched = () => { changes += 1; };
412
+ /**
413
+ * Where the page and what the commits know of it stand: the same number as long as neither
414
+ * changed (null when not watched: never the same).
415
+ */
416
+ function generation() {
417
+ if (!observer) return null;
418
+ if (matters(observer.takeRecords())) changes += 1;
419
+ return changes;
420
+ }
421
+ let last = null; // collect()'s last answer: { collected, generation, layout }
422
+
423
+ /**
424
+ * What changed on the page since the last commit (collectEdits), its marks written first; the
425
+ * answer it gave last when nothing changed since (generation).
426
+ */
427
+ function collect() {
428
+ const now = generation();
429
+ if (now !== null && last?.generation === now && last.layout === getLayout()) return last.collected;
430
+ marks();
431
+ const collected = collectEdits(root, baselines, getLayout());
432
+ last = { collected, generation: generation(), layout: getLayout() };
433
+ return collected;
434
+ }
435
+
436
+ /** Whether `collected` (collect()'s) is what collect() would answer now: nothing changed since. */
437
+ const current = (collected) => collected !== null && collected !== undefined && last?.collected === collected && last.generation !== null && last.generation === generation() && last.layout === getLayout();
438
+
439
+ /** How many changes the next commit would carry (of `collected`: collect()'s result). */
440
+ function waiting(collected = collect()) {
441
+ const blanks = new Set(collected.blank);
442
+ return collected.edits.filter((edit) => !skipText(edit, blanks, false)).length + collected.units.filter((change) => !skipUnit(change, blanks, false)).length
443
+ + [...naming].filter((id) => map.units[id] && !map.units[id].blockId && !removed.has(id) && unitElement(id)).length;
444
+ }
445
+
446
+ // The outermost blocks made on the page around blocks no commit can carry yet (`nodes`).
447
+ const unfinished = (nodes) => {
448
+ const roots = new Set();
449
+ for (const node of nodes) {
450
+ let top = null;
451
+ for (let at = node; at && at !== root; at = at.parentElement) if (at.hasAttribute?.("data-ge-new")) top = at;
452
+ if (top) roots.add(top);
453
+ }
454
+ return [...roots].filter((top) => ![...roots].some((other) => other !== top && other.contains(top)));
455
+ };
456
+
457
+ // Blocks no commit can carry yet (a new disclosure's title still empty, a new block with no place,
458
+ // a row of columns one of which holds nothing): the text of the outermost block made on the page
459
+ // around each, where it has any. A block of the file that must keep some text is not one of them:
460
+ // the file has its text.
461
+ function drafts({ blank, unplaced }) {
462
+ return unfinished([...blank, ...unplaced]).map(blocksText).filter(Boolean);
463
+ }
464
+
465
+ /**
466
+ * How many changes the next commit would carry, and how many blocks none can carry yet (drafts):
467
+ * counted with or without text, as the page shows them and the file does not have them (an empty
468
+ * paragraph is written once it has a place: a row holding one beside an empty code block is not).
469
+ */
470
+ function counts(collected = collect()) {
471
+ return { waiting: waiting(collected), drafts: unfinished([...collected.blank, ...collected.unplaced]).length };
472
+ }
473
+
474
+ /** The texts of the page's blocks, as the reader sees them (the crash buffer's safety net: checkLeft). */
475
+ const texts = () => [...root.querySelectorAll("[data-ge-block], [data-ge-new], [data-ge-part]")].filter((node) => node.getAttribute("contenteditable") === "true").map(ownText).filter(Boolean);
476
+
477
+ // Words typed in a text block the file writes from elsewhere, before it has: no commit can carry them.
478
+ const typedParts = () => [...root.querySelectorAll("[data-ge-part]")].filter((host) => host.getAttribute("data-ge-part") !== ownHTML(host)).map(ownText).filter(Boolean);
479
+
480
+ /**
481
+ * What the crash buffer keeps: the commit of everything not in the file (what is held back too),
482
+ * the texts of the blocks no commit can carry yet, and those typed in a block the commit makes
483
+ * (they go with the next commit, once it has made it). { request, drafts: [text], typed: [text] }
484
+ * `collected`: collect()'s result, when the caller has it from the same task.
485
+ */
486
+ function buffer(collected = collect()) {
487
+ return { request: build({ held: true, whole: true, collected })?.request ?? null, drafts: drafts(collected), typed: typedParts() };
488
+ }
489
+
490
+ /**
491
+ * The commit of everything changed since the last one: { request, pending }, or null when
492
+ * nothing is left to commit. What is held back is left out, unless `held` (the crash buffer), and
493
+ * of what components wrote what one commit carries goes, unless `whole` (the crash buffer again,
494
+ * which keeps all a page leaves).
495
+ */
496
+ function build({ held: all = false, whole = false, collected = null } = {}) {
497
+ const { edits, units: changes, blank } = collected ?? collect();
498
+ const snapshot = snapshotLayout(root);
499
+ const blanks = new Set(blank);
500
+ const texts = [];
501
+ const ops = [];
502
+ const touch = new Map();
503
+ // `moves`: the units the page shows moved, whether or not the commit carries their move (apply: a unit that
504
+ // waits stands where the file has it).
505
+ const pending = { texts: new Map(), units: new Map(), made: new Map(), restores: new Map(), html: new Map(), snapshot, moves: new Set(changes.filter((change) => change.move).map((change) => change.id)) };
506
+ // Every new text block's html as sent: what it starts from once committed.
507
+ for (const host of root.querySelectorAll("[data-ge-new][contenteditable='true'], [data-ge-convert] [contenteditable='true'], [contenteditable='true'][data-ge-convert]")) pending.html.set(host, serializeInner(host));
508
+ // A unit takes along what its component says (a Sheet tab, its panel: boxOwner), so a change in
509
+ // such a box is a change of that unit too (its hash covers the box).
510
+ const touchOwner = (box) => {
511
+ const owner = box ? boxOwner(box) : null;
512
+ const id = owner ? idOf(owner) : null;
513
+ if (id !== null && !removed.has(id)) touchUnits(id);
514
+ };
515
+ // The boxes a unit stood in when the last commit left it: what leaves them changes them. Each box
516
+ // as a search for it finds it, from one index of the page's boxes (nothing here moves one).
517
+ let boxElements = null;
518
+ const touchLeft = (id) => {
519
+ for (const [box, order] of getLayout()?.orders ?? []) if (order.includes(id)) touchOwner((boxElements ??= firstByAttribute(root, "data-ge-box")).get(String(box)) ?? null);
520
+ };
521
+ function touchUnits(id) {
522
+ for (const at of chain(id)) {
523
+ if (touch.has(at)) continue;
524
+ touch.set(at, unitRef(at));
525
+ touchLeft(at);
526
+ }
527
+ }
528
+ const touchAround = (node) => {
529
+ for (let at = node; at?.nodeType === 1; at = at.parentElement) {
530
+ const id = idOf(at);
531
+ if (id !== null && !removed.has(id) && !touch.has(id)) touch.set(id, unitRef(id));
532
+ if (at.hasAttribute("data-ge-box")) touchOwner(at);
533
+ }
534
+ };
535
+ // The units inside one that moves go to a new indentation with it: their text changes too.
536
+ const touchWithin = (element) => {
537
+ for (const node of element?.querySelectorAll?.(`[${ELEMENT_ID}]`) ?? []) {
538
+ const id = idOf(node);
539
+ if (id !== null && !removed.has(id) && !touch.has(id)) touch.set(id, unitRef(id));
540
+ }
541
+ };
542
+ const key = () => `n${serial++}`;
543
+ // The parts of what a component wrote that this commit carries: as many as one commit takes (`most`,
544
+ // its page object's `dataMost`, in bytes) with what it carries already, the first whatever it is (the
545
+ // server says what it makes of it), the parts one edit wrote together (`groups`) all or none. Those
546
+ // left out still differ from what the file holds, and go with the next commit.
547
+ let carried = 0;
548
+ const within = (source, most, groups) => {
549
+ const kept = {};
550
+ const apart = Object.keys(source).filter((name) => !groups.some((group) => group.includes(name))).map((name) => [name]);
551
+ for (const names of [...groups, ...apart]) {
552
+ const bytes = names.reduce((sum, name) => sum + new TextEncoder().encode(source[name]).length, 0);
553
+ if (carried > 0 && carried + bytes > most) continue;
554
+ carried += bytes;
555
+ for (const name of names) kept[name] = source[name];
556
+ }
557
+ return Object.fromEntries(Object.keys(source).filter((name) => Object.hasOwn(kept, name)).map((name) => [name, kept[name]]));
558
+ };
559
+ const stoodOf = (id) => (stood.has(id) ? { stood: stood.get(id) } : {});
560
+ // A deleted unit undo put back: its source, to be written again.
561
+ const restoreNode = (id, element) => {
562
+ const nodeKey = key();
563
+ const { source, seal } = removed.get(id);
564
+ pending.made.set(nodeKey, element);
565
+ pending.restores.set(nodeKey, id);
566
+ return { restore: { source, seal }, key: nodeKey, blockId: proposedId(element), ...stoodOf(id) };
567
+ };
568
+ const nodeRef = (node) => {
569
+ // A copy of a card or a table: the file copies the unit's own source (with the keys the
570
+ // paste gave the charts drawn from tables in it: core.mjs nodeOf).
571
+ if (node.copy !== undefined) {
572
+ const nodeKey = key();
573
+ const element = madeFrom.get(node);
574
+ if (element) pending.made.set(nodeKey, element);
575
+ return { copy: unitRef(node.copy), key: nodeKey, blockId: proposedId(element), ...(node.keys ? { keys: node.keys } : {}) };
576
+ }
577
+ if (node.unit !== undefined) {
578
+ if (removed.has(node.unit)) return restoreNode(node.unit, madeFrom.get(node) ?? unitElement(node.unit));
579
+ touchUnits(map.units[node.unit]?.parent); // taken from its place
580
+ touchLeft(node.unit);
581
+ touchWithin(madeFrom.get(node) ?? unitElement(node.unit));
582
+ return { unit: unitRef(node.unit), ...stoodOf(node.unit) };
583
+ }
584
+ const nodeKey = key();
585
+ const element = madeFrom.get(node);
586
+ if (element) pending.made.set(nodeKey, element);
587
+ const { children, ...rest } = node;
588
+ const blockId = BLOCK_TYPES[node.type]?.keyed ? undefined : proposedId(element); // a keyed block (a detail card) is known by its own id
589
+ // One of the file put back around its blocks, still of the type the source says.
590
+ const was = dropped.get(identity(element));
591
+ const put = was?.around?.type === node.type ? { restore: { source: was.around.source, seal: was.around.seal } } : {};
592
+ return { ...rest, ...put, ...stoodOf(was?.unit), key: nodeKey, ...(blockId ? { blockId } : {}), ...(children ? { children: children.map(nodeRef) } : {}) };
593
+ };
594
+
595
+ for (const edit of edits) {
596
+ const textKey = issueKey("text", edit.id); // the key of a conflict of its text too
597
+ const host = hostOf(edit.id);
598
+ if (skipText(edit, blanks, all)) continue;
599
+ const parts = edit.parts.map((part) => {
600
+ if (part.keep) return { keep: true, html: part.html };
601
+ const partKey = key();
602
+ const element = madeFrom.get(part);
603
+ pending.made.set(partKey, element);
604
+ return { tag: part.tag, html: part.html, ...(part.classes ? { classes: part.classes } : {}), ...(part.attributes ? { attributes: part.attributes } : {}), key: partKey, ...(element ? { blockId: proposedId(element) } : {}) };
605
+ });
606
+ texts.push({ key: textKey, block: blockRef(edit.id), parts });
607
+ pending.texts.set(textKey, { id: edit.id, parts, before: sources.get(edit.id) ?? "" });
608
+ if (host) touchAround(host);
609
+ else touchUnits(map.blocks[edit.id].unit);
610
+ }
611
+
612
+ // Units put back (undo of a deletion an earlier commit made): what changed meanwhile in them and in
613
+ // what they hold or took along (converted, moved within or out, resized) waits for the next commit,
614
+ // once the file has them back (it names nothing of them before).
615
+ const restored = changes.filter((change) => change.move && removed.has(change.id)).map((change) => change.id);
616
+ const restoring = restored.map(unitElement).filter(Boolean);
617
+ // The units a change concerns: its own, or those a block made on the page takes along.
618
+ const concerned = (change) => {
619
+ if (!change.insert) return [change.id];
620
+ const units = [];
621
+ const visit = (node) => {
622
+ if (node.unit !== undefined) units.push(node.unit);
623
+ node.children?.forEach(visit);
624
+ };
625
+ visit(change.insert);
626
+ return units;
627
+ };
628
+ // Units that went with one deleted whole (an earlier commit) and stand on the page again without
629
+ // it (undo put it back, then the reader took them out of it; or it put back a block made on the
630
+ // page holding them): the file has them only inside it, so it comes back first, from its source,
631
+ // where the first of them stands, holding them; taking them out of it (and it away) goes with the
632
+ // next commit, once the file has them back.
633
+ const ownerOf = (change) => (change.move || change.insert ? concerned(change).map((id) => removedWith.get(id)).find((owner) => owner !== undefined) : undefined);
634
+ const orphans = new Map(); // owner -> the first of them (its move, or the block made holding it)
635
+ for (const change of changes) {
636
+ const owner = ownerOf(change);
637
+ if (owner !== undefined && removed.has(owner) && !unitElement(owner) && !orphans.has(owner)) orphans.set(owner, change);
638
+ }
639
+ const withinRestored = (change) => {
640
+ if (change.move && removed.has(change.id)) return false;
641
+ if (concerned(change).some((id) => restored.includes(removedWith.get(id)))) return true;
642
+ if (change.insert) return false;
643
+ const element = unitElement(change.id);
644
+ return Boolean(element) && restoring.some((other) => other.contains(element));
645
+ };
646
+ // Items the file has holding lists (in their own box) that the page turned into another type:
647
+ // what this commit does to their lists (taken out of them, or away) goes first, their
648
+ // conversion and their move (a paragraph again, in a box that takes no items) with the next
649
+ // commit, once the file has them holding none (an indent undone while it was on its way).
650
+ const holding = new Set([...(getLayout()?.orders ?? [])].filter(([box, order]) => boxRole(map.boxes[box]?.kind) === "nest" && order.length > 0).map(([box]) => map.boxes[box].unit));
651
+ const waitsForLists = (change) => holding.has(change.id) && Boolean(change.replace || change.move) && ownTypeOf(unitElement(change.id) ?? root.documentElement) !== getLayout()?.types?.get(change.id);
652
+ const removing = new Set(); // the units this commit deletes (a unit's deletion is among its changes before any move)
653
+ const leavesRemoved = (id) => removing.has(map.boxes[layoutBox(id)]?.unit);
654
+ // A unit held for the reader that leaves a container this commit deletes (a section dissolved around a block
655
+ // whose conversion waits) goes with the blocks around it, its place alone (`along`): what it waits with stands.
656
+ const leaves = (change) => {
657
+ const held = change.move && !all ? issues.get(issueKey("unit", change.id)) : null;
658
+ return held?.kind === "unit" && kindOf(held.change) !== "move" && leavesRemoved(change.id);
659
+ };
660
+ for (const change of changes) {
661
+ const opKey = `u${serial++}`; // this operation's key in the request (the key of its conflict is issueKey("unit", unit))
662
+ const along = leaves(change);
663
+ if (skipUnit(change, blanks, all || along)) continue;
664
+ if (withinRestored(change) || waitsForLists(change)) continue;
665
+ const owner = ownerOf(change);
666
+ if (orphans.has(owner)) {
667
+ if (orphans.get(owner) !== change) continue;
668
+ const element = removed.get(owner).element;
669
+ const node = restoreNode(owner, element);
670
+ ops.push({ key: opKey, insert: node, place: placeRef(change.move ?? change.place) });
671
+ pending.units.set(opKey, { insert: true, element, node });
672
+ continue;
673
+ }
674
+ if (change.insert) {
675
+ const element = madeFrom.get(change);
676
+ const node = nodeRef(change.insert);
677
+ const place = (elsewhere.has(identity(element)) && endOf(element)) || change.place;
678
+ ops.push({ key: opKey, insert: node, place: placeRef(place) });
679
+ pending.units.set(opKey, { insert: true, element, node });
680
+ touchAround(element.parentElement);
681
+ continue;
682
+ }
683
+ const element = unitElement(change.id);
684
+ if (change.move && removed.has(change.id)) {
685
+ // Deleted by an earlier commit, back on the page (undo).
686
+ const node = restoreNode(change.id, element);
687
+ ops.push({ key: opKey, insert: node, place: placeRef((elsewhere.has(identity(element)) && endOf(element)) || change.move) });
688
+ pending.units.set(opKey, { insert: true, element, node });
689
+ touchAround(element.parentElement);
690
+ continue;
691
+ }
692
+ const unit = unitRef(change.id);
693
+ const hash = map.units[change.id].hash;
694
+ let op;
695
+ if (change.remove) {
696
+ // A container that goes when emptied (a list, a quote) names the units it held: if the file
697
+ // has it changed by someone else, only these go (commitEdits), what they added stays.
698
+ const items = getLayout()?.elements.get(change.id)?.hasAttribute("data-ge-pure") ? map.units.filter((other) => other.parent === change.id && !removed.has(other.id)).map((other) => unitRef(other.id)) : [];
699
+ op = { remove: true, hash, ...(items.length > 0 ? { items } : {}) };
700
+ touchUnits(map.units[change.id].parent);
701
+ touchLeft(change.id);
702
+ } else if (change.move) {
703
+ op = { move: placeRef((movedElsewhere.has(change.id) && endOf(element)) || change.move), ...stoodOf(change.id) };
704
+ touchUnits(map.units[change.id].parent);
705
+ touchLeft(change.id);
706
+ touchAround(element);
707
+ touchWithin(element);
708
+ } else if (change.replace) op = { replace: change.replace, hash };
709
+ else if (change.table) op = { table: change.table, hash }; // its rows and columns (components/table/grid.mjs)
710
+ else if (change.source) { // what its component wrote, but the parts a write held back holds (heldParts) and those given up (givenParts)
711
+ const waits = new Set([...(all ? [] : heldParts(change) ?? []), ...givenParts(change)]);
712
+ const going = Object.fromEntries(Object.entries(change.source).filter(([name]) => !waits.has(name)));
713
+ const written = pageOfUnit(element).together?.(element, Object.keys(going)) ?? [];
714
+ const source = whole ? going : within(going, pageOfUnit(element).dataMost, written);
715
+ if (Object.keys(source).length === 0) continue;
716
+ // What each part held as the file last said: it goes in where someone else changed only the rest,
717
+ // the parts one edit wrote together together.
718
+ const held = map.units[change.id].parts ?? {};
719
+ const parts = Object.fromEntries(Object.keys(source).filter((name) => held[name]).map((name) => [name, held[name]]));
720
+ const together = written.filter((group) => group.every((name) => Object.hasOwn(source, name)));
721
+ op = { source, hash, parts, ...(together.length > 0 ? { together } : {}), definition: change.definition };
722
+ } else if (change.retag) op = { retag: change.retag };
723
+ else if (change.toggle) op = { toggle: change.toggle, on: change.on };
724
+ else if (change.size) op = { size: change.size, value: change.value };
725
+ else if (change.attribute) op = { attribute: change.attribute, value: change.value };
726
+ else continue;
727
+ // The reader said so, of a change of this kind: whatever the file did to it, so long as the file still
728
+ // has the unit as the refusal they were shown gave it (its `hash`). A change of another kind made since
729
+ // (the block converted anyway deleted meanwhile) is held to what the page saw, as any is.
730
+ const word = forced.get(change.id);
731
+ if (word && word.kind === kindOf(change)) Object.assign(op, { force: true }, word.hash !== undefined ? { hash: word.hash } : {});
732
+ if (!change.remove) touchAround(element);
733
+ ops.push({ key: opKey, unit, ...op });
734
+ pending.units.set(opKey, { id: change.id, change: op.source ? { ...change, source: op.source } : change, ...(op.source ? { parts: op.parts } : {}), ...(op.force ? { forced: true } : {}), ...(along ? { along } : {}) });
735
+ // What waits off the page right after a unit that leaves a container this commit deletes (a section
736
+ // dissolved around a paragraph whose deletion waits: waitingAfter) goes with it, where the file has it
737
+ // still, so that the blocks keep their order and none stands in the container alone, its place alone
738
+ // too (`along`). An answer that does not take such a place says nothing: the unit waits as it did.
739
+ if (change.remove) removing.add(change.id);
740
+ for (const id of change.move && !all && leavesRemoved(change.id) ? waitingAfter(change.id) : []) {
741
+ const alongKey = `u${serial++}`;
742
+ ops.push({ key: alongKey, unit: unitRef(id), move: op.move });
743
+ pending.units.set(alongKey, { id, change: { id, move: change.move }, along: true });
744
+ }
745
+ }
746
+ // The units asked to be named, each with the id it proposes (unitRef), alone when nothing else changed.
747
+ pending.named = [];
748
+ for (const id of naming) {
749
+ if (!map.units[id] || map.units[id].blockId) naming.delete(id);
750
+ else if (!removed.has(id) && unitElement(id)) {
751
+ if (!touch.has(id)) touch.set(id, unitRef(id));
752
+ pending.named.push(id);
753
+ }
754
+ }
755
+ marks(); // the ids proposed
756
+ if (texts.length === 0 && ops.length === 0 && pending.named.length === 0) return null;
757
+ return { request: { opening: map.version, texts, units: ops, touch: [...touch.values()], pageIds: pageIds() }, pending };
758
+ }
759
+
760
+ // ---------- taking in the answer ----------
761
+ // A text block of the page is block `id` of the map from now on (a new one, unless it is one
762
+ // already): in the page unit `unit` (null: in none), at the place the file
763
+ // says (`file`: { self } or { path }, never counted on the page). It starts from what the commit
764
+ // sent when it sent it as new (a block made on the page, the text of a unit turned into another
765
+ // type), else from what it shows, when that is what the file says it is (`file`: { inner, atoms };
766
+ // a copy's text, which the page shows as it was pasted). Showing other words (typed while the commit was on its way into a block the file
767
+ // has, though the page took it for gone), it starts from the file's text, as the page shows
768
+ // that, so that the page's own words go with the next commit.
769
+ const registerBlock = (host, pending, file = null, unit = null) => {
770
+ const start = () => pending.html.get(host)
771
+ ?? (file && !matchesSource(host, file.inner) ? shownAs(host, file.inner, file.atoms) : null)
772
+ ?? serializeInner(host);
773
+ const known = blockOf(host);
774
+ if (known !== null && map.blocks[known]) {
775
+ const block = map.blocks[known];
776
+ if (file?.path && !block.self) block.path = file.path;
777
+ if (block.gone || pending.html.has(host)) {
778
+ const html = start();
779
+ Object.assign(block, { gone: false, tag: host.localName, kind: host.getAttribute("data-ge-kind") ?? block.kind });
780
+ sources.set(known, html);
781
+ baselines.set(known, html);
782
+ }
783
+ return known;
784
+ }
785
+ const id = map.blocks.length;
786
+ // Words of a block the file wrote from what it holds (`source`), as the page made them: those it
787
+ // made them from were old, or hold what only the file's text says how to write back, and it shows
788
+ // the file's. Others were typed, and go with the next commit.
789
+ if (file && host.getAttribute("data-ge-part") === ownHTML(host) && (file.atoms || !matchesSource(host, file.inner))) asLoaded(host, file);
790
+ const html = start();
791
+ map.blocks.push({
792
+ id,
793
+ tag: host.localName,
794
+ kind: host.getAttribute("data-ge-kind") ?? "flow",
795
+ caps: (host.getAttribute("data-ge-caps") ?? "").split(/\s+/).filter(Boolean),
796
+ inner: html,
797
+ atoms: [],
798
+ unit,
799
+ ...(file?.self ? { self: true } : file?.path ? { path: file.path } : {}), // where in its unit the file has it
800
+ });
801
+ bind(host, "block", id);
802
+ sources.set(id, html);
803
+ baselines.set(id, html);
804
+ return id;
805
+ };
806
+ // A text block as a load of the page shows the file's (its inline elements of the report's own
807
+ // atoms, written back as they are: core.mjs prepareBlock), the caret where it stood.
808
+ const asLoaded = (host, { inner, atoms = [], kind, caps }) => {
809
+ const selection = host.ownerDocument.getSelection();
810
+ const place = selection?.rangeCount && host.contains(selection.anchorNode) ? rangeOffsets(host, selection.getRangeAt(0)) : null;
811
+ prepareBlock(host, { inner, atoms, kind, caps });
812
+ if (place) select(rangeAt(host, place));
813
+ };
814
+ // The selection in a text block someone else's words are about to be merged into, as it stands among the
815
+ // reader's words: what puts it back there once they are in. Counted from the block's end where the words
816
+ // after it are still what follows it (theirs went in before it), from its start where the words before it
817
+ // are still what precedes it (theirs went in after it), else right after as much of the reader's words
818
+ // before it as the merged text holds, the place nearest where it was (theirs went in on both sides).
819
+ const caretKept = (host) => {
820
+ const selection = host.ownerDocument.getSelection();
821
+ if (!selection?.rangeCount || !host.contains(selection.anchorNode)) return () => {};
822
+ const length = () => rangeOffsets(host, rangeAt(host, [0, Infinity]))[1];
823
+ const words = (from, to) => rangeAt(host, [from, to]).toString();
824
+ const [from, to] = rangeOffsets(host, selection.getRangeAt(0));
825
+ const [total, before, after] = [length(), words(0, from), words(to, Infinity)];
826
+ // How many characters of `text` there are up to the end of the longest end of the reader's words before
827
+ // it that `text` holds, the one nearest where it was.
828
+ const pastTail = (text) => {
829
+ for (let size = before.length; size > 0; size -= 1) {
830
+ const ends = [];
831
+ for (let at = text.indexOf(before.slice(-size)); at >= 0; at = text.indexOf(before.slice(-size), at + 1)) ends.push(at + size);
832
+ if (ends.length > 0) return ends.reduce((best, end) => (Math.abs(end - before.length) < Math.abs(best - before.length) ? end : best));
833
+ }
834
+ return 0;
835
+ };
836
+ // The place `count` characters of the block's words in (a line break is a place, and no character).
837
+ const placeOf = (count) => {
838
+ let [low, high] = [0, length()];
839
+ while (low < high) {
840
+ const middle = (low + high) >> 1;
841
+ if (words(0, middle).length < count) low = middle + 1;
842
+ else high = middle;
843
+ }
844
+ return low;
845
+ };
846
+ return () => {
847
+ const now = length();
848
+ const shift = now - total;
849
+ if (to + shift >= 0 && words(to + shift, Infinity) === after) return select(rangeAt(host, [Math.max(0, from + shift), to + shift]));
850
+ if (from <= now && words(0, from) === before) return select(rangeAt(host, [from, Math.min(to, now)]));
851
+ const place = placeOf(pastTail(words(0, Infinity)));
852
+ select(rangeAt(host, [place, Math.min(place + to - from, now)]));
853
+ };
854
+ };
855
+ // A block the file knows by its id, not as a unit (`keyed`: a detail card).
856
+ const keyedBlock = (node) => Boolean(BLOCK_TYPES[blockTypeOf(node)]?.keyed);
857
+ const registerBox = (node, kind, key = null) => {
858
+ const id = map.boxes.length;
859
+ // A keyed block's box is named by the block's id; any other box by the unit it is in (and its
860
+ // key there: a Sheet panel's).
861
+ let keyed = node;
862
+ while (keyed && !(keyedBlock(keyed) && BLOCK_TYPES[blockTypeOf(keyed)].box === kind)) keyed = keyed.parentElement;
863
+ const entry = { id, kind, unit: unitAround(keyed ?? node), ...(keyed ? { key: keyed.id } : key !== null && key !== undefined ? { key } : {}) };
864
+ map.boxes.push(entry);
865
+ boxes?.set(id, entry);
866
+ bind(node, "box", id);
867
+ return id;
868
+ };
869
+ // ---------- what the file says ----------
870
+ // After a commit the page knows of the file only what the file says (the answer's `file`: the
871
+ // units the commit named or made, by id; commitEdits in source/), never what it sent and
872
+ // expects the file to hold: a text's `before` is the file's text (its whitespace too), a block the
873
+ // commit wrote is known whether or not it is still on the page (deleted while the commit was on
874
+ // its way, it is in the file all the same, and the next commit takes it out), and the boxes a
875
+ // unit has are those the file gives it (every list item takes lists, for one).
876
+
877
+ // The box of `element` (a unit of the page) that is the file's box of `kind` (and `key`), as its component says.
878
+ const boxElement = (element, { kind, key }) => boxIn(element, kind, key ?? null);
879
+ // The text blocks of a sealed unit (what a component drew around them moves a path off), which are
880
+ // those the file gives in its order, or null: what is sealed holds no blocks of its own.
881
+ const inFileOrder = (element, blocks) => {
882
+ if (!SEALED || !element.matches(SEALED)) return null;
883
+ const hosts = [...element.querySelectorAll("[contenteditable='true']")].filter((host) => host.parentElement?.closest("[data-ge-unit], [data-ge-unit-kind]") === element);
884
+ return hosts.length === blocks.length ? hosts : null;
885
+ };
886
+ // The text block of `element` (a unit of the page) that is the file's { self } or { path } one.
887
+ const blockElement = (element, block) => {
888
+ // Its own text: the element itself, or the span of its own its description's `title` names (a Sheet tab's name).
889
+ if (block.self) return element.matches("[contenteditable='true']") ? element : BLOCK_TYPES[blockTypeOf(element)]?.title?.(element) ?? null;
890
+ if (!Array.isArray(block.path) || block.path[0] !== "unit") return null;
891
+ const want = JSON.stringify(block.path);
892
+ const owner = (host) => host.parentElement?.closest("[data-ge-unit], [data-ge-unit-kind]");
893
+ return [...element.querySelectorAll("[contenteditable='true']")].find((host) => owner(host) === element && JSON.stringify(pathFrom(element, host)) === want) ?? null;
894
+ };
895
+
896
+ /**
897
+ * Take in what the file says of the units a commit named or made (`facts`, by id): `elements`,
898
+ * the page's elements by the id they carry, are those units; `skip`, page units whose operation
899
+ * the file refused (they keep what the page knew); `converted`, page units the commit turned
900
+ * into another type -> that change; `written`, the text blocks the commit wrote; `data`, what the next layout
901
+ * holds of what components write, by page unit (apply), to which it adds what the file says of the
902
+ * units the commit wrote whole, made or put back; `wrote`, page unit -> the parts of what its component
903
+ * writes that the commit wrote, as sent; `given`, the hashes the answer gives (`hashes`: of the units the
904
+ * page named, by page unit, and of the blocks it made, by key). Returns { orders: the order of the units in each
905
+ * box of those units, by page box id, as the file has them; madeIn: page unit made by this commit ->
906
+ * the page unit the file has it in (null: the page); arounds: page unit put back around its blocks ->
907
+ * the source it was put back with }.
908
+ *
909
+ * `alone`: nobody but this page wrote the file since the page showed it, so what the file says
910
+ * is what the page's own commits made of it, all of which the page takes in. Otherwise the file
911
+ * holds someone else's changes the page does not show yet (it shows them once nothing is left to
912
+ * commit): those must stay changes of theirs, not become what the page started from (its next
913
+ * commit would undo them: move a block back, write over their words). Then the page takes in
914
+ * only what the commit itself wrote: the blocks it made and their boxes, and the texts it sent.
915
+ */
916
+ function adopt(facts, elements, skip, converted, pending, written, alone, data, wrote, given) {
917
+ const orders = new Map();
918
+ const adopted = new Map(); // id -> [page unit id, element]
919
+ const made = new Set(); // page units this commit made (or converted)
920
+ const gone = new Set([...removed.keys(), ...removedWith.keys()]); // page units deleted whole, and what went with them: those the file has are put back
921
+ const madeIn = new Map(); // page unit made by this commit -> the page unit the file has it in (null: the page)
922
+ const parents = new Map(); // page unit -> the file's id of the unit the file has it in (null: none)
923
+ const strays = new Map(); // page unit id -> the units that went with it, standing elsewhere on the page
924
+ const arounds = new Map(); // page unit put back around its blocks -> its source (dropped's `around`)
925
+ // The keys the commit made blocks under (pending.made), by element: the answer gives the hash of each
926
+ // it holds as the page sent it.
927
+ const madeKeys = new Map([...pending.made].map(([key, element]) => [element, key]));
928
+ for (const [id, fact] of Object.entries(facts)) {
929
+ const element = elements.get(id);
930
+ if (!element) continue;
931
+ let unit = idOf(element);
932
+ if (unit !== null && skip.has(unit)) {
933
+ // Of what its component wrote, the parts that went in are the file's, the rest held.
934
+ if (wrote.has(unit)) map.units[unit].parts = { ...map.units[unit].parts, ...Object.fromEntries(Object.keys(wrote.get(unit)).filter((name) => fact.parts?.[name]).map((name) => [name, fact.parts[name]])) };
935
+ continue;
936
+ }
937
+ const fresh = unit === null;
938
+ if (fresh) {
939
+ // Put back around its blocks, it is the unit it was; else a new one.
940
+ const was = dropped.get(identity(element));
941
+ unit = was?.around ? was.unit : map.units.length;
942
+ if (!was?.around) {
943
+ const entry = { id: unit, kind: fact.kind, parent: null, hash: fact.hash };
944
+ map.units.push(entry);
945
+ units?.set(unit, entry);
946
+ }
947
+ bind(element, "unit", unit);
948
+ if (was?.around) arounds.set(unit, was.around);
949
+ if (stood.has(was?.unit)) stood.set(unit, stood.get(was.unit)); // whichever unit it comes back as
950
+ // Back under the id it had (proposedId): one unit of the map has an id, the file naming units by it.
951
+ if (was?.unit !== unit && map.units[was?.unit]?.blockId === id) map.units[was.unit].blockId = undefined;
952
+ }
953
+ if (fresh || converted.has(unit)) made.add(unit);
954
+ // Written whole, from its template, what was copied or the source it went with: what its component
955
+ // writes is what the file says it holds, whatever the page shows (that goes with the next commit).
956
+ if (fact.markup !== undefined && (made.has(unit) || gone.has(unit))) data.set(unit, pageOfKind(fact.kind)?.dataIn?.(fact.markup) ?? null);
957
+ // Its hash is the file's where nobody else wrote, or where the file holds it as this commit wrote it:
958
+ // put back from its source, converted whole, or made, the answer giving the hash of what it made
959
+ // (none of one made around a unit someone else changed, or copied from one: their change is in it).
960
+ // A unit retagged, or whose table's rows and cells changed, holds the rest as the file had it: as for
961
+ // any other, its hash is the one the answer gave (`hashes`) or the one the page saw, and one made
962
+ // holding someone else's change has none the page saw (null). Someone else's change of it stays
963
+ // theirs: deleting, converting or writing it asks first. So do the hashes of the parts its component
964
+ // writes, but those this commit wrote or made, the file's now.
965
+ const own = alone || gone.has(unit) || (converted.has(unit) ? Boolean(converted.get(unit).replace) : fresh && Object.hasOwn(given, madeKeys.get(element) ?? ""));
966
+ const hash = own ? fact.hash : fresh ? null : map.units[unit].hash;
967
+ const parts = own || fresh ? fact.parts : { ...map.units[unit].parts, ...Object.fromEntries(Object.keys(wrote.get(unit) ?? {}).filter((name) => fact.parts?.[name]).map((name) => [name, fact.parts[name]])) };
968
+ Object.assign(map.units[unit], { kind: fact.kind, hash, parts, blockId: id, keep: fact.keep ?? undefined, grid: fact.grid || undefined });
969
+ // Put back (undo): its text blocks, as the file gave them when it went, are its own again.
970
+ for (const [block, source, baseline] of removed.get(unit)?.blocks ?? []) {
971
+ if (source !== undefined) sources.set(block, source);
972
+ if (baseline !== undefined) baselines.set(block, baseline);
973
+ }
974
+ removed.delete(unit);
975
+ for (const [other, owner] of removedWith) {
976
+ if (owner !== unit) continue;
977
+ removedWith.delete(other);
978
+ const stray = unitElement(other);
979
+ if (stray && !element.contains(stray)) strays.set(unit, [...(strays.get(unit) ?? []), stray]);
980
+ }
981
+ adopted.set(id, [unit, element]);
982
+ }
983
+ for (const [id, [unit, element]] of adopted) {
984
+ const fact = facts[id];
985
+ const parent = fact.parent ? adopted.get(fact.parent)?.[0] : undefined;
986
+ if (fact.parent === null) map.units[unit].parent = null;
987
+ else if (parent !== undefined) map.units[unit].parent = parent;
988
+ // Made by this commit: where the file has it is what the commit wrote, whoever else wrote.
989
+ const owner = fact.parent === null ? null : (parent ?? map.units.find((other) => other.blockId === fact.parent)?.id);
990
+ if (made.has(unit) && owner !== undefined) madeIn.set(unit, owner);
991
+ parents.set(unit, fact.parent);
992
+ // Its boxes, with the units in each: by id, or by hash for those the file gives none (units
993
+ // the commit did not change), matched in order with the page's units of that box.
994
+ for (const box of fact.boxes) {
995
+ const node = boxElement(element, box);
996
+ if (!node) continue;
997
+ const fresh = boxOf(node) === null; // joins the map now: it holds only what the commit put there
998
+ if (fresh) registerBox(node, box.kind, box.key);
999
+ if (alone || fresh) orders.set(boxOf(node), orderOf(node, box.units, adopted, strays.get(unit)));
1000
+ }
1001
+ // Its text blocks, with the file's texts. Of a unit it converted, one that stays (a table's cell, a
1002
+ // list's item retagged) holds what the file held, someone else's words too, which are no `before`
1003
+ // of the page's (its words would go over theirs): it keeps the one the page had, but where nobody else wrote.
1004
+ const matched = new Set();
1005
+ const inOrder = inFileOrder(element, fact.blocks);
1006
+ for (const [index, block] of fact.blocks.entries()) {
1007
+ const host = blockElement(element, block) ?? inOrder?.[index];
1008
+ if (!host) continue;
1009
+ const fresh = blockOf(host) === null;
1010
+ const own = registerBlock(host, pending, block, unit);
1011
+ matched.add(own);
1012
+ const taken = alone || fresh || written.has(own) || (made.has(unit) && !converted.has(unit));
1013
+ if (!issues.has(issueKey("text", own)) && taken) sources.set(own, block.inner);
1014
+ }
1015
+ // What the page made a text block of a block it made, and the file takes for none: not one, as
1016
+ // a load of the page shows it.
1017
+ if (made.has(unit)) for (const host of element.querySelectorAll("[data-ge-part]")) if (blockOf(host) === null) pageOfUnit(element)?.notText?.(host);
1018
+ // Its text blocks the file no longer has went with it: off the page, or the text of what it
1019
+ // was before this commit converted it.
1020
+ for (const block of map.blocks) {
1021
+ if (block.unit !== unit || block.gone || matched.has(block.id) || (hostOf(block.id) && !converted.has(unit))) continue;
1022
+ block.gone = true;
1023
+ sources.delete(block.id);
1024
+ baselines.delete(block.id);
1025
+ unbind("block", block.id);
1026
+ }
1027
+ }
1028
+ return { orders, madeIn, made, parents, arounds };
1029
+ }
1030
+
1031
+ // The page's units of box `node` in the order the file has them (`units`: the file's units of
1032
+ // the box, by id, or { hash } for those without, matched in order with the page's units of that
1033
+ // box that have none). `adopted`: id -> [page unit id, element], the units just taken in;
1034
+ // `elsewhere`: page units of that box standing elsewhere on the page (taken out of a unit put back).
1035
+ function orderOf(node, units, adopted = new Map(), elsewhere = []) {
1036
+ const plain = [...node.children, ...elsewhere].filter((child) => idOf(child) !== null && !map.units[idOf(child)]?.blockId);
1037
+ const order = [];
1038
+ for (const child of units) {
1039
+ if (typeof child === "string") {
1040
+ const known = adopted.get(child)?.[0] ?? map.units.find((other) => other.blockId === child)?.id;
1041
+ if (known !== undefined) order.push(known);
1042
+ } else {
1043
+ const at = plain.findIndex((other) => map.units[idOf(other)].hash === child.hash);
1044
+ if (at >= 0) order.push(idOf(plain.splice(0, at + 1).at(-1)));
1045
+ }
1046
+ }
1047
+ return order;
1048
+ }
1049
+
1050
+ /**
1051
+ * Take in what the file says of the keyed blocks (detail cards) a commit made (the answer's
1052
+ * `keyed`: by id, the units of its content). A keyed block is no unit: the file knows it by its id.
1053
+ * It becomes one of the file, as the page shows one it opened with: its content a box of the map
1054
+ * (the units the commit wrote in it already taken in by adopt; a block made in it later goes in as
1055
+ * a new block of that box). Nothing of it is made again. Its making undone, it stays in the file
1056
+ * with nothing opening it (as one whose entry the reader took out), and redone the page shows the
1057
+ * file's again. Adds the box's order to `orders`.
1058
+ */
1059
+ function adoptKeyed(facts, pending, orders) {
1060
+ for (const [key, card] of Object.entries(facts ?? {})) {
1061
+ // The block made on the page the file knows by that id (of a type that is `keyed`).
1062
+ const block = [...pending.made.values()].find((element) => element?.id === key && keyedBlock(element));
1063
+ if (!block || keyedIds.has(identityOf(block))) continue;
1064
+ const spec = BLOCK_TYPES[blockTypeOf(block)];
1065
+ const content = spec.content?.(block) ?? block;
1066
+ if (content && boxOf(content) === null) orders.set(registerBox(content, spec.box), orderOf(content, card.units));
1067
+ keyedIds.add(identityOf(block));
1068
+ keyedBlocks.set(key, block); // see keyedAway
1069
+ }
1070
+ }
1071
+
1072
+ // Whether what the file gave of a deleted unit is all of it: not `around`, its blocks gone elsewhere,
1073
+ // and not nothing, the file having had it no more.
1074
+ const deletedWhole = (source) => Boolean(source?.source && source.seal && !source.around);
1075
+ const elementDeleted = (id, pending) => unitElement(id) ?? pending.snapshot.units.find((node) => idOf(node) === id) ?? getLayout()?.elements.get(id) ?? null;
1076
+ // The units a deleted one took with it (`source`: what the file gave of it): itself, and those inside
1077
+ // it or in a box it takes along (a Sheet tab, its panel) that were not on the page when the commit
1078
+ // was built (the others went elsewhere: a merged section's blocks). One deleted by itself, by an
1079
+ // earlier commit or by this one, is none of them, nor is what it holds: it comes back from its own
1080
+ // source. Of one deleted whole, what this commit deleted in it is among them: that source holds it.
1081
+ const deletedWith = (id, source, pending) => {
1082
+ // Those the commit moved are elsewhere, on the page or waiting off it (`along`).
1083
+ const present = new Set([...pending.snapshot.units.map(idOf), ...[...pending.units.values()].filter((sent) => sent.change?.move).map((sent) => sent.id)].filter((unit) => unit !== null));
1084
+ const itself = new Set(deletedWhole(source) ? [] : [...pending.units.values()].filter((sent) => sent.change?.remove).map((sent) => sent.id));
1085
+ const element = elementDeleted(id, pending);
1086
+ const apart = (unit) => present.has(unit) || itself.has(unit) || removed.has(unit) || removedWith.has(unit);
1087
+ const along = new Set(map.boxes.filter((box) => box.unit === map.units[id]?.parent && takesBox(element, box)).flatMap((box) => getLayout()?.orders.get(box.id) ?? []));
1088
+ const within = (unit) => {
1089
+ for (const at of chain(unit)) {
1090
+ if (at === id) return true;
1091
+ if (apart(at)) return false;
1092
+ if (along.has(at)) return true;
1093
+ }
1094
+ return false;
1095
+ };
1096
+ return new Set([id, ...map.units.map((unit) => unit.id).filter(within)]);
1097
+ };
1098
+ // The units inside a deleted one that the page showed elsewhere when the commit was built but the
1099
+ // commit placed nowhere (no move takes them, nor a block made on the page holding them: they
1100
+ // stand in a container not written yet, a new row of columns one of whose columns is still
1101
+ // empty). The file deleted them with it all the same.
1102
+ const leftBehind = (id, pending) => {
1103
+ const placed = new Set();
1104
+ const visit = (node) => {
1105
+ if (node?.unit && typeof node.unit === "object") placed.add(node.unit.key);
1106
+ for (const child of node?.children ?? []) visit(child);
1107
+ };
1108
+ for (const sent of pending.units.values()) {
1109
+ if (sent.change?.move) placed.add(sent.id);
1110
+ if (sent.insert) visit(sent.node);
1111
+ }
1112
+ for (const unit of pending.restores.values()) placed.add(unit); // put back by the same commit, from its own source
1113
+ const present = new Set(pending.snapshot.units.map(idOf).filter((unit) => unit !== null));
1114
+ const away = (unit) => {
1115
+ for (const at of chain(unit.id)) {
1116
+ if (at === id) break;
1117
+ if (placed.has(at)) return false;
1118
+ }
1119
+ return true;
1120
+ };
1121
+ return map.units.filter((unit) => present.has(unit.id) && isInside(unit, id) && away(unit)).map((unit) => unit.id);
1122
+ };
1123
+ // A unit the file dropped while the page may still show it: its element (whichever stands for it)
1124
+ // is made on the page from now on, and so are its text blocks and boxes (sync-state.mjs asMade).
1125
+ const drop = (id) => {
1126
+ if (!map.units[id]) return;
1127
+ unbind("unit", id, true);
1128
+ for (const block of map.blocks) {
1129
+ if (block.unit !== id || block.gone) continue;
1130
+ block.gone = true;
1131
+ sources.delete(block.id);
1132
+ baselines.delete(block.id);
1133
+ unbind("block", block.id, true);
1134
+ }
1135
+ for (const box of map.boxes) if (box.unit === id) unbind("box", box.id, true);
1136
+ };
1137
+ // A text block the file dropped (emptied), likewise.
1138
+ const dropBlock = (id) => {
1139
+ if (!map.blocks[id]) return;
1140
+ map.blocks[id].gone = true;
1141
+ unbind("block", id, true);
1142
+ };
1143
+ // A unit deleted by a commit. Its text blocks leave the commits (they went with it); deleted
1144
+ // whole, it keeps the source the file gave, for undo: whatever stands for it on the page (undo put
1145
+ // it back while the commit was on its way, or later) is that unit put back, and the next commit
1146
+ // writes its source again. Else (`around`, its blocks went elsewhere; or the file had it no more)
1147
+ // it and what went with it are made on the page from now on: a container whose blocks went
1148
+ // elsewhere comes back holding them, around them from that source when they stand straight in it.
1149
+ const registerRemoved = (id, source, pending) => {
1150
+ // What it took along that the page still shows elsewhere is made on the page from now on (it is
1151
+ // written anew once it stands where the file can have it).
1152
+ for (const unit of leftBehind(id, pending)) drop(unit);
1153
+ const element = elementDeleted(id, pending);
1154
+ const gone = deletedWith(id, source, pending);
1155
+ const blocks = map.blocks.filter((block) => gone.has(block.unit) && !block.gone).map((block) => [block.id, sources.get(block.id), baselines.get(block.id)]);
1156
+ for (const [block] of blocks) {
1157
+ sources.delete(block);
1158
+ baselines.delete(block);
1159
+ }
1160
+ if (deletedWhole(source)) {
1161
+ // The types it and what went with it showed as, and their looks and tables' rows and cells as the
1162
+ // layout had them: what its source puts back.
1163
+ const types = new Map([...gone].map((unit) => [unit, getLayout()?.types?.get(unit)]));
1164
+ const looks = new Map([...gone].map((unit) => [unit, Object.fromEntries(LOOKS.map((part) => [part, getLayout()?.[part]?.get(unit)]))]));
1165
+ removed.set(id, { source: source.source, seal: source.seal, type: types.get(id), types, looks, blocks, element });
1166
+ for (const unit of gone) if (unit !== id) removedWith.set(unit, id);
1167
+ return;
1168
+ }
1169
+ for (const unit of gone) drop(unit);
1170
+ const was = dropped.get(map.units[id]?.cid);
1171
+ const type = element && containerTypeOf(element);
1172
+ if (was && source?.around && source.seal && holdsStraight(BLOCK_TYPES[type])) was.around = { source: source.source, seal: source.seal, type };
1173
+ };
1174
+ // The units a commit deleted (`deletions`: unit id -> what the file gave of it). One deleted in a unit
1175
+ // deleted whole is no deletion of its own, however the page took it out (a selection across blocks
1176
+ // empties a list item by item): it went with that unit, and comes back with it.
1177
+ const registerDeletions = (deletions, pending) => {
1178
+ const taken = new Set();
1179
+ for (const [id, source] of deletions) {
1180
+ if (!deletedWhole(source)) continue;
1181
+ for (const unit of deletedWith(id, source, pending)) if (unit !== id) taken.add(unit);
1182
+ }
1183
+ for (const [id, source] of deletions) if (!taken.has(id)) registerRemoved(id, source, pending);
1184
+ };
1185
+
1186
+ // The text the file has of a text block outside any unit (the answer's `outside`, by key: the
1187
+ // report title's "title", else the element's id; shared/ids.mjs outsideKey), or undefined.
1188
+ const outsideText = (id, outside) => {
1189
+ const block = map.blocks[id];
1190
+ if (!block || (block.unit !== null && block.unit !== undefined)) return undefined;
1191
+ const key = outsideKey(block.kind === "title", hostOf(id)?.id);
1192
+ return key ? outside?.[key] : undefined;
1193
+ };
1194
+
1195
+ /**
1196
+ * Take in the answer to a commit (build's `pending`, the endpoint's receipt; `alone`: nobody but
1197
+ * this page wrote the file since it showed it, see adopt). Returns { conflicts, merged: hosts now
1198
+ * showing a merge, stale: a merge the page could not show, outdated: it runs a manifest that is no
1199
+ * more, forced: the page units a change the reader said to make anyway went in for, all it held }.
1200
+ * A conflict of a change sent as the reader said to make it anyway says so (`again`).
1201
+ */
1202
+ function apply(pending, receipt, { alone = true } = {}) {
1203
+ touched(); // the baselines, the layout and the table change: collect() anew
1204
+ // Asked once: a unit the file could not name (gone meanwhile) is not asked for again and again.
1205
+ for (const id of pending.named ?? []) naming.delete(id);
1206
+ marks();
1207
+ const conflicts = [];
1208
+ const merged = [];
1209
+ let stale = false;
1210
+ let shownAgain = false; // what a component wrote went by a manifest that is no more
1211
+ const written = new Set(); // text blocks the commit wrote
1212
+ // The page's elements the commit was built from, by page unit id (on the page or not by now).
1213
+ const built = new Map(pending.snapshot.units.filter((node) => idOf(node) !== null).map((node) => [idOf(node), node]));
1214
+ const elementOf = (id) => built.get(id) ?? unitElement(id) ?? getLayout()?.elements.get(id) ?? null;
1215
+ // The ids the file gives: to units the page named, and to the blocks it made or put back (the
1216
+ // one the page proposed, or another when the file has that one already).
1217
+ for (const [key, id] of Object.entries(receipt.ids ?? {})) if (map.units[Number(key)]) map.units[Number(key)].blockId = id;
1218
+ for (const [key, id] of Object.entries(receipt.made ?? {})) {
1219
+ const element = pending.made.get(key);
1220
+ if (element && idOf(element) !== null) map.units[idOf(element)].blockId = id; // put back
1221
+ else if (element) proposals.set(identityOf(element), id);
1222
+ }
1223
+ for (const [key, hash] of Object.entries(receipt.hashes ?? {})) if (map.units[Number(key)]) map.units[Number(key)].hash = hash;
1224
+ // The keys of charts drawn from tables the file gave the blocks the commit made, other than the
1225
+ // page chose (source/chart-keys.mjs: one the file had meanwhile): theirs on the page too, so
1226
+ // that each card draws the table the file links it to ({ old: new | null }, null: off a table).
1227
+ if (receipt.chartKeys) {
1228
+ for (const element of pending.made.values()) {
1229
+ // A copy not written yet sends its keys again: the file's now.
1230
+ const sent = element?.getAttribute(COPY_KEYS);
1231
+ if (sent) {
1232
+ const keys = Object.fromEntries(Object.entries(JSON.parse(sent)).map(([from, to]) => [from, to !== null && Object.hasOwn(receipt.chartKeys, to) ? receipt.chartKeys[to] : to]));
1233
+ element.setAttribute(COPY_KEYS, JSON.stringify(keys));
1234
+ }
1235
+ for (const node of element ? [element, ...element.querySelectorAll(`[${CHART_DATA}], [${CHART_SOURCE}]`)] : []) {
1236
+ for (const name of [CHART_DATA, CHART_SOURCE]) {
1237
+ const key = node.getAttribute(name);
1238
+ if (key === null || !Object.hasOwn(receipt.chartKeys, key)) continue;
1239
+ const next = receipt.chartKeys[key];
1240
+ if (typeof next === "string") node.setAttribute(name, next);
1241
+ else if (next === null && name === CHART_SOURCE) node.removeAttribute(name);
1242
+ }
1243
+ }
1244
+ }
1245
+ }
1246
+
1247
+ for (const entry of receipt.texts ?? []) {
1248
+ const sent = pending.texts.get(entry.key);
1249
+ if (!sent) continue;
1250
+ if (entry.status === "conflict") {
1251
+ // What the reader wrote there: the block's text and the blocks split off it (they go with it).
1252
+ const conflict = { ...entry, kind: "text", block: sent.id, mine: sent.parts.map((part) => part.html).filter(Boolean).join("\n") || null };
1253
+ issues.set(entry.key, conflict);
1254
+ conflicts.push(conflict);
1255
+ continue;
1256
+ }
1257
+ const kept = sent.parts.find((part) => part.keep);
1258
+ if (!kept) {
1259
+ sources.delete(sent.id);
1260
+ baselines.delete(sent.id);
1261
+ // Emptied (the file dropped it, whatever blocks split off it went in): what stands for it
1262
+ // on the page is a new block from now on, the block itself or what it became meanwhile (an
1263
+ // empty last item turned into a paragraph after its list, say).
1264
+ const block = map.blocks[sent.id];
1265
+ if (block?.self) drop(block.unit);
1266
+ else dropBlock(sent.id);
1267
+ continue;
1268
+ }
1269
+ const host = hostOf(sent.id);
1270
+ // What the file has now (its own text, below, for a block of a unit; the answer's `outside`
1271
+ // for one outside any unit), until then what went in.
1272
+ sources.set(sent.id, outsideText(sent.id, receipt.outside) ?? (entry.status === "merged" ? entry.html : kept.html));
1273
+ if (entry.status === "merged" && !(host && serializeInner(host) === kept.html && !host.querySelector("[data-ge-atom]"))) {
1274
+ // Someone else's words went in too, and the block is not as it was sent (or holds a formula): the
1275
+ // next commit merges again, from what the page sent, which stays its `before` (a commit of the
1276
+ // block from the file's text, theirs in it, would write theirs out). So it is none the commit wrote.
1277
+ sources.set(sent.id, kept.html);
1278
+ baselines.set(sent.id, kept.html);
1279
+ stale = true;
1280
+ } else if (entry.status === "merged") {
1281
+ // Shown, the block as it was sent.
1282
+ const caret = caretKept(host);
1283
+ setOwnHTML(host, entry.html);
1284
+ caret();
1285
+ baselines.set(sent.id, serializeInner(host));
1286
+ merged.push(host);
1287
+ written.add(sent.id);
1288
+ } else {
1289
+ baselines.set(sent.id, kept.html);
1290
+ written.add(sent.id);
1291
+ }
1292
+ committedText(entry.key, sent);
1293
+ }
1294
+
1295
+ const skip = new Set(); // page units whose operation the file refused: they keep what the page knew
1296
+ const again = new Set(); // of those, the ones it goes again for (its place gone, tried once more)
1297
+ const converted = new Map(); // page units the commit turned into another type -> that change
1298
+ const landed = new Set(); // page units the change the reader said to make anyway went in for, all of it
1299
+ const movedFrom = new Map(); // page units the commit moved -> the file's id of the unit they were in
1300
+ const deletions = new Map(); // page units the commit deleted -> what the file gave of each
1301
+ const wrote = new Map(); // page units whose component's parts the commit wrote -> those parts, as sent
1302
+ const went = new Map(); // page units -> what of the layout the changes of them that went in are about (layoutOf)
1303
+ const fileParent = (id) => {
1304
+ const parent = map.units[id]?.parent;
1305
+ return parent === null || parent === undefined ? null : map.units[parent]?.blockId ?? "";
1306
+ };
1307
+ for (const entry of receipt.units ?? []) {
1308
+ const sent = pending.units.get(entry.key);
1309
+ if (!sent) continue;
1310
+ if (entry.status === "conflict") {
1311
+ // A block made on the page that is not on it any more (turned into another meanwhile, as an
1312
+ // empty paragraph "- " made a list of, sent while offline): nothing to hold; what stands for
1313
+ // it now goes with the next commit, and is held itself if it has no place either. Nor is the place
1314
+ // of a unit that waits, sent with the blocks around it (`along`): it waits as it did.
1315
+ if ((sent.insert && sent.element && !sent.element.isConnected) || sent.along) continue;
1316
+ // The page runs an old manifest of its package: nothing for the reader to decide. It is given
1317
+ // up and that alone: what else of the unit went in is taken in as any unit's, what waits goes its
1318
+ // way, and the page shown again runs the manifest there is.
1319
+ if (entry.reason === "definition") {
1320
+ unwritten.add(sent.id);
1321
+ shownAgain = true;
1322
+ continue;
1323
+ }
1324
+ // Its place is gone: the end of its box, once, before it is the reader's to decide.
1325
+ if (entry.reason === "position" && sent.insert && !elsewhere.has(identity(sent.element)) && endOf(sent.element)) {
1326
+ elsewhere.add(identityOf(sent.element));
1327
+ continue;
1328
+ }
1329
+ if (entry.reason === "position" && sent.change?.move && !movedElsewhere.has(sent.id) && endOf(unitElement(sent.id))) {
1330
+ movedElsewhere.add(sent.id);
1331
+ again.add(sent.id);
1332
+ skip.add(sent.id);
1333
+ continue;
1334
+ }
1335
+ // Deleted, with blocks in it that go elsewhere and had no place (their neighbour gone): with
1336
+ // them, once they go to the end of their box.
1337
+ if (entry.reason === "position" && sent.change?.remove && !removedElsewhere.has(sent.id)) {
1338
+ removedElsewhere.add(sent.id);
1339
+ again.add(sent.id);
1340
+ skip.add(sent.id);
1341
+ continue;
1342
+ }
1343
+ // Of what its component wrote, the parts someone else left alone went in all the same (`wrote`):
1344
+ // those are the file's, and the rest is the change held.
1345
+ let change = sent.change;
1346
+ if (change?.source && Array.isArray(entry.wrote)) {
1347
+ const [went, waits] = [true, false].map((goes) => Object.fromEntries(Object.entries(change.source).filter(([name]) => entry.wrote.includes(name) === goes)));
1348
+ wrote.set(sent.id, went);
1349
+ change = { ...change, source: waits };
1350
+ }
1351
+ // Held by the key it keeps until the reader decides: a block made on the page by the id it
1352
+ // proposes, with what it holds (the reader's words, to copy and for the crash buffer).
1353
+ const key = sent.insert ? issueKey("insert", (sent.element && proposals.get(identity(sent.element))) || sent.node?.blockId || sent.element?.id || serial++) : issueKey("unit", sent.id);
1354
+ // A write of other parts refused where one is held already: one conflict of the instance, holding both.
1355
+ const before = issues.get(key);
1356
+ if (change?.source && before?.change?.source) change = { ...change, source: { ...before.change.source, ...change.source } };
1357
+ const kept = change?.source && before?.change?.source ? { theirs: { ...before.theirs, ...entry.theirs }, parts: { ...before.parts, ...entry.parts } } : {};
1358
+ // Sent as the reader said to make it anyway, it was written again since they were shown it (`again`).
1359
+ const conflict = sent.insert ? { ...entry, key, kind: "insert", element: sent.element, mine: nodeHTML(sent.node) || null } : { ...entry, ...kept, key, kind: "unit", unit: sent.id, change, mine: writtenHTML(change), ...(sent.forced ? { again: true } : {}) };
1360
+ issues.set(key, conflict);
1361
+ if (sent.insert && sent.element) refused.set(identityOf(sent.element), key);
1362
+ else skip.add(sent.id);
1363
+ conflicts.push(conflict);
1364
+ continue;
1365
+ }
1366
+ // Moved: from the unit it was in (the file's id of it), for the revert check.
1367
+ if (sent.change?.move) movedFrom.set(sent.id, fileParent(sent.id));
1368
+ if (sent.change?.replace || sent.change?.retag || sent.change?.table) converted.set(sent.id, sent.change);
1369
+ if (sent.change?.source) wrote.set(sent.id, sent.change.source);
1370
+ if (!sent.insert) went.set(sent.id, new Set([...(went.get(sent.id) ?? []), ...layoutOf(sent.change)]));
1371
+ // The reader's word is for the change it was given for, gone in once all it held has: what they do to
1372
+ // the unit next is held to what the page saw, as any change is.
1373
+ const word = sent.forced ? forced.get(sent.id) : null;
1374
+ if (word?.parts) word.parts = word.parts.filter((name) => !Object.hasOwn(sent.change.source, name));
1375
+ if (word && !word.parts?.length) {
1376
+ forced.delete(sent.id);
1377
+ landed.add(sent.id);
1378
+ }
1379
+ // A table's rows made on the page are the file's now (its cells are adopted as text blocks).
1380
+ if (sent.change?.table) elementOf(sent.id)?.querySelectorAll("tr[data-ge-new]").forEach((row) => row.removeAttribute("data-ge-new"));
1381
+ else if (sent.change?.remove) deletions.set(sent.id, receipt.removed?.[entry.key]);
1382
+ }
1383
+ registerDeletions(deletions, pending);
1384
+ for (const unit of heldUnits(issues.keys())) skip.add(unit);
1385
+ // Units the file no longer has though this commit did not delete them (a list goes with its last
1386
+ // item): taken as deleted (no source to put back), so what stands for them on the page, if
1387
+ // anything, is made anew.
1388
+ const deleted = new Set([...pending.units.values()].filter((sent) => sent.change?.remove).map((sent) => sent.id));
1389
+ for (const id of receipt.gone ?? []) {
1390
+ const unit = map.units.find((entry) => entry.blockId === id);
1391
+ if (!unit || deleted.has(unit.id) || skip.has(unit.id)) continue;
1392
+ registerRemoved(unit.id, null, pending);
1393
+ }
1394
+
1395
+ // Units know the unit they are in (the chain a later commit touches), as committed: those the
1396
+ // file refused to move, or held unmoved, are in the one the file has them in still (adopt gives
1397
+ // the others the file's own).
1398
+ for (const [box, children] of pending.snapshot.boxes) {
1399
+ const owner = unitAround(box);
1400
+ for (const child of children) {
1401
+ const id = idOf(child);
1402
+ if (id !== null && map.units[id] && (!skip.has(id) || went.get(id)?.has("place"))) map.units[id].parent = owner;
1403
+ }
1404
+ }
1405
+ // What the file says of the units the commit named or made: those elements by the id they carry
1406
+ // (the commit's own, then any other the page has).
1407
+ const elements = new Map();
1408
+ const note = (node, id = fileIdOf(node)) => {
1409
+ if (node && id && !elements.has(id)) elements.set(id, node);
1410
+ };
1411
+ pending.snapshot.units.forEach((node) => note(node));
1412
+ pending.made.forEach((node) => note(node));
1413
+ for (const unit of map.units) if (unit.blockId) note(elementOf(unit.id), unit.blockId);
1414
+ // Units put back show as what was put back: the source they had, of the type they had then
1415
+ // (an element standing for one turned into another type since converts it next).
1416
+ const restoredTypes = new Map([...pending.restores.values()].filter((id) => removed.has(id)).flatMap((id) => [...(removed.get(id).types ?? new Map([[id, removed.get(id).type]]))]));
1417
+ // Their looks and tables' rows and cells too, as the layout had them when they went: what changed on
1418
+ // the page since (put back, then resized or a column taken out) goes with the next commit.
1419
+ const restoredLooks = [...pending.restores.values()].filter((id) => removed.has(id)).flatMap((id) => [...(removed.get(id).looks ?? [])]);
1420
+ // What the file holds of what components write: what the last commit left, with the parts this one
1421
+ // wrote, and of the units it wrote whole what the file says (adopt). A write it did not carry still
1422
+ // differs from that, and goes with the next.
1423
+ const previous = getLayout();
1424
+ const data = new Map([...(previous?.data ?? [])].map(([id, held]) => [id, wrote.has(id) ? { ...held, ...wrote.get(id) } : held]));
1425
+ const { orders, madeIn, made, parents, arounds } = adopt(receipt.file ?? {}, elements, skip, converted, pending, written, alone, data, wrote, receipt.hashes ?? {});
1426
+ for (const [unit, parent] of parents) {
1427
+ if (made.has(unit)) placed.set(unit, { parent, ...(arounds.has(unit) ? { around: arounds.get(unit) } : {}) });
1428
+ else if (movedFrom.has(unit)) placed.set(unit, { parent, from: placed.get(unit)?.from ?? movedFrom.get(unit) });
1429
+ else if (placed.has(unit)) placed.set(unit, { ...placed.get(unit), parent }); // taken along by a block made or put back
1430
+ }
1431
+ // What its component wrote that went in, for the revert check: each part by the hash the commit said the
1432
+ // file held of it, and the one the file gives it now.
1433
+ for (const sent of pending.units.values()) {
1434
+ if (sent.change?.source) for (const name of Object.keys(wrote.get(sent.id) ?? {})) noteWrite(sent.id, name, sent.parts?.[name], map.units[sent.id]?.parts?.[name]);
1435
+ }
1436
+ adoptKeyed(receipt.keyed, pending, orders);
1437
+ // What is held back or goes again stays to be done: those units keep, in the next layout, all they had
1438
+ // in the last, so that what waits still shows against it, and so does whatever else of them the reader
1439
+ // did since, which no commit sends while they are held (a move, a look, a column taken out): a move the
1440
+ // file did not make is no move made. What of them went in with this commit is the file's, taken from
1441
+ // what it left (`went`: a table's operation that went in beside a move refused for its place, kept as it
1442
+ // was, would be sent again, against a table it no longer names).
1443
+ // A unit deleted and not put back (its putting back refused, say) is none the file has.
1444
+ const present = (node) => {
1445
+ const id = idOf(node);
1446
+ return id === null || removed.has(id) ? null : id;
1447
+ };
1448
+ const next = captureLayout(root, map, pending.snapshot, { unitOf: present, boxOf, typeOf: ownTypeOf, data });
1449
+ for (const [id, type] of restoredTypes) if (type !== undefined && next.elements.has(id) && !removed.has(id)) next.types.set(id, type);
1450
+ for (const [id, look] of restoredLooks) {
1451
+ if (!next.elements.has(id) || removed.has(id)) continue;
1452
+ for (const part of LOOKS) if (look[part] !== undefined) next[part].set(id, look[part]);
1453
+ }
1454
+ // A unit is in the one box the file has it in.
1455
+ for (const [box, order] of orders) {
1456
+ next.orders.set(box, order);
1457
+ for (const [other, list] of next.orders) if (other !== box && list.some((id) => order.includes(id))) next.orders.set(other, list.filter((id) => !order.includes(id)));
1458
+ }
1459
+ // A block this commit made is in the unit the file has it in: where the page shows it in another
1460
+ // (the commit wrote it next to a block that had not moved where the page moved it), the next
1461
+ // commit moves it there.
1462
+ for (const [unit, owner] of madeIn) {
1463
+ for (const [box, list] of next.orders) if (list.includes(unit) && (map.boxes[box]?.unit ?? null) !== owner) next.orders.set(box, list.filter((id) => id !== unit));
1464
+ }
1465
+ // A unit put back that the page no longer shows (the blocks it held taken out of it): known to
1466
+ // the next commit, which takes it away again.
1467
+ for (const [key, element] of pending.made) {
1468
+ const id = pending.restores.has(key) ? idOf(element) : null;
1469
+ if (id !== null && !element.isConnected) {
1470
+ next.elements.set(id, element);
1471
+ next.types.set(id, ownTypeOf(element));
1472
+ }
1473
+ }
1474
+ // One the file deleted, and the page has not put back, waits for nothing (`present`): what was given up of
1475
+ // it stays given up should it come back.
1476
+ const waiting = new Set([...heldUnits(discarded.keys()), ...[...issues.values()].filter((entry) => entry.kind === "unit").map((entry) => entry.unit), ...again].filter((id) => !removed.has(id)));
1477
+ for (const id of previous ? waiting : []) {
1478
+ const done = went.get(id) ?? new Set();
1479
+ for (const part of ["elements", "types", ...LOOKS]) if (!done.has(part) && previous[part]?.has(id)) next[part].set(id, previous[part].get(id));
1480
+ // What its component writes is none of them: it is what the commits say the file holds (`data`), which
1481
+ // holds what went in and not what waits, kept for it off the page too.
1482
+ if (!next.data.has(id) && data.has(id)) next.data.set(id, data.get(id));
1483
+ // Where the page shows it in its place among the units around it (no move of it: `moves`), the file has it
1484
+ // there too, whatever this commit moved around it.
1485
+ if (done.has("place") || (pending.moves && !pending.moves.has(id) && [...next.orders.values()].some((order) => order.includes(id)))) continue;
1486
+ // Its own place only, where the page shows it moved and no commit sends that, or not at all: out of the
1487
+ // boxes it was not in, and back after the unit that stood before it that this commit left where it was
1488
+ // (one it moved stands elsewhere in the file). The others' places are as this commit left them (taking
1489
+ // back a whole box undid their moves too, and sent them again, without end).
1490
+ for (const [box, order] of next.orders) if (order.includes(id) && !previous.orders.get(box)?.includes(id)) next.orders.set(box, order.filter((other) => other !== id));
1491
+ for (const [box, before] of previous.orders) {
1492
+ if (!before.includes(id)) continue;
1493
+ const order = (next.orders.get(box) ?? []).filter((other) => other !== id);
1494
+ const after = before.slice(0, before.indexOf(id)).reverse().find((other) => order.includes(other) && !went.get(other)?.has("place"));
1495
+ order.splice(after === undefined ? 0 : order.indexOf(after) + 1, 0, id);
1496
+ next.orders.set(box, order);
1497
+ }
1498
+ }
1499
+ setLayout(next);
1500
+ marks();
1501
+ return { conflicts, merged, stale, outdated: shownAgain, forced: [...landed] };
1502
+ }
1503
+
1504
+ // A text committed: what it replaced (the first time this page committed it), what it is now, and what
1505
+ // the commits of it between left it as (`earlier`, the newest TEXTS_KEPT): someone writing back what
1506
+ // they read then puts back what the page committed after. Back as it was first, it is asked of what it
1507
+ // was between, as a part of what a component writes is (noteWrite): an undo is a commit like any other.
1508
+ function committedText(key, sent) {
1509
+ const mine = sources.get(sent.id);
1510
+ const was = journal.get(key);
1511
+ const original = was?.original ?? sent.before;
1512
+ if (mine === undefined) return void journal.delete(key);
1513
+ const earlier = [...new Set([...(was?.earlier ?? []), ...(was ? [was.mine] : [])])].filter((text) => text !== mine && text !== original).slice(-TEXTS_KEPT);
1514
+ if (mine === original && earlier.length === 0) return void journal.delete(key);
1515
+ journal.set(key, { key, block: blockRef(sent.id), original, mine, ...(earlier.length > 0 ? { earlier } : {}) });
1516
+ }
1517
+
1518
+ // A part of what a unit's component writes, written by a commit (`before`, `after`: the hashes the file
1519
+ // had of it then and has now), for the revert check: every hash it had in the file as the page wrote it
1520
+ // (before each of its writes, and as each left it), the newest WRITES_KEPT, but the one it has now
1521
+ // (undone, or written so), and that one. A commit that leaves it as the file held it (`before` is
1522
+ // `after`: written over what someone else wrote, as the reader said to) settles it there all the same.
1523
+ function noteWrite(unit, name, before, after) {
1524
+ if (typeof before !== "string" || typeof after !== "string") return;
1525
+ const entry = writes.get(unit) ?? { parts: {} };
1526
+ const was = entry.parts[name];
1527
+ const passed = [...(was?.before ?? []), ...(was ? [was.after] : []), before].filter((hash) => hash !== after);
1528
+ const counting = passed.filter((hash, index) => passed.lastIndexOf(hash) === index).slice(-WRITES_KEPT);
1529
+ if (counting.length > 0) entry.parts[name] = { before: counting, after };
1530
+ else delete entry.parts[name];
1531
+ entry.time = Date.now();
1532
+ if (Object.keys(entry.parts).length > 0) writes.set(unit, entry);
1533
+ else writes.delete(unit);
1534
+ }
1535
+
1536
+ // Parts of what a unit's component wrote that are the file's now (the reader kept the file's, or took
1537
+ // it in place of theirs): no longer asked of.
1538
+ function unwrite(unit, names) {
1539
+ const entry = writes.get(unit);
1540
+ for (const name of names) delete entry?.parts[name];
1541
+ if (entry && Object.keys(entry.parts).length === 0) writes.delete(unit);
1542
+ }
1543
+
1544
+ /**
1545
+ * The reader decided on a held conflict (key: an issueKey): "mine" commits
1546
+ * the reader's text over the file's (the file's becomes its `before`), "force" deletes, converts or
1547
+ * adjusts the unit anyway, by the next commit that carries a change of that kind, over the unit as the
1548
+ * card showed it, "discard" gives the change
1549
+ * up, that alone (never sent again: the page shows the file's once it reloads; a block made on the page is
1550
+ * taken off it; what a component wrote, that write; of a unit the file has no one of, all of it), "forget" drops what the page knew of
1551
+ * a block the file no longer has (the reader deleted it too, or made it a new block), "retry" sends
1552
+ * a block made on the page again where it stands now (the reader put it elsewhere), and whatever
1553
+ * else was held goes with the next commit, should the page ask for it again (undone, then redone).
1554
+ */
1555
+ function resolve(key, action) {
1556
+ touched(); // what is held back, the baselines: collect() and the next commit anew
1557
+ const entry = issues.get(key);
1558
+ issues.delete(key);
1559
+ if (action === "retry" && entry?.element) {
1560
+ refused.delete(identity(entry.element));
1561
+ elsewhere.delete(identity(entry.element));
1562
+ return;
1563
+ }
1564
+ const { kind, ref: id } = parseIssueKey(key);
1565
+ if (kind === "unit" && action !== "force") forced.delete(id); // an earlier word of the reader's, refused again since, is not this one
1566
+ // The end of its box was tried before its place's card: a move the reader makes from now on goes where the page
1567
+ // has it.
1568
+ if (kind === "unit" && entry?.reason === "position") movedElsewhere.delete(id);
1569
+ if (action === "mine" && kind === "text" && typeof entry?.theirs === "string") sources.set(id, entry.theirs);
1570
+ else if (action === "force" && kind === "unit") {
1571
+ // For the change the card was about, over the unit as it showed it: what someone wrote since asks
1572
+ // again. What its component writes goes over each part as the reader was shown it (the refusal's
1573
+ // hashes of them), the word lasting until every part it held has gone in.
1574
+ const source = entry?.change?.source;
1575
+ forced.set(id, { kind: kindOf(entry?.change), ...(typeof entry?.hash === "string" ? { hash: entry.hash } : {}), ...(source ? { parts: Object.keys(source) } : {}) });
1576
+ if (entry?.parts && map.units[id]) map.units[id].parts = { ...map.units[id].parts, ...entry.parts };
1577
+ } else if (action === "discard") {
1578
+ discarded.set(key, kind === "unit" ? { kind: kindOf(entry?.change), ...(entry?.change ? { change: entry.change } : {}), reason: entry?.reason, words: holdsWords(entry) } : {});
1579
+ // The parts its component wrote are the file's, as the reader chose: what the page wrote of them before
1580
+ // is asked of no more.
1581
+ if (kind === "unit" && entry?.change?.source) unwrite(id, Object.keys(entry.change.source));
1582
+ } else if (action === "forget" && kind === "text") {
1583
+ sources.delete(id);
1584
+ baselines.delete(id);
1585
+ journal.delete(key);
1586
+ }
1587
+ marks(); // what was given up read-only from now on
1588
+ }
1589
+
1590
+ /**
1591
+ * The units this load made or moved that the page still shows, as the revert check names them
1592
+ * (revertedUnits): [{ key, id, parent, from? }], key the page's unit.
1593
+ */
1594
+ const placedUnits = () => [...placed].filter(([unit]) => map.units[unit]?.blockId && !removed.has(unit) && !issues.has(issueKey("unit", unit)) && unitElement(unit))
1595
+ .map(([unit, { parent, from }]) => ({ key: unit, id: map.units[unit].blockId, parent, ...(from !== undefined ? { from } : {}) }));
1596
+
1597
+ /**
1598
+ * Someone else's write put back a unit this load made (`gone`) or moved (`moved`), and the reader
1599
+ * keeps theirs (`mine`): one it made is a block made on the page again, written anew (around its
1600
+ * blocks from the source it was put back with, if it was); one it moved goes where the page has it
1601
+ * again. Otherwise it is no longer checked.
1602
+ */
1603
+ function putBack(unit, { gone, mine }) {
1604
+ const { around } = placed.get(unit) ?? {};
1605
+ placed.delete(unit);
1606
+ if (!mine) return;
1607
+ touched(); // the layout changes in place
1608
+ if (gone) {
1609
+ const key = map.units[unit].cid;
1610
+ drop(unit);
1611
+ if (around && dropped.has(key)) dropped.get(key).around = around;
1612
+ } else for (const [box, order] of getLayout()?.orders ?? []) getLayout().orders.set(box, order.filter((id) => id !== unit));
1613
+ marks();
1614
+ }
1615
+
1616
+ /** The text of the blocks in a unit the page shows (the reader's words), or "". */
1617
+ const unitText = (unit) => {
1618
+ const element = unitElement(unit);
1619
+ return element ? blocksText(element) : "";
1620
+ };
1621
+
1622
+ /**
1623
+ * What the components of the units the page shows wrote that went in, as the revert check names them
1624
+ * (revertedParts): [{ key, id, parts, time }], key the page's unit, id the file's it has now; none of a
1625
+ * unit deleted, held for the reader or off the page, while it is.
1626
+ */
1627
+ const written = () => [...writes].filter(([unit]) => map.units[unit]?.blockId && !removed.has(unit) && !issues.has(issueKey("unit", unit)) && unitElement(unit))
1628
+ .map(([unit, { parts, time }]) => ({ key: unit, id: map.units[unit].blockId, parts, time }));
1629
+
1630
+ /**
1631
+ * What components wrote in an earlier load of the page (written() as it kept it: [{ id, parts, time }]),
1632
+ * asked of as this load's own: each by the unit of the page that has its id. Entries of no such shape
1633
+ * are none.
1634
+ */
1635
+ function carry(entries) {
1636
+ for (const entry of Array.isArray(entries) ? entries : []) {
1637
+ if (typeof entry?.id !== "string" || !entry.parts || typeof entry.parts !== "object") continue;
1638
+ const unit = map.units.findIndex((other) => other.blockId === entry.id);
1639
+ if (unit < 0 || writes.has(unit) || !unitElement(unit)) continue;
1640
+ const parts = Object.fromEntries(Object.entries(entry.parts).filter(([, part]) => Array.isArray(part?.before) && part.before.every((hash) => typeof hash === "string") && typeof part.after === "string"));
1641
+ if (Object.keys(parts).length > 0) writes.set(unit, { parts, time: typeof entry.time === "number" ? entry.time : Date.now() });
1642
+ }
1643
+ }
1644
+
1645
+ /** What a unit's component holds on the page of the parts named, by name, or null. */
1646
+ const dataOfUnit = (unit, names) => {
1647
+ const element = unitElement(unit);
1648
+ const held = element ? pageOfUnit(element)?.dataOf?.(element) : null;
1649
+ return held ? Object.fromEntries(names.filter((name) => typeof held[name] === "string").map((name) => [name, held[name]])) : null;
1650
+ };
1651
+
1652
+ /**
1653
+ * Of `parts` (by name, a hash of each), those a unit's component holds on the page otherwise than the
1654
+ * file's markup of the unit (`markup`) holds them, read as the component reads one (`dataIn`): a part the
1655
+ * file spells otherwise than the page writes it, holding what the page shows, put back nothing the reader
1656
+ * would lose. All of them where that cannot be told.
1657
+ */
1658
+ const heldOtherwise = (unit, parts, markup) => {
1659
+ const names = Object.keys(parts);
1660
+ const held = dataOfUnit(unit, names);
1661
+ const file = held && typeof markup === "string" ? pageOfUnit(unitElement(unit))?.dataIn?.(markup) : null;
1662
+ return Object.fromEntries(names.filter((name) => typeof held?.[name] !== "string" || typeof file?.[name] !== "string" || held[name] !== file[name]).map((name) => [name, parts[name]]));
1663
+ };
1664
+
1665
+ /**
1666
+ * Someone else's write put back parts of what a unit's component wrote (`parts`: by name, the hashes the
1667
+ * file holds of them), and the reader keeps theirs (`mine`): the next commit writes those parts as the
1668
+ * page holds them over what the file holds, asking should it be written again since. Otherwise they are
1669
+ * the file's, and no longer asked of.
1670
+ */
1671
+ function rewrite(unit, parts, mine) {
1672
+ if (!mine) return void unwrite(unit, Object.keys(parts));
1673
+ if (!map.units[unit]) return;
1674
+ touched(); // the layout changes in place
1675
+ map.units[unit].parts = { ...map.units[unit].parts, ...parts };
1676
+ const data = getLayout()?.data;
1677
+ if (data?.has(unit)) data.set(unit, { ...data.get(unit), ...Object.fromEntries(Object.keys(parts).map((name) => [name, null])) });
1678
+ }
1679
+
1680
+ /** The texts committed, as the revert check names them (revertedTexts): [{ key, block, original, mine, earlier? }]. */
1681
+ // Each named as the page knows it now (a later commit may have given its unit an id, or moved it).
1682
+ const committed = () => [...journal.values()].filter((entry) => !issues.has(entry.key) && sources.get(parseIssueKey(entry.key).ref) === entry.mine && map.blocks[parseIssueKey(entry.key).ref])
1683
+ .map((entry) => ({ ...entry, block: { ...blockRef(parseIssueKey(entry.key).ref), before: entry.mine } }));
1684
+
1685
+ /** A committed text the reader let someone else put back: no longer checked. */
1686
+ const unjournal = (key) => journal.delete(key);
1687
+
1688
+ /**
1689
+ * What the page knows, as values and the elements they name: all a page opening once this one is
1690
+ * gone needs to take up exactly where it was (createCommits' `restore`), `pending` being the commit
1691
+ * whose answer is not known. As JSON: keep().
1692
+ */
1693
+ const save = (pending) => ({
1694
+ map: { version: map.version, units: map.units, blocks: map.blocks, boxes: map.boxes },
1695
+ openingUnits, openingBlocks, openingHashes,
1696
+ baselines, sources, issues, refused, removed, removedWith, discarded, unwritten, forced, elsewhere, movedElsewhere, removedElsewhere, journal, writes, placed, keyedBlocks, stood, serial,
1697
+ table, dropped, keyedIds, proposals,
1698
+ layout: getLayout(),
1699
+ pending,
1700
+ });
1701
+
1702
+ // The page as it opened: every element the map knows, with its identity; the layout with the
1703
+ // type each unit shows as.
1704
+ marks();
1705
+ const start = getLayout();
1706
+ if (start?.types && start.types.size === 0) for (const [id, node] of start.elements) start.types.set(id, ownTypeOf(node));
1707
+ if (!restore) {
1708
+ for (const [box, order] of start?.orders ?? []) {
1709
+ for (const id of order) if (!accepts(map.boxes[box].kind, map.units[id].kind)) stood.set(id, map.boxes[box].kind);
1710
+ }
1711
+ }
1712
+ observer?.observe(root, { subtree: true, childList: true, characterData: true, attributes: true });
1713
+
1714
+ /**
1715
+ * The id `element` (a unit of the page, or a block made on it) has in the file, or is to have: the
1716
+ * file's, else the one the page proposes for it, which the next commit gives it (a unit of the file
1717
+ * without one is named by it, alone when nothing else waits); null for what is no unit.
1718
+ */
1719
+ function idFor(element) {
1720
+ const unit = idOf(element);
1721
+ if (unit !== null) {
1722
+ if (map.units[unit].blockId) return map.units[unit].blockId;
1723
+ if (!naming.has(unit)) touched(); // the next commit names it: there is something to commit
1724
+ naming.add(unit);
1725
+ return (map.units[unit].proposed ??= randomBlockId());
1726
+ }
1727
+ return element?.hasAttribute?.("data-ge-new") ? proposedId(element) ?? null : null;
1728
+ }
1729
+
1730
+ return {
1731
+ build, apply, collect, current, marks, counts, buffer, texts, resolve, committed, unjournal, placedUnits, putBack, unitText, written, carry, dataOfUnit, heldOtherwise, rewrite, issues, removed, pageIds, idFor,
1732
+ /** The id a unit of the page has in the file, or the one it proposes for it; null for none. */
1733
+ fileId: (unit) => map.units[unit]?.blockId ?? map.units[unit]?.proposed ?? null,
1734
+ /** save(pending) as JSON, with the page's body (sync-state.mjs pack). */
1735
+ keep: (pending) => pack(root, save(pending)),
1736
+ /** Identities more than one element on the page carries (none, ever). */
1737
+ doubled: () => [...doubled],
1738
+ };
1739
+ }
1740
+
1741
+ // What the file did not take is held under a key (the commits' `issues`): of a text block ("b"), of a
1742
+ // unit's operation ("u") or of a block made or put back on the page, by the id it proposes ("i");
1743
+ // the conflict's `kind` is "text", "unit" or "insert". The key is built and read only here (and
1744
+ // kept as it is by the crash buffer and the conflict cards).
1745
+ const ISSUE_PREFIX = { text: "b", unit: "u", insert: "i" };
1746
+ export const issueKey = (kind, ref) => `${ISSUE_PREFIX[kind]}${ref}`;
1747
+ /** { kind, ref } of a key: ref the block's or the unit's number, or the id (a string) of an insert. */
1748
+ export function parseIssueKey(key) {
1749
+ const kind = Object.keys(ISSUE_PREFIX).find((name) => ISSUE_PREFIX[name] === key[0]);
1750
+ return { kind, ref: kind === "insert" ? key.slice(1) : Number(key.slice(1)) };
1751
+ }
1752
+ // The units whose operation is held, of these keys.
1753
+ const heldUnits = (keys) => [...keys].map(parseIssueKey).filter(({ kind }) => kind === "unit").map(({ ref }) => ref);
1754
+
1755
+ // The kind of a unit's change (collectEdits'): the name of what it does, its one field besides the unit and
1756
+ // what that field takes (`on`, `value`, `definition`): "remove", "replace", "source" and the like; null for none.
1757
+ export const kindOf = (change) => Object.keys(change ?? {}).find((key) => !["id", "on", "value", "definition"].includes(key)) ?? null;
1758
+
1759
+ // Whether a unit's conflict (or what was given up of one) is of a unit the file has no one of: gone, or
1760
+ // there twice.
1761
+ const lacks = (entry) => entry?.reason === "missing" || entry?.reason === "ambiguous";
1762
+
1763
+ // Whether the texts in a unit wait with a change of it held back (`entry`, its conflict): where the change
1764
+ // carries them (a conversion the words of its block, a table's operation those of its cells, a deletion
1765
+ // those of the blocks it took off the page, `off`, which go or are given up with it), or where the file has
1766
+ // no one unit to write them to (they would be refused the same). A move, a look, a deletion of blocks on the
1767
+ // page again (undone since) or what a component writes holds none of them.
1768
+ const holdsWords = (entry, off = false) => !entry?.change || Boolean(entry.change.replace || entry.change.table || (entry.change.remove && off)) || lacks(entry);
1769
+
1770
+ // What a layout holds of a unit's look and of its table's rows and cells (core.mjs captureLayout): what a
1771
+ // unit held back keeps, and what one deleted whole takes along and is put back with.
1772
+ const LOOKS = ["toggles", "sizes", "attributes", "tables"];
1773
+
1774
+ // What of a layout a unit's change is about: a conversion its element and type, and all its looks, which
1775
+ // the element it writes carries (a list given the other tag those two alone: its switches go as changes of
1776
+ // their own), a move its place ("place"), a look or a table's operation that part (LOOKS, each named for the
1777
+ // field of the change it holds: "sizes" a `size`'s); a deletion or what its component writes none of them.
1778
+ const layoutOf = (change) => (change?.replace ? ["elements", "types", ...LOOKS] : change?.retag ? ["elements", "types"] : change?.move ? ["place"] : LOOKS.filter((part) => change?.[part.slice(0, -1)] !== undefined));
1779
+
1780
+ // What an operation of a commit carries in the reader's words, where the file did not take it: the
1781
+ // text of a unit turned into another type, what a component wrote; null for any other.
1782
+ export const writtenHTML = (op) => op?.replace?.html ?? (op?.source ? Object.values(op.source).join("\n") : null);
1783
+
1784
+ // What a block made on the page holds, as a commit carries it: its text, its title and those of the
1785
+ // blocks in it, one a line (the reader's words, when the file did not take it).
1786
+ export const nodeHTML = (node) => [node?.title, node?.html, node?.restore?.source, ...(node?.children ?? []).map(nodeHTML)].filter(Boolean).join("\n");
1787
+
1788
+ // Where a text block of the page sits in its unit, as the server's scan writes it (pathIn): the
1789
+ // element steps from the unit down, each [tag, index among the parent's children of that tag, how
1790
+ // many there are]. Only to find the element a path of the file names (blockElement), never to say where a block is.
1791
+ function pathFrom(unit, host) {
1792
+ const steps = [];
1793
+ for (let node = host; node && node !== unit; node = node.parentElement) {
1794
+ const same = [...(node.parentElement?.children ?? [])].filter((child) => child.localName === node.localName);
1795
+ steps.unshift([node.localName, same.indexOf(node), same.length]);
1796
+ }
1797
+ return ["unit", ...steps];
1798
+ }