@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,1725 @@
1
+ // First: what the browser keeps under the names before 2026-10-28 carried to the new ones (the
2
+ // editor's gallery-edit:… keys, src/shared/renamed-storage.mjs), before any module below reads them.
3
+ import "../../shared/renamed-storage.mjs";
4
+ import { OUTER, can, captureLayout, editableAhead, firstHost, isHost, limitText, markFrames, markKinds, matchesSource, newHost, newMention, ownHTML, plainText, prepareBlock, splitBlock, textEnd, tidyEmptied, unitElement, withLeads } from "./core.mjs";
5
+ import { createBlocks } from "./blocks.mjs";
6
+ import { createFlags } from "./flags.mjs";
7
+ import { createFrame } from "./frame.mjs";
8
+ import { createHistory } from "./history.mjs";
9
+ import { editable, hostOf, select, selectAt, caretAt, caretAtOffset, atEdge, endArrowRun, arrow, besideLine, chipsPress, followPress, outOfAtom, showCaret, followEnd, selectionContext, textNear } from "./caret.mjs";
10
+ import { closePanel, messageKind, noticeNode, panelItem, panelNode, relayTo, say, settle, showPanel } from "./notices.mjs";
11
+ import { failure, postAnswer } from "./post.mjs";
12
+ import { exportReport } from "./export-report.mjs";
13
+ import { createCommits, kindOf } from "./commit.mjs";
14
+ import { createConflicts, packageChanged, unfit } from "./conflicts.mjs";
15
+ import { checkLeft, createAutosave, keepWith, lostKey, pendingKey, readLost, recover, reloadView, setReloader, takeView, viewNow } from "./autosave.mjs";
16
+ import { SOLO, connectWorkbench } from "./session.mjs";
17
+ import { belowTargets, convertBlock, convertGroup, groupParents, groupTargets, noTextToCarry, targetsOf, touchedBy, turnInto, turnParents, turnUnit, typeOf } from "./convert.mjs";
18
+ import { joinCode, rangeAt, rangeOffsets } from "./marks.mjs";
19
+ import { addStyle, button, copyAddress, element, layer } from "./ui.mjs";
20
+ import { createClipboard } from "./clipboard/index.mjs";
21
+ import { createFormatting } from "./format-bar/index.mjs";
22
+ import { connectHints, hideHint, readOnlyReason, scheduleHint, trackPointer } from "./hints/index.mjs";
23
+ import { createInsert } from "./insert/index.mjs";
24
+ import { connectLinks, dismissLink, drawPasted, followLink, leadsToPage, linkBoxNode, linkHolds, linkNodes, linkOf, linkPending, openLink, pageLinkOf } from "./links/index.mjs";
25
+ import { bookmarkItems, connectViews, linkTitle, offerViews } from "./links/views.mjs";
26
+ import { connectChipFills } from "./chip-fills.mjs";
27
+ import { connectFormula, dismissFormula, formulaBoxNode, loadKatex, openFormula } from "./formula/index.mjs";
28
+ import { settlePads } from "./pads.mjs";
29
+ import { beforeDelete, codeArrow, codeClick, codePress, settleCodeEdge } from "./typing/code-edges.mjs";
30
+ import { createMarkdown } from "./markdown/index.mjs";
31
+ import { createResize } from "./resize/index.mjs";
32
+ import { connectPages, fieldOpen, keptByComponent, menuRow, modalOps, pageAt, pageIcon, pageObjects, pageOf, pageOfHost, pageOfType, pageOfUnit, panelOpen, typedAsIs } from "./page-objects.mjs";
33
+ import { preloading } from "./type-grid.mjs";
34
+ import { createColumnGaps } from "./columns/index.mjs";
35
+ import { createTableEditing } from "./table/index.mjs";
36
+ import { createAtMenu } from "./at-menu/index.mjs";
37
+ import { createSlashMenu } from "./slash-menu/index.mjs";
38
+ import { createTyping } from "./typing/index.mjs";
39
+ import { createLeaving } from "./typing/leave.mjs";
40
+ import { createTextSelection } from "./text-select/index.mjs";
41
+ import { createSelectAll, topStep } from "./select-all/index.mjs";
42
+ import { revealText } from "./find/index.mjs";
43
+ import { createOutline } from "./outline/index.mjs";
44
+ import { createAnnotate } from "./annotate/index.mjs";
45
+ import { BLOCK_TYPES, HOSTS, PLAIN_TEXT, TYPE_INFO, accepts, blankSaysOf, boxOwner, texOnly, textFilters } from "../components/registry.mjs";
46
+ import { createPages } from "../components/pages.mjs";
47
+ import { openMediaPicker } from "./emoji-picker/index.mjs";
48
+ import { connectPicker, popupOpen } from "./emoji-picker/support.mjs";
49
+ import { menuOpen } from "./menu.mjs";
50
+ import { DEFAULT_TAB_ICON, iconUrl } from "../../shared/emoji/icon-value.mjs";
51
+ import { mediaUrl } from "../../shared/media.mjs";
52
+ import { decoded, fullAddress, isFileLink } from "../../shared/page-address.mjs";
53
+ import { goBackTo, placeIn, unmoved } from "../../shared/reading-place.mjs";
54
+ import { heldList, holdPages } from "./links/pages.mjs";
55
+ import { hideBlocks } from "./offered.mjs";
56
+ import { local, storageOf } from "./storage.mjs";
57
+ import { language, t } from "../../shared/i18n/index.mjs";
58
+
59
+ // In-place editing for the live preview (`npm run dev:local` only; never exported).
60
+ // Each editable block is its own contenteditable host, so a selection never spans blocks and
61
+ // every block maps to one source range (a list item's text; the lists it holds are blocks of their
62
+ // own, never part of its content). Plain typing and IME composition stay with the browser.
63
+ //
64
+ // This module starts the editor and routes the page's events to the editing tools, each in a
65
+ // folder of its own (docs/editor.md, "Where the editing tools live"):
66
+ //
67
+ // typing/ Enter, Backspace/Delete at block edges, auto spacing, placeholders
68
+ // insert/ new blocks: below a block (its menu), a click below the last block, a paragraph before or after
69
+ // format-bar/ the bar over selected text and its keys (marks.mjs does the marks)
70
+ // links/ the link box (Ctrl+K), following links; hints/: why something is read-only
71
+ // formula/ the formula box: selected text made an inline formula (Ctrl+Shift+E)
72
+ // slash-menu/ the "/" menu; at-menu/: the "@" menu; markdown/: Markdown shortcuts as the reader types
73
+ // clipboard/ copying, cutting and pasting blocks and text
74
+ // resize/ dragging the edges of cards, tables and code blocks
75
+ // columns/ the gaps between columns (分栏) and between a flowchart's nodes in a row: resizing the two beside one, a column added at its pin
76
+ // table/ a table's rows and columns: their grips and menus, + to put one in, the bars that add them
77
+ // select-all/ Ctrl+A: the block's text, then its container's blocks, one level a press, then the page
78
+ // outline/ 目录: the page's headings at its right (the room there is margin.mjs's)
79
+ // annotate/ 批注: started from the format bar, a block's button or Ctrl+Alt+M; cards in the margin, with no room markers in the gutter and the card by the words
80
+ // blocks.mjs the block tools: handle/, block-menu/, selection/, drag/
81
+ //
82
+ // and the components' own editing (src/editor/components/<name>/page.mjs). Undo and redo are
83
+ // history.mjs; turning blocks into other types goes through convert.mjs (convertHost, convertUnit
84
+ // below). A report's page is always editing and saves by itself (autosave.mjs, commit.mjs).
85
+
86
+ const mapElement = document.getElementById("doc-edit-map");
87
+ const MAP = JSON.parse(mapElement.textContent);
88
+ mapElement.remove();
89
+
90
+ const keys = { tab: "gallery-edit:tab" };
91
+ const units = new Map((MAP.units ?? []).map((unit) => [unit.id, unit]));
92
+ const boxes = new Map((MAP.boxes ?? []).map((box) => [box.id, box]));
93
+ const baselines = new Map();
94
+ let layout = null; // the page's units as the last commit left them (captureLayout, commit.mjs)
95
+ let blocks = null; // block handles, selection and deleting (blocks.mjs)
96
+ let resize = null; // resizing cards and tables (resize/)
97
+ let gaps = null; // the gaps between columns (columns/)
98
+ let tables = null; // a table's rows and columns (table/)
99
+ let formatting = null; // the bar over selected text (format-bar/)
100
+ let slash = null; // the "/" menu (slash-menu/)
101
+ let atMenu = null; // the "@" menu (at-menu/)
102
+ let markdown = null; // Markdown shortcuts as the reader types (markdown/)
103
+ let selectAll = null; // Ctrl+A widening step by step (select-all/)
104
+ let outline = null; // the page's headings at its right (outline/), not on a page opened on its own (?solo)
105
+ let annotate = null; // 批注 (annotate/), on every page of a session
106
+ let editing = false;
107
+ let composing = false; // an input method is composing
108
+ // A composition ends with compositionend, which Chrome does not always send (a paste over the text
109
+ // being composed): a key, an input, a press or a paste that is no part of one ends it too, so that
110
+ // saving and the format bar never wait on a composition that is over.
111
+ const compositionOver = (event) => {
112
+ if (composing && !event.isComposing && event.keyCode !== 229) composing = false;
113
+ };
114
+ let pressing = false; // the main button is down on the page (a click or a drag under way)
115
+ let commits = null; // the page's commits (commit.mjs)
116
+ let autosave = null; // when they go (autosave.mjs)
117
+ let conflicts = null; // what the file refused, for the reader to decide (conflicts.mjs)
118
+ let unsaveable = { blank: 0, unplaced: 0 }; // blocks the page cannot save as they are
119
+ let notice = null; // { text, time }: what a commit just said, to say again if the page reloads by itself
120
+
121
+ const session = storageOf(() => sessionStorage);
122
+
123
+ // This tab, kept over reloads (sessionStorage). Each tab keeps what waits to be saved (the crash
124
+ // buffer, autosave.mjs) under its own id, so two tabs of a report never overwrite each other's; the
125
+ // open tabs answer with their ids (a BroadcastChannel), so a page only sends what tabs that are gone
126
+ // left there.
127
+ const newTab = () => Math.random().toString(36).slice(2, 10);
128
+ let TAB = session.get(keys.tab) ?? newTab();
129
+ session.set(keys.tab, TAB);
130
+ const channel = (() => { try { return new BroadcastChannel(`gallery-edit:${MAP.slug}`); } catch { return null; } })();
131
+ channel?.addEventListener("message", (event) => {
132
+ if (event.data?.type === "who") channel.postMessage({ type: "here", tab: TAB });
133
+ });
134
+
135
+ function openTabs(wait = 150) {
136
+ if (!channel) return Promise.resolve(new Set());
137
+ const open = new Set();
138
+ const listen = (event) => { if (event.data?.type === "here") open.add(event.data.tab); };
139
+ channel.addEventListener("message", listen);
140
+ channel.postMessage({ type: "who" });
141
+ return new Promise((resolve) => setTimeout(() => {
142
+ channel.removeEventListener("message", listen);
143
+ resolve(open);
144
+ }, wait));
145
+ }
146
+
147
+ const STYLE = `
148
+ /* A frame shows only around what is being edited: the text with the caret, or the component
149
+ whose text it is, drawn in the editor's layer (frame.mjs) with the corners given here, never
150
+ as the element's own outline. Selected blocks get a fill instead (selection/). */
151
+ /* Editable text has a text block's corners and, framed, a light fill under the pointer, unless its
152
+ component declares others: --ge-text-radius, and --ge-text-tint (none: plain, as Notion's and
153
+ Feishu's; a cell, a callout, a code block and its title, a disclosure's title). */
154
+ [data-ge-editing] [contenteditable="true"] { border-radius: var(--ge-text-radius, 3px); }
155
+ [data-ge-editing] [contenteditable="true"]:focus { outline: none; }
156
+ [data-ge-editing] [data-ge-round="box"] { border-radius: 8px; }
157
+ [data-ge-editing] [data-ge-round="text"] { border-radius: 3px; } /* as a text block's */
158
+ /* Text inside a component: the component carries the frame, the text lights up under the pointer. */
159
+ [data-ge-editing] [data-ge-framed][contenteditable="true"]:is(:hover, :focus) { box-shadow: var(--ge-text-tint, inset 0 0 0 100vmax color-mix(in srgb, var(--doc-accent, #2678d8) 9%, transparent)); }
160
+ /* An inline element of the report's own kept whole (data-ge-own) looks as the report draws it. */
161
+ [data-ge-editing] [data-ge-atom]:not([data-ge-own]) { border-radius: 4px; }
162
+ /* A chip of the text is one character to a selection (Notion's user-select: all): a drag that reaches it takes all of it. */
163
+ [data-ge-editing] [data-ge-atom] { user-select: all; }
164
+ /* An empty block under the caret says what it takes; out of the flow, so the caret stays at its start. */
165
+ [data-ge-editing] [data-ge-placeholder]::before { content: attr(data-ge-placeholder); position: absolute; color: var(--doc-muted, #6e6e73); opacity: .7; white-space: nowrap; pointer-events: none; }
166
+ [data-ge-editing] :is(h1, h2, h3, h4)[data-ge-placeholder]::before { opacity: .35; }
167
+ /* 输入以筛选… while the handle's "+" menu is open (typing/ updatePlaceholder): on Notion's gray of its temporary input (notion-lite's slash-menu.js decorate: --ca-graBacPriTra, a 5.5px outline of it, the tertiary text). */
168
+ [data-ge-editing] [data-ge-adding][data-ge-placeholder]::before { color: var(--ge-tx3); opacity: 1; background: var(--ge-field); border-radius: .5px; outline: 5.5px solid var(--ge-field); }
169
+ /* An empty block one is typing in is a line high, yet Chrome lets the margin above it pass
170
+ through it as through an empty box, and counts it again below it: a new paragraph made a
171
+ quote's bar, a panel, a paragraph gap longer until it got a character. A trace of padding
172
+ keeps the margin where it is (a minimum height does not). It weighs nothing: a text with
173
+ padding of its own keeps it (a new code block's empty title lost its 8px below, standing 4px
174
+ off its fold arrow until a character came). */
175
+ :where([data-ge-editing]) :where([contenteditable="true"]:empty) { padding-bottom: .1px; }
176
+ `;
177
+
178
+ // ---------- state ----------
179
+
180
+ // What the editor says when the preview server cannot be reached.
181
+ const OFFLINE = t("连不上 Gallery 的服务(它还在运行吗?gallery open 会启动它)");
182
+
183
+ // Where saving stands: what the workbench session (session.mjs) reports and data-ge-save says.
184
+ const IDLE_STATE = { waiting: 0, syncing: false, offline: false, stopped: null, foreign: false, reloading: false, held: 0, drafts: 0, unstored: false };
185
+ // A change made in the last moment, not counted yet (checkChanges), waits too.
186
+ const view = () => {
187
+ const state = autosave?.state ?? IDLE_STATE;
188
+ return {
189
+ editing, ...state, waiting: state.waiting + (checkPending ? 1 : 0), ...unsaveable,
190
+ };
191
+ };
192
+
193
+ // The editor's state changed: data-ge-save, the workbench session, and a word when commits stop
194
+ // getting through or get through again. While editing, the preview's live reload leaves
195
+ // reloading to the page (autosave.mjs: once nothing waits).
196
+ let lastState = "saved";
197
+ let reconnecting = false; // back from offline, what it held on its way: said to be saved once it is
198
+ function refresh() {
199
+ window.__galleryEditBusy = editing;
200
+ const state = saveState(view());
201
+ document.documentElement.setAttribute("data-ge-save", state);
202
+ document.documentElement.toggleAttribute("data-ge-drafts", view().drafts > 0); // blocks not finished, not written (tests read it)
203
+ if (autosave) {
204
+ if (state === "offline" && lastState !== "offline") say(t("{0}:修改暂存在浏览器里,连上后自动保存。", OFFLINE), "error");
205
+ else if (state === "saved" && reconnecting) say(t("已重新连上,修改已保存。"));
206
+ reconnecting = state !== "held" && state !== "saved" && (reconnecting || state === "offline");
207
+ lastState = state;
208
+ }
209
+ workbench?.update();
210
+ showTabIcon();
211
+ }
212
+
213
+ // The browser tab shows the page's icon (the server's link in the head, render/emoji.mjs), changed
214
+ // with it: a new icon, one taken away, an undo. A page with none shows the default document icon
215
+ // (DEFAULT_TAB_ICON: a tab whose link is taken away keeps the image it showed), its link the
216
+ // preview's own (data-doc-dev: an export of the view leaves it out, as the file has no icon).
217
+ let tabIcon;
218
+ function showTabIcon() {
219
+ const value = pageIcon();
220
+ if (value === tabIcon) return;
221
+ tabIcon = value;
222
+ let link = document.head.querySelector("link[data-doc-tab-icon]");
223
+ if (!link) {
224
+ link = element("link", { rel: "icon", "data-doc-tab-icon": "" });
225
+ document.head.append(link);
226
+ }
227
+ link.toggleAttribute("data-doc-dev", !value);
228
+ const href = value ? iconUrl(value, MAP.slug, { tab: true }) : DEFAULT_TAB_ICON;
229
+ if (link.getAttribute("href") !== href) link.setAttribute("href", href);
230
+ }
231
+
232
+ // Where saving stands, in one word (on the page's root as data-ge-save, for styles and tests).
233
+ const saveState = ({ waiting, syncing, offline, stopped, reloading, held }) => stopped ? "stopped" : reloading ? "reloading" : offline ? "offline"
234
+ : syncing ? "syncing" : waiting > 0 ? "waiting" : held > 0 ? "held" : "saved";
235
+
236
+ // ---------- undo, the components, typing ----------
237
+
238
+ const history = createHistory({
239
+ changed: markChanged, beforeUndo: () => closeTyped(), blocks: () => blocks, show: showLanded, tidy: () => [...pageObjects().flatMap((page) => page.tidy ?? []), tidyEmptied],
240
+ afterText: (element) => pageOf(element)?.afterText?.(element), // what follows a text step undone or redone
241
+ refuse: () => say(t("这里合并进了 AI 同时写的字,撤销会把它们一起撤掉:没有撤销,可以直接改。"), "error"),
242
+ });
243
+ const { lastKnown, recordText, recordStep, sourceStep, closeTyping, struct, finish: finishStep, lookStep, undo, redo } = history;
244
+
245
+ // Where undo or redo landed shown: the Sheet panels out of sight around it, from the outside in (a
246
+ // click on a panel's tab switches to it), and the detail card it is in.
247
+ function showLanded(node) {
248
+ const panels = [];
249
+ for (let at = node?.nodeType === 1 ? node : node?.parentElement; at; at = at.parentElement) if (at.hidden) panels.unshift(at);
250
+ panels.forEach((panel) => boxOwner(panel)?.click());
251
+ modalOps().show(node);
252
+ }
253
+
254
+ const insert = createInsert({ struct, blocks: () => blocks });
255
+ const { newParagraph } = insert;
256
+
257
+ // What a component does on the page beyond what every block does is its own page module
258
+ // (src/editor/components/<name>/page.mjs, listed in components/pages.mjs); the editor calls it where
259
+ // that happens (Enter, Backspace, Tab, typing, a press, undo) and gives them all one `ctx`.
260
+
261
+ // The icon picker's images go into the report's folder (editor/media-store.mjs), where the server serves them.
262
+ const assetUrl = (file) => mediaUrl("asset", MAP.slug, file);
263
+ connectPicker({ slug: MAP.slug, token: MAP.token, notify: say });
264
+ // Whether a block tool may act: the page edits, no reload is on its way and no field of a component is being edited.
265
+ const active = () => editing && !autosave?.state.reloading && !fieldOpen();
266
+ // A picture or file sent to the report's folder (kind: "image", "file" or "embed"): the server's answer.
267
+ async function uploadTo(kind, body) {
268
+ const answer = await post("/__gallery/edit/upload", { slug: MAP.slug, kind, ...body });
269
+ if (!answer.ok) throw new Error(failure(answer, t("上传失败")));
270
+ return answer;
271
+ }
272
+ // A web page as the server reads it (parts: "all" for a bookmark, "icon" or "title" for a link's
273
+ // views: links/views.mjs), its images kept in the report's folder.
274
+ async function readPage(url, parts = "all") {
275
+ const answer = await post("/__gallery/edit/unfurl", { slug: MAP.slug, url, parts });
276
+ if (!answer.ok) throw new Error(failure(answer, t("打不开这个网页")));
277
+ return answer;
278
+ }
279
+ // The one `ctx` of the components' page modules, each taking what it uses of it (docs/editor.md,
280
+ // "Component definitions"); createPages adds `pages`, what they have of each other. What is made
281
+ // only in boot() (the block tools, the format bar, the "/" menu) is asked for when it is used.
282
+ connectPages(createPages({
283
+ // the page's file and undo: the edit map, the baselines, the last recorded content of each block, an undo step of each kind
284
+ map: MAP, baselines, lastKnown, recordText, closeTyping, struct, finishStep, look: lookStep, sourceStep,
285
+ // the editor's state, and what it does for a component
286
+ say, isEditing: () => editing, isComposing: () => composing, active, closeTools,
287
+ hideFormatBar: () => formatting.hide(),
288
+ // text blocks and their types
289
+ limitText, caretAtOffset, selectionContext, typeOf, targetsOf, convertHost, newParagraph,
290
+ // the block tools, and a table's tools (table/); a key that is the "/" or "@" menu's, standing on text a component takes keys of (their key())
291
+ blocks: () => blocks, tables: () => tables,
292
+ typedKey,
293
+ // the report's folder: pictures, files, embedded files and a bookmark's images go there (editor/media-store.mjs)
294
+ assetUrl, upload: uploadTo,
295
+ fileUrl: (path) => mediaUrl("file", MAP.slug, path),
296
+ embedUrl: (path) => mediaUrl("embed", MAP.slug, path),
297
+ async importEmoji(library) {
298
+ const answer = await post("/__gallery/edit/upload", { slug: MAP.slug, library });
299
+ if (!answer.ok) throw new Error(failure(answer, t("无法使用这个自定义表情")));
300
+ return answer.path;
301
+ },
302
+ // the web, through the server: a bookmark's page, whether a page lets frames show it
303
+ unfurl: (url) => readPage(url),
304
+ frameable: (url) => post("/__gallery/edit/frameable", { slug: MAP.slug, url }),
305
+ // tools of the page's: the icon picker, a link's 显示为 › rows (links/views.mjs), KaTeX
306
+ openMediaPicker, showAs: (figure, link) => bookmarkItems(figure, link), loadKatex,
307
+ })); // what the engines ask of a component (page-objects.mjs)
308
+
309
+ const typing = createTyping({
310
+ struct, say, keepText: (node) => blocks.keepText(node), paragraphBefore: insert.paragraphBefore, paragraphAfter: insert.paragraphAfter, removeBlocks: (units) => blocks.remove(units), selectBlock: (unit) => blocks.select(unit),
311
+ adding: () => slash?.adding ?? null,
312
+ leave: createLeaving({ struct, touchedBy, convertHost, typeOf, targetsOf }),
313
+ });
314
+ // Selecting text across blocks: its listeners go before the editor's own keys and clipboard.
315
+ const crossText = createTextSelection({
316
+ isEditing: () => editing, struct, say, keepText: (node) => blocks.keepText(node), selectUnits: (list) => blocks.select(list), markUnits: (list) => blocks.mark(list), split: (host) => typing.split(host),
317
+ copy: (data, range, pieces, cut) => clipboard.copyAcross(data, range, pieces, cut), // its blocks, each of its type (clipboard/)
318
+ claims: (from, to) => tables?.claims(from, to) ?? false, // a drag between cells of a table selects them (table/)
319
+ selectAll: (event, touched) => selectAll?.across(event, touched) ?? false, // Ctrl+A: the first container holding its blocks (select-all/)
320
+ // The format bar: shown for it, its keys and buttons formatting it (format-bar/).
321
+ keeps: (node) => formatting?.holds(node) ?? false, formatKey: (event) => formatting?.key(event) ?? false, changed: () => formatting?.schedule(),
322
+ });
323
+ // A key that is the "/" or "@" menu's, standing on what is typed: it comes before anything of the text's.
324
+ function typedKey(event) {
325
+ return slash.key(event) || atMenu.key(event);
326
+ }
327
+
328
+ // What stands on what is being typed goes, the text staying: the "/" menu and the "@" menu, which
329
+ // hold commits back while they stand.
330
+ function closeTyped() {
331
+ slash?.close();
332
+ atMenu?.close();
333
+ }
334
+
335
+ // The box of a formula in the text closes, and what stands open over the page as a component's editor
336
+ // (`panel`), what is in them kept: commits wait while one stands.
337
+ function keepPanels() {
338
+ if (formulaBoxNode()) dismissFormula();
339
+ panelOpen()?.dismiss();
340
+ }
341
+
342
+ // What a press that edits something closes first: what opens over the page.
343
+ function closeTools() {
344
+ formatting.hide();
345
+ closeTyped();
346
+ blocks.closeMenu();
347
+ if (linkBoxNode()) dismissLink();
348
+ keepPanels();
349
+ }
350
+ const clipboard = createClipboard({
351
+ editing: () => editing, blocks: () => blocks, struct, recordText, say,
352
+ loadKatex,
353
+ // A link shown with its icon pasted: its icon kept where it is a file of this report's folder
354
+ // (served from it, or a copy of this report's own blocks), else a globe; a page block as it is, its
355
+ // address good in any document, but for one to a block of the page it was copied from, and shown
356
+ // as its document is now.
357
+ mention(parts, same, from) {
358
+ const chip = newMention(document, { href: parts.href.startsWith("#") && !same && from ? `../${from}/doc.html${parts.href}` : parts.href, title: isFileLink(parts.href) ? parts.title : linkTitle(parts.title, parts.href), icon: (parts.slug ? parts.slug === MAP.slug : same) ? parts.icon : null, drawn: parts.drawn }, assetUrl);
359
+ drawPasted(chip);
360
+ return chip;
361
+ },
362
+ linkPasted: (link, page) => offerViews(link, { pasted: true, page }),
363
+ pageLink: pageLinkOf,
364
+ titleBold: () => formatting?.armed ?? false, // a node's empty title: what is pasted there bold, as typed
365
+ showCaret,
366
+ });
367
+
368
+ // ---------- editing ----------
369
+
370
+ // The page edits from the start: every block prepared, the layout and the commits set up.
371
+ function enter() {
372
+ if (editing) return;
373
+ // Two passes: every block made editable (or not) as the page was served, then every block rebuilt
374
+ // (core.mjs editableAhead: a contenteditable set after blocks were rebuilt lays the page out again).
375
+ const nodes = new Map();
376
+ for (const block of MAP.blocks) {
377
+ if (HOSTS[block.kind]?.closed) continue;
378
+ const node = document.querySelector(`[data-ge-block="${block.id}"]`);
379
+ if (!node) continue;
380
+ if (HOSTS[block.kind]?.tex) node.setAttribute("contenteditable", "true"); // a display formula (components/math/)
381
+ else if (!matchesSource(node, block.inner)) {
382
+ node.setAttribute("data-ge-lock", "script");
383
+ continue;
384
+ } else editableAhead(node, block);
385
+ nodes.set(block, node);
386
+ }
387
+ const hosts = [];
388
+ // The blocks rebuilt from the file look as the page drew them: the kit's components hear it
389
+ // quietly (DocKit.quietly: its tables lay themselves out again once the page is idle, not in
390
+ // the frame editing starts in, which the outline's first drawing and a workbench's ready wait for).
391
+ const quietly = globalThis.DocKit?.quietly ?? ((rebuild) => rebuild());
392
+ quietly(() => {
393
+ for (const block of MAP.blocks) {
394
+ if (HOSTS[block.kind]?.closed) {
395
+ pageOfHost(block.kind).prepare(block);
396
+ continue;
397
+ }
398
+ const node = nodes.get(block);
399
+ if (!node) continue;
400
+ if (HOSTS[block.kind]?.tex) { // a display formula: the kit's drawing as its chip (components/math/)
401
+ baselines.set(block.id, pageOfHost(block.kind).prepare(block, node));
402
+ lastKnown.set(node, ownHTML(node));
403
+ hosts.push(node);
404
+ continue;
405
+ }
406
+ baselines.set(block.id, prepareBlock(node, block));
407
+ const host = node.hasAttribute("data-ge-block") ? node : node.firstElementChild; // a Sheet tab's name span
408
+ lastKnown.set(host, ownHTML(host));
409
+ hosts.push(host);
410
+ }
411
+ });
412
+ layout = captureLayout(document, MAP);
413
+ markFrames(document, layout, hosts);
414
+ commits = createCommits({ root: document, map: MAP, baselines, units, boxes, getLayout: () => layout, setLayout: (next) => (layout = next) });
415
+ // Commits wait while an input method composes, a formula is being edited (formula/), the "/" menu
416
+ // or the "@" menu stands on what is typed for it (half of it is no text of the file's) or a gap
417
+ // between a flowchart's nodes is dragged (columns/: the row is shown at the weights being dragged,
418
+ // written on its nodes, until letting go makes them one undo step or Esc puts them back); the page
419
+ // is not shown again (someone else's write) while the pointer is down, a 批注 is being written
420
+ // (annotate/) or 仍然调整 has just written over someone else's change, its undo the reader's to take
421
+ // (keepsOverwrite), unless the page asked to be shown again itself.
422
+ autosave = createAutosave({
423
+ map: MAP, commits, blocked: () => composing || formulaBoxNode() !== null || panelOpen() !== null || linkPending() || Boolean(gaps?.dragging || slash?.host || atMenu?.host), changedAt: () => lastChange, holding: (asked) => pressing || Boolean(annotate?.writing()) || keepsOverwrite(asked), refresh, applied: committed, failed: refused, storage: local, tab: () => TAB,
424
+ before: () => annotate?.sync() ?? null, // where the words of 批注 went with the edit, told before it is written (annotate/)
425
+ warn: () => say(t("浏览器存储写不进去(空间满了,或被禁用):没保存的修改只在这个页面上,先不要关掉它。"), "error"),
426
+ notice: () => notice && Date.now() - notice.time < 10000 ? notice.text : null,
427
+ session,
428
+ reverted: (entries) => {
429
+ conflicts.add(entries.map((entry) => ({ ...entry, key: `r:${entry.key}`, revert: entry.key, kind: entry.written ? "reverted-source" : entry.unit === undefined ? "reverted" : "reverted-unit" })));
430
+ say(t("AI 的写入把你之前保存的 {0} 处修改改回去了:在左下方选择保留哪一边。", entries.length), "error");
431
+ },
432
+ });
433
+ conflicts = createConflicts({ act: decide, nodeOf: conflictNode, restorable, copyBlock: (node) => clipboard.copyBlocks([node]), say });
434
+ editing = true;
435
+ document.documentElement.setAttribute("data-ge-editing", "");
436
+ refresh();
437
+ for (const page of pageObjects()) page.entered?.();
438
+ }
439
+
440
+ // What the page says when what a component wrote went by a manifest that changed: given up, the page shown again.
441
+ const outdatedNotice = () => t("组件包刚刚变了,这次改动没有保存,页面已按新的组件重新显示。");
442
+
443
+ // What 仍然调整 wrote over someone else's change of what a component writes, by page unit: the step that
444
+ // gives theirs back (installed/ overwrite), and when its write went in, all of it (commit.mjs `forced`).
445
+ // Refused again (someone wrote it again since the reader was shown it), the step stands for nothing and
446
+ // goes; gone in, no undo goes below it (history fold), and the page is not shown again for someone else's
447
+ // write before the reader's next change (that undo among them) or KEEP_OVERWRITE: shown again, the page has
448
+ // no undo steps, and theirs would be gone from it. A showing the page asks for itself (保留 AI 的, a
449
+ // package changed) is not held: `asked`.
450
+ const overwrites = new Map(); // unit -> { step, landed }
451
+ const KEEP_OVERWRITE = 30000;
452
+ const keepsOverwrite = (asked) => !asked && [...overwrites.values()].some(({ landed }) => landed > lastChange && Date.now() - landed < KEEP_OVERWRITE);
453
+
454
+ // A commit was taken in (commit.mjs): a block merged with someone else's words shows the merge;
455
+ // what the file refused is flagged and held back, the rest goes on.
456
+ function committed({ conflicts: held, merged, outdated, forced = [] }, receipt) {
457
+ for (const host of merged) history.merged(host);
458
+ for (const unit of forced) {
459
+ const written = overwrites.get(unit);
460
+ if (!written || written.landed > 0) continue;
461
+ history.fold(written.step);
462
+ written.landed = Date.now();
463
+ }
464
+ for (const entry of held) {
465
+ const written = entry.kind === "unit" && entry.again && entry.change?.source ? overwrites.get(entry.unit) : null; // that write, refused again
466
+ if (!written || written.landed > 0) continue;
467
+ history.drop(written.step);
468
+ overwrites.delete(entry.unit);
469
+ }
470
+ if (outdated) autosave.reloadSoon(); // what a component wrote by a manifest that changed: the page goes by the new one, shown again
471
+ if (held.length > 0) {
472
+ conflicts.add(held);
473
+ const conflicted = held.filter((entry) => !unfit(entry) && !packageChanged(entry)).length;
474
+ const packaged = held.filter(packageChanged).length;
475
+ say(conflicted > 0 ? t("{0} 处修改和文件的最新版本冲突,没有保存:在左下方选择怎么处理。", conflicted) : packaged > 0 ? t("组件包刚刚变了,{0} 处修改没有保存:在左下方选择怎么处理。", packaged) : t("{0} 处内容文档存不下,没有保存:在左下方选择怎么处理。", held.length), "error");
476
+ }
477
+ else if (outdated || merged.length > 0 || receipt.texts?.some((entry) => entry.status === "merged")) {
478
+ const count = receipt.texts.filter((entry) => entry.status === "merged").length;
479
+ notice = { text: outdated ? outdatedNotice() : t("已自动合并 {0} 段 AI 同时做的修改。", count), time: Date.now() };
480
+ say(notice.text, "note", 5000);
481
+ }
482
+ // What the server says besides (service.mjs retitle, reicon: "标题已更新", what did not follow it),
483
+ // each note in the UI's language, as one sentence.
484
+ else if (receipt.notes?.length) say(t("{0}。", receipt.notes.map((note) => t(note)).join(t(";"))), "note", receipt.notes.length > 1 ? 6000 : undefined);
485
+ }
486
+
487
+ // ---------- conflicts ----------
488
+
489
+ // The block a conflict is about, on the page.
490
+ function conflictNode(entry) {
491
+ if (entry.kind === "text") return document.querySelector(`[data-ge-block="${entry.block}"]`);
492
+ if (entry.kind === "unit" || entry.kind === "reverted-unit" || entry.kind === "reverted-source") return document.querySelector(`[data-ge-unit="${entry.unit}"]`);
493
+ if (entry.kind === "insert") return entry.element?.isConnected ? entry.element : null;
494
+ // Someone else put a committed text back: found by what names it (it may be from an earlier load).
495
+ if (entry.block?.title) return document.querySelector('[data-ge-kind="title"]');
496
+ const unit = entry.block?.unit?.id ? document.querySelector(`[data-block-id="${CSS.escape(entry.block.unit.id)}"]`) : null;
497
+ return entry.block?.self ? unit : null;
498
+ }
499
+
500
+ // Whether a key types or edits text: a character or an input method's (229), Enter, Backspace, Delete
501
+ // or Tab, with no Ctrl, Alt or ⌘.
502
+ const EDIT_KEYS = new Set(["Enter", "Backspace", "Delete", "Tab"]);
503
+ const editsText = (event) => !event.ctrlKey && !event.metaKey && !event.altKey && (event.key.length === 1 || event.keyCode === 229 || EDIT_KEYS.has(event.key));
504
+
505
+ // A text the file no longer has can be made again as a new block when it is a plain one.
506
+ // The keys that only modify others: pressed alone, nothing is typed.
507
+ const MODIFIER_KEYS = new Set(["Alt", "AltGraph", "Control", "Shift", "Meta", "OS", "CapsLock", "Fn"]);
508
+
509
+ function restorable(entry) {
510
+ const host = entry.kind === "text" && entry.reason === "missing" ? conflictNode(entry) : null;
511
+ return Boolean(host && MAP.blocks[entry.block]?.self && PLAIN_TEXT.has(typeOf(host)));
512
+ }
513
+
514
+ // Whether a held entry is about nothing the page still changes: a block made on the page that is off
515
+ // it, a text or a block's change the file cannot hold, which the page no longer asks for, or a block's
516
+ // change refused for someone else's change of it (`changed`) when the page makes no change of that kind
517
+ // any more (the deletion undone, the conversion undone or the block deleted since): what the reader does
518
+ // to it then goes, or asks, as any change does. What a component wrote stays on its card through undo and
519
+ // redo, as the reader's version of the block.
520
+ function undoneRefusal(entry, { edits, units }) {
521
+ if (entry.kind === "insert") return Boolean(entry.element) && !entry.element.isConnected;
522
+ if (entry.kind === "unit" && entry.reason === "changed" && !entry.change?.source) return !units.some((change) => change.id === entry.unit && kindOf(change) === kindOf(entry.change));
523
+ if (!unfit(entry)) return false;
524
+ return entry.kind === "text" ? !edits.some((edit) => edit.id === entry.block) : entry.kind === "unit" && !units.some((change) => change.id === entry.unit);
525
+ }
526
+
527
+ /** Do what the reader chose for a conflict (conflicts.mjs); true when it is settled. */
528
+ async function decide(entry, action) {
529
+ if (entry.kind.startsWith("reverted")) return autosave.decide(entry.revert, action === "mine");
530
+ const key = entry.key; // as commit.mjs holds it: "b<block>", "u<unit>" or "i<id>"
531
+ const node = conflictNode(entry);
532
+ if (action === "mine" || action === "force") {
533
+ commits.resolve(key, action);
534
+ // Written over what someone else wrote of what its component writes (`theirs`, by part, as the reader
535
+ // was shown it): one undo gives the file's back.
536
+ const step = action === "force" && entry.theirs && typeof entry.theirs === "object" && node ? pageOfUnit(node)?.overwrite?.(node, entry.theirs) : null;
537
+ if (step) overwrites.set(entry.unit, { step, landed: 0 });
538
+ } else if (action === "theirs") {
539
+ commits.resolve(key, "discard"); // the page shows the file's once nothing else waits
540
+ autosave.reloadSoon();
541
+ return true;
542
+ } else if (action === "accept") { // the file deleted it: so does the page
543
+ const unit = entry.kind === "text" ? node?.closest("[data-ge-unit]") : node;
544
+ unit && withLeads(unit).forEach((part) => part.remove());
545
+ commits.resolve(key, entry.kind === "text" ? "forget" : "discard");
546
+ } else if (action === "restore") { // made again, as a new block where it was
547
+ const next = newHost(document, typeOf(node), node);
548
+ next.innerHTML = ownHTML(node);
549
+ node.replaceWith(next);
550
+ commits.resolve(key, "forget");
551
+ } else if (action === "undo" && entry.element) { // the block made on the page goes
552
+ withLeads(entry.element).forEach((part) => part.remove());
553
+ commits.resolve(key, "discard");
554
+ } else if (action === "nearby" && entry.element?.isConnected) {
555
+ // Where the file surely takes it: right after the block it stands in, in the page's own flow,
556
+ // or at the end of that flow when it stands there already. An undoable move; then written anew.
557
+ const block = entry.element;
558
+ const flow = block.closest("main");
559
+ let top = block;
560
+ while (top.parentElement && top.parentElement !== flow) top = top.parentElement;
561
+ if (!flow || !top.parentElement) return false;
562
+ const moved = struct([block.parentElement, flow], [], () => {
563
+ if (top === block) flow.append(block);
564
+ else top.after(block);
565
+ return { touched: [], focus: null, select: block };
566
+ });
567
+ if (!moved) return false;
568
+ commits.resolve(key, "retry");
569
+ } else return false;
570
+ markChanged(true);
571
+ return true;
572
+ }
573
+
574
+ // A commit the server refuses whatever the page does: saving stops, and the page says why (the
575
+ // server's message, a whole one, in the UI's language).
576
+ function refused(reason, message) {
577
+ if (reason === "token" || reason === "origin" || reason === "host") {
578
+ say(t("预览服务器不认得这个页面了(重启过?),正在刷新;没保存的修改会自动放回。"), "prompt");
579
+ autosave.store();
580
+ setTimeout(() => reloadView(MAP.slug), 600);
581
+ return;
582
+ }
583
+ say(reason === "missing" ? t("这份文档的文件不见了(被移动或删除了?)。修改暂存在浏览器里,没有保存。") : t("没有保存:{0}", message ? t(message) : reason), "error");
584
+ }
585
+
586
+ // ---------- change tracking ----------
587
+
588
+ // Comparing every block with its baseline is cheap but not free, so batch bursts of input.
589
+ // A timer, not requestAnimationFrame: animation frames pause while the page is not painted.
590
+ // `now`: a discrete change (not typing), committed without waiting for a pause.
591
+ let checkPending = false;
592
+ let discrete = false;
593
+ let lastChange = 0; // when the reader last changed anything (the page reloads by itself only in a pause)
594
+ function markChanged(now = false) {
595
+ if (now) {
596
+ // A structural change (undo among them): blocks it brought back are marked from what the file
597
+ // has, and a conflict's flag is on the block it is about now.
598
+ commits?.marks();
599
+ conflicts?.reflag();
600
+ }
601
+ if (messageKind() === "error") settle(); // the reader is dealing with it
602
+ lastChange = Date.now();
603
+ discrete ||= now;
604
+ const counting = checkPending;
605
+ checkPending = true; // counts as waiting until counted
606
+ refresh();
607
+ if (!counting) setTimeout(checkChanges, 60);
608
+ }
609
+
610
+ // Also run at once when the page goes away, so the last keystrokes are counted.
611
+ function checkChanges() {
612
+ if (!checkPending || !autosave) return;
613
+ checkPending = false;
614
+ const now = discrete;
615
+ discrete = false;
616
+ document.querySelectorAll('[data-ge-flag="check"]').forEach((node) => node.removeAttribute("data-ge-flag"));
617
+ const collected = commits.collect();
618
+ // What the file refused and the reader has undone: nothing is held for it any more.
619
+ const lapsed = conflicts.drop((entry) => undoneRefusal(entry, collected));
620
+ lapsed.forEach((entry) => commits.resolve(entry.key, "retry"));
621
+ if (lapsed.length > 0 && messageKind() === "error") settle();
622
+ // A new block with no place, or one that must keep some text: left out until the reader fixes it.
623
+ collected.unplaced.forEach((node) => node.setAttribute("data-ge-flag", "check"));
624
+ const before = unsaveable;
625
+ unsaveable = { blank: collected.blank.length, unplaced: collected.unplaced.length };
626
+ if (unsaveable.unplaced > before.unplaced) say(t("红框里的新段落所在的位置放不下段落,没有保存:请并入相邻段落,或拖到别处。"), "error");
627
+ else if (unsaveable.blank > before.blank && !messageKind()) {
628
+ say(collected.blank.map(blankSaysOf).find(Boolean) ?? t("这里还空着:写上内容后才会保存。")); // what its component says
629
+ }
630
+ autosave.changed(now, collected);
631
+ refresh();
632
+ }
633
+
634
+ // ---------- block types ----------
635
+ // Turning blocks into other types (convert.mjs) as undo steps: from the "/" menu (slash-menu/),
636
+ // Markdown typed at a block's start (markdown/), the block menu (block-menu/) and the components.
637
+
638
+ // Turn a text block into another type as one undo step; the caret goes to the start of the block
639
+ // that holds its text now. `typed`: a range of what the reader typed to ask for it ("/…", "# "),
640
+ // taken away first (the block it leaves empty stands for a new highlight block or disclosure:
641
+ // convertBlock's `fresh`).
642
+ // `after(focus)`: what else the same undo step does to the converted block (a list made by "3. "
643
+ // beginning at 3), returning the containers it made.
644
+ function convertHost(host, type, typed = null, after = null) {
645
+ const result = struct(touchedBy(host), [host], () => {
646
+ typed?.deleteContents();
647
+ const { focus, created } = convertBlock(host, type, { fresh: typed !== null });
648
+ if (!focus && !typed) return null;
649
+ const more = focus && after ? after(focus) : [];
650
+ return { touched: [focus ?? host], focus: focus ?? host, created: [...created, ...more] };
651
+ });
652
+ if (result) afterConvert(result.focus, type, result.focus !== host ? t("已转换为{0}", menuRow(type).label) : null);
653
+ }
654
+
655
+ // What the component of a type does with a new block of it (`onCreated`, registry.mjs): true when
656
+ // what comes next is its own.
657
+ const onCreated = (type, block) => pageOfType(type)?.onCreated?.(block, type);
658
+
659
+ // What a block turned or made into a type needs next: its component's (a media block's picture,
660
+ // file, link or page chosen, a formula block's formula typed), else the caret goes in it, on the
661
+ // words it came with where those are a sample, which typing takes the place of (`sample`); `said`,
662
+ // what the status line says of it.
663
+ function afterConvert(focus, type, said) {
664
+ if (onCreated(type, focus)) return;
665
+ caretAt(focus, "start");
666
+ if (menuRow(type)?.sample && isHost(focus)) {
667
+ const words = document.createRange();
668
+ words.selectNodeContents(focus);
669
+ select(words);
670
+ }
671
+ if (said) say(t("{0} · Ctrl+Z 撤销", said));
672
+ }
673
+
674
+ /**
675
+ * A block of `type` below `target` (a text block or any other block), as the "/" menu makes one in a
676
+ * block with text and the block menu's 在下方新建 (Notion's): the typed "/…" (`typed`) taken away; a
677
+ * text block splits at its end (as Enter there would: an item after an item, a paragraph after a
678
+ * heading) and the new block turns into the type; below any other block, an empty paragraph of its
679
+ * box does (the first box out that takes one). One undo step.
680
+ */
681
+ function insertBelow(target, type, typed = null) {
682
+ const splits = isHost(target) && can(target, "split");
683
+ let after = target;
684
+ while (!splits && after && !accepts(after.parentElement?.getAttribute("data-ge-box-kind"), "paragraph")) after = after.parentElement?.closest(OUTER);
685
+ if (!splits && !after) return say(t("这里不能新建块。"));
686
+ const result = struct(splits ? touchedBy(target) : [after.parentElement], splits ? [target] : [], () => {
687
+ typed?.deleteContents();
688
+ let fresh;
689
+ if (splits) {
690
+ const end = document.createRange();
691
+ end.selectNodeContents(target);
692
+ end.collapse(false);
693
+ fresh = splitBlock(target, end);
694
+ } else {
695
+ fresh = newHost(document, "paragraph");
696
+ after.after(fresh);
697
+ }
698
+ if (!fresh) return null;
699
+ const turned = type === "paragraph" && fresh.localName === "p" ? { focus: null, created: [] } : convertBlock(fresh, type, { fresh: true });
700
+ const focus = turned.focus ?? fresh;
701
+ return { touched: splits ? [target, focus] : [focus], focus, created: turned.created };
702
+ });
703
+ if (result) afterConvert(result.focus, type, t("已插入{0}", menuRow(type).label));
704
+ }
705
+
706
+ // The block handle's "+" (handle/; notion-lite's hover menu, 398193): an empty block at the block's
707
+ // level below it (`where` "above": Alt, before it), the caret in it and the "/" menu open there
708
+ // unfiltered (slash-menu/ add: what is typed then filters it, as after a "/"), one undo step taking
709
+ // the block away again. A flowchart's node gets a node beside it (flowchart/ addAfter: its title the
710
+ // menu's, whose blocks go below the title, as a "/" typed there), any other block an empty paragraph
711
+ // or list item (insert/ addNear).
712
+ function addNear(unit, where) {
713
+ const above = where === "above";
714
+ const own = pageOfUnit(unit)?.addNear?.(unit, above);
715
+ const host = own === undefined ? insert.addNear(unit, above ? "above" : "below") : own;
716
+ if (!host) return say(t("这里不能新建块。"));
717
+ if (slash.add(host)) typing.updatePlaceholder(host); // 输入以筛选… while nothing is typed
718
+ }
719
+
720
+ // 公式, 链接 and 页面 from the "/" menu: the typed "/…" goes (as typing), then a formula, a link or a
721
+ // page block where it was, its field open (formula/, links/).
722
+ const inlineInsert = {
723
+ formula(host, typed) {
724
+ typed.deleteContents();
725
+ recordText(host);
726
+ select(typed);
727
+ openFormula(host, { fresh: true });
728
+ },
729
+ link(host, typed) {
730
+ typed.deleteContents();
731
+ recordText(host);
732
+ select(typed);
733
+ openLink(host, { fresh: true });
734
+ },
735
+ page(host, typed) {
736
+ typed.deleteContents();
737
+ recordText(host);
738
+ select(typed);
739
+ openLink(host, { fresh: true, page: true });
740
+ },
741
+ };
742
+
743
+ // The block menu: a block (its handle) turned into a type, as one undo step (convert.mjs turnInto,
744
+ // turnUnit). What the type draws as it is made (`preload`) comes first when the block has text, the
745
+ // grid's preload (type-grid.mjs) not having brought it yet: what undo and redo put back is what was made.
746
+ function convertUnit(unit, type, { loaded = false } = {}) {
747
+ const made = noTextToCarry(unit); // with no text to carry it is a new block, whose component says what follows (`onCreated`)
748
+ const waits = !loaded && !made ? preloading(type) : null;
749
+ if (waits) return void waits.then(() => unit.isConnected && convertUnit(unit, type, { loaded: true }));
750
+ let emptied = false; // no block left to select: an empty one to write in
751
+ const result = struct(turnParents(unit), [], () => {
752
+ const turned = turnUnit(unit, type);
753
+ emptied = Boolean(turned.emptied);
754
+ return turned.focus ? { touched: [turned.focus], focus: turned.focus, created: turned.created, select: emptied ? null : unit } : null;
755
+ });
756
+ if (!result) return;
757
+ if (!made || !onCreated(type, result.focus)) {
758
+ if (emptied && isHost(result.focus)) caretAt(result.focus, "start");
759
+ else {
760
+ // What it is now: the new highlight block a block went into, as a disclosure (notion-lite's
761
+ // transformBlocks selects the callout), the flowchart a block or a list went into (a container
762
+ // of a type that takes a block as it is, no `wraps`), else the block holding the text.
763
+ const { container, wraps } = BLOCK_TYPES[type];
764
+ const target = container && !wraps ? result.focus.closest(container) : unitElement(result.focus);
765
+ if (target) blocks.select(target, { quiet: true });
766
+ }
767
+ }
768
+ say(t("已转换为{0} · Ctrl+Z 撤销", TYPE_INFO[type].label));
769
+ }
770
+
771
+ /**
772
+ * Several blocks turned into one type at once (convert.mjs convertGroup, notion-lite's
773
+ * transformBlocks: the block menu of a group of selected blocks, the format bar over text across
774
+ * blocks), one undo step. `select`: what they are now is selected (the block menu: undo selects what
775
+ * they were, redo again what they are now); the format bar keeps its text selection itself. Returns
776
+ * convertGroup's outcome (`hosts`: where each text block's text is now), or null when nothing changed.
777
+ */
778
+ function convertAll(group, type, { select = true } = {}) {
779
+ let outcome = null;
780
+ const result = struct(groupParents(group, type), group.filter(isHost), () => {
781
+ outcome = convertGroup(group, type);
782
+ if (!outcome.changed) return null;
783
+ const texts = [...outcome.hosts.values()].filter((host) => host?.isConnected && isHost(host));
784
+ return { touched: texts, focus: texts[0] ?? null, created: outcome.created, ...(select ? { select: [...group], selectAfter: outcome.units } : {}) };
785
+ });
786
+ if (!result) return null;
787
+ if (select) blocks.select(outcome.units, { quiet: true });
788
+ preloading(type)?.then(() => pageOfType(type).redraw?.(outcome.units)); // what the type draws, come after the blocks did
789
+ say(t("已转换为{0} · Ctrl+Z 撤销", TYPE_INFO[type].label));
790
+ return outcome;
791
+ }
792
+
793
+ function onKeydown(event) {
794
+ compositionOver(event);
795
+ if (!editing) return;
796
+ // A 批注's card is a field of its own: its keys are its own, but for Ctrl+S, which saves it and the report.
797
+ if (annotate?.holds(event.target)) {
798
+ const mod = event.ctrlKey || event.metaKey;
799
+ if (mod && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "s") {
800
+ event.preventDefault();
801
+ saveNow();
802
+ }
803
+ return;
804
+ }
805
+ if (keptByComponent(event.target, event)) return; // the field of a code block's language menu keeps its keys, and all that stands in an instance of a component package
806
+ followEnd(); // the caret it takes into a detail card's last block: the content to its end (caret.mjs)
807
+ if (annotate?.key(event)) return; // Ctrl+Alt+M: what is selected or the caret's block annotated, or the marked words' card
808
+ if (event.target?.closest?.(".ge-pop") && event.target.localName === "input") return; // so does any field of a menu card (a list number's)
809
+ if (event.target?.closest?.(".notion-popup-layer")) return; // and the icon picker (emoji-picker/), which keeps its own
810
+ if (typedKey(event)) return;
811
+ const mod = event.ctrlKey || event.metaKey;
812
+ if ((event.key !== "ArrowUp" && event.key !== "ArrowDown") || mod || event.altKey || event.shiftKey) endArrowRun();
813
+ const key = event.key.toLowerCase();
814
+ if (mod && !event.altKey && !event.shiftKey && key === "s") {
815
+ event.preventDefault();
816
+ saveNow();
817
+ return;
818
+ }
819
+ if (pageAt(event.target)?.key?.(event)) return; // F2 on a tab, Enter or Esc in a tab being renamed
820
+ // The outline keeps its own keys (outline/list.mjs); undo and redo work there as on the page itself.
821
+ if (outline?.holds(event.target)) {
822
+ if (mod && !event.altKey && (key === "z" || key === "y")) {
823
+ event.preventDefault();
824
+ key === "y" || event.shiftKey ? redo() : undo();
825
+ }
826
+ return;
827
+ }
828
+ const renamed = fieldOpen() === event.target;
829
+ // Undo and redo also work with the focus on the page itself, or where a component says.
830
+ if ((pageAt(event.target)?.undoHere?.(event.target) || event.target === document.body) && mod && !event.altKey && (key === "z" || key === "y")) {
831
+ event.preventDefault();
832
+ key === "y" || event.shiftKey ? redo() : undo();
833
+ return;
834
+ }
835
+ const host = hostOf(event);
836
+ // A formula block has no text the keys go into, its TeX changing in its editor alone (notion-lite's
837
+ // has no text area, its keys those of a selected block). With the caret on one (a right click puts it
838
+ // there), Enter opens its editor, as on it selected (blocks.mjs), and any other key that types or
839
+ // edits selects it, saying so, and does nothing more, the focus staying on the page: the next key is
840
+ // the selected block's (Backspace and Delete delete it), as Backspace selects a block above that holds
841
+ // no text before it deletes it (typing/).
842
+ if (texOnly(host) && editsText(event)) {
843
+ const enter = event.key === "Enter";
844
+ blocks.select(unitElement(host), { quiet: enter });
845
+ if (!enter || !blocks.onKeydown(event)) event.preventDefault();
846
+ return;
847
+ }
848
+ if (!host && !renamed && blocks.onKeydown(event)) return; // a selected block (keys wait while blocks are dragged)
849
+ // Ctrl+A: the block's text, then its container's blocks, one level a press, then the page; a tab
850
+ // being renamed keeps the browser's (its name).
851
+ if (!renamed && selectAll.key(event, host)) return;
852
+ if (!host || event.isComposing || event.keyCode === 229) return;
853
+ // A modifier pressed alone types nothing: the handle and a hint stay (Alt held over the handle's
854
+ // "+" adds above, handle/).
855
+ if (!MODIFIER_KEYS.has(event.key)) {
856
+ hideHint();
857
+ blocks.typing();
858
+ }
859
+ if (formatting.key(event)) return; // Esc hides the format bar, its keys format the selected text
860
+ // Backspace and Delete never stop at a pad by inline code (typing/code-edges.mjs); beside a
861
+ // component's entry, outside it, they take the whole entry away, one undo step.
862
+ if ((event.key === "Backspace" || event.key === "Delete") && !mod && !event.altKey && textFilters(host)) {
863
+ const before = ownHTML(host);
864
+ const removed = beforeDelete(host, event.key);
865
+ if (removed) {
866
+ event.preventDefault();
867
+ recordStep(host, before);
868
+ say(removed);
869
+ return;
870
+ }
871
+ }
872
+ const unit = event.key === "Escape" && !typing.escape(host) ? unitElement(host) : null;
873
+ if (unit) {
874
+ event.preventDefault(); // Esc in text selects the block around it, where no component keeps it
875
+ blocks.select(unit);
876
+ } else if (mod && !event.altKey && (key === "z" || key === "y")) {
877
+ event.preventDefault();
878
+ key === "y" || event.shiftKey ? redo() : undo();
879
+ } else if (mod && !event.altKey && key === "k") {
880
+ event.preventDefault();
881
+ openLink(host);
882
+ } else if (event.key === "Tab" && !mod && !event.altKey) {
883
+ if (typing.tab(host, event.shiftKey)) event.preventDefault();
884
+ else tabFrom(host, event.shiftKey); // the browser's, which passes over a formula block (passTab)
885
+ } else if (event.key === "Enter" && mod && !event.altKey && !event.shiftKey && typing.commandEnter(host)) {
886
+ event.preventDefault();
887
+ } else if (event.key === "Enter" && !event.shiftKey) {
888
+ event.preventDefault();
889
+ typing.split(host);
890
+ } else if (event.key === "Enter") {
891
+ if (typing.shiftEnter(host)) event.preventDefault();
892
+ } else if (event.key === "Backspace" && !mod && !event.altKey && atEdge(host, false)) {
893
+ if (typing.backspace(host)) event.preventDefault();
894
+ } else if (event.key === "Delete" && !mod && !event.altKey && atEdge(host, true)) {
895
+ if (typing.forwardDelete(host)) event.preventDefault();
896
+ } else if (mod && event.shiftKey && !event.altKey && (event.key === "ArrowUp" || event.key === "ArrowDown") && !renamed) {
897
+ event.preventDefault();
898
+ moveHostBlock(host, event.key === "ArrowUp" ? -1 : 1);
899
+ } else if (event.key.startsWith("Arrow") && !mod && !event.altKey && !event.shiftKey && !renamed) {
900
+ // ←/→ stop on both sides of inline code's edges (typing/code-edges.mjs); a tab's caret stays in its name.
901
+ if ((textFilters(host) && codeArrow(host, event.key)) || arrow(host, event.key)) event.preventDefault();
902
+ }
903
+ }
904
+
905
+ // The browser's Tab or Shift+Tab from a text block while it moves the focus (tabFrom): the block, which
906
+ // way, and where the caret was in it; null once the focus has moved, or left the page.
907
+ let tabbing = null;
908
+ function tabFrom(host, back) {
909
+ const selection = getSelection();
910
+ tabbing = { host, back, range: selection.rangeCount ? selection.getRangeAt(0).cloneRange() : null };
911
+ setTimeout(() => { tabbing = null; });
912
+ }
913
+
914
+ // A formula block is no stop of the browser's Tab, having no text to type in, as the arrow keys pass
915
+ // over it (caret.mjs places): the focus Tab puts on one goes on to the text block beyond it that way,
916
+ // the caret at its start, or, with none there, back to where it was.
917
+ function passTab(event) {
918
+ const tab = tabbing;
919
+ tabbing = null;
920
+ const formula = tab ? editable(event.target) : null;
921
+ if (!texOnly(formula)) return;
922
+ const beyond = textNear(formula, tab.back ? -1 : 1);
923
+ if (beyond) return void caretAt(beyond, "start");
924
+ if (!tab.host.isConnected || !tab.range) return;
925
+ tab.host.focus({ preventScroll: true });
926
+ select(tab.range);
927
+ }
928
+
929
+ function onBeforeInput(event) {
930
+ if (!editing) return;
931
+ endArrowRun();
932
+ const host = hostOf(event);
933
+ if (!host) return;
934
+ const type = event.inputType;
935
+ if (type === "historyUndo" || type === "historyRedo") {
936
+ event.preventDefault();
937
+ // Chrome has one undo stack for a page: past what a field of a component or a 批注's card can undo,
938
+ // its Ctrl+Z lands on the last text block typed in, and with the focus there it is no undo of the page's.
939
+ if (keptByComponent(document.activeElement) || annotate?.holds(document.activeElement)) return;
940
+ type === "historyUndo" ? undo() : redo();
941
+ } else if (type === "insertParagraph" && !event.isComposing) {
942
+ event.preventDefault();
943
+ typing.split(host);
944
+ } else if (type === "insertLineBreak" && typedAsIs(host, "\n")) {
945
+ event.preventDefault();
946
+ } else if (type === "insertLineBreak" && can(host, "plain")) {
947
+ event.preventDefault();
948
+ say(limitText(host, "split"));
949
+ } else if (type.startsWith("format")) {
950
+ event.preventDefault(); // formatting goes through format-bar/: the kit's markup only
951
+ } else if (type === "insertFromDrop" || type === "deleteByDrag") {
952
+ event.preventDefault(); // no drag-moves between blocks
953
+ }
954
+ }
955
+
956
+ // Whether what is typed in `host` is a menu's to look for.
957
+ const typedFor = (host) => slash.host === host || atMenu.host === host;
958
+
959
+ function onInput(event) {
960
+ compositionOver(event);
961
+ const host = editing && hostOf(event);
962
+ if (!host) return;
963
+ if (event.isComposing) {
964
+ if (slash.host === host) slash.update(); // the "/" menu filters on the pinyin being typed too (its letters)
965
+ if (atMenu.host === host) atMenu.update(); // and so does the "@" menu
966
+ return;
967
+ }
968
+ settlePads();
969
+ // What is typed after a "/" or an "@" while its menu is open is the menu's (what it looks for), no
970
+ // words: no space goes beside it, to stay in the text once the menu has put something there.
971
+ if ((event.inputType === "insertText" || event.inputType === "insertCompositionText") && !typedFor(host)) {
972
+ formatting.typed(host, event.data); // the formats the caret kept (Ctrl+B with nothing selected)
973
+ typing.autoSpace(host, event.data);
974
+ }
975
+ // What stood between two pieces of inline code deleted: they are one (marks.mjs joinCode).
976
+ if (event.inputType.startsWith("delete") && textFilters(host)) joinCode(host);
977
+ pageOf(host)?.typed?.(host);
978
+ recordText(host);
979
+ markdown.typed(host, event.data);
980
+ typing.updatePlaceholder(host);
981
+ if (event.inputType === "insertText" || event.inputType === "insertCompositionText") showCaret(); // what an input method put in without composing ("、")
982
+ }
983
+
984
+ function onCompositionEnd(event) {
985
+ composing = false;
986
+ settlePads();
987
+ const host = editing && hostOf(event);
988
+ if (host) {
989
+ if (!typedFor(host)) {
990
+ formatting.typed(host, event.data);
991
+ typing.autoSpace(host, event.data); // a menu's query: no space beside it (onInput)
992
+ }
993
+ pageOf(host)?.typed?.(host);
994
+ // Typed text as a key's is: committed after the reader's pause, not at once. Each phrase put in
995
+ // was once a commit of its own (a file written and a version announced per phrase, about 100ms
996
+ // of the server's and 200–400ms of the page's each); the commit still waits out a composition
997
+ // under way (autosave.mjs blocked()), and the crash buffer still takes the first change at once.
998
+ recordText(host);
999
+ markdown.typed(host, event.data);
1000
+ typing.updatePlaceholder(host);
1001
+ showCaret(); // the browser followed the composition, not the text put in for it (caret.mjs)
1002
+ }
1003
+ formatting.schedule();
1004
+ }
1005
+
1006
+ function onPointerDown(event) {
1007
+ compositionOver(event);
1008
+ // A press on the editor's own controls (the handle, its menu, the format bar) keeps the caret
1009
+ // where it is, so it moves no focus: in a workbench, where the focus may still be outside the
1010
+ // report's frame, the page takes it here, or the keys after it (Ctrl+Z) would go to the workbench.
1011
+ if (editing && !document.hasFocus()) window.focus();
1012
+ hideHint();
1013
+ endArrowRun();
1014
+ if (event.button === 0) pressing = true;
1015
+ // What the press came down on may take it, by its component's `press`: code opens for editing, a
1016
+ // display formula goes into the formula editor, and no caret goes in.
1017
+ if (editing && event.button === 0 && pageAt(event.target)?.press?.(event)) return;
1018
+ if (!editing || annotate?.holds(event.target) || formatting.holds(event.target) || linkHolds(event.target) || formulaBoxNode()?.contains(event.target) || panelOpen()?.holds(event.target) || slash.contains(event.target) || atMenu.contains(event.target)) return;
1019
+ if (event.target.closest?.('[data-ge-flag="conflict"]')) conflicts?.show(event.target);
1020
+ closeTyped();
1021
+ formatting.pressed(event.button === 0, keptByComponent(event.target));
1022
+ if (messageKind() === "note" && editable(event.target)) settle(); // an old note never outlives a click into the text
1023
+ if (linkBoxNode()) dismissLink(); // a press elsewhere keeps what the link card says
1024
+ keepPanels();
1025
+ const reason = readOnlyReason(event.target);
1026
+ if (reason) say(reason, "hint"); // what its hover hint says (hints/)
1027
+ }
1028
+
1029
+ // ---------- the block under the caret ----------
1030
+
1031
+ function onSelectionChange() {
1032
+ if (!editing) return;
1033
+ formatting.schedule();
1034
+ if (!composing) formatting.moved();
1035
+ const selection = getSelection();
1036
+ const host = selection.rangeCount ? editable(selection.anchorNode) : null;
1037
+ // A text block no step named, one a block made on the page came with: what the caret finds there is
1038
+ // what the first change of it undoes to, as a change is only seen once made.
1039
+ if (host && !lastKnown.has(host)) lastKnown.set(host, ownHTML(host));
1040
+ if (slash.host && host !== slash.host) slash.close();
1041
+ else if (slash.host) slash.update(); // the caret before the "/": it closes
1042
+ if (atMenu.host && host !== atMenu.host) atMenu.close();
1043
+ else atMenu.moved();
1044
+ // A caret after the lists a list item holds (a click below them) goes to the end of its text.
1045
+ if (host && selection.isCollapsed && selection.anchorNode === host && selection.anchorOffset > textEnd(host)) {
1046
+ caretAt(host, "end");
1047
+ return;
1048
+ }
1049
+ settlePads(true);
1050
+ if (host && !pressing && outOfAtom()) return; // never a caret inside a formula (a click settles on pointerup)
1051
+ if (host && !pressing && textFilters(host)) settleCodeEdge(host); // a click settles on pointerup
1052
+ typing.updatePlaceholder(host);
1053
+ }
1054
+
1055
+ // ---------- moving the block under the caret, Enter on a selected block ----------
1056
+
1057
+ // Ctrl+Shift+↑/↓ in text moves the block around it; the caret stays where it was.
1058
+ function moveHostBlock(host, delta) {
1059
+ const unit = unitElement(host);
1060
+ if (!unit) return;
1061
+ const selection = getSelection();
1062
+ const offsets = selection.rangeCount ? rangeOffsets(host, selection.getRangeAt(0)) : null;
1063
+ if (!blocks.moveBy(unit, delta, { keepFocus: true })) return;
1064
+ host.focus({ preventScroll: true });
1065
+ if (offsets) select(rangeAt(host, offsets));
1066
+ }
1067
+
1068
+ // Enter on a selected block (the first of those selected, when it is text: blocks.mjs): what the
1069
+ // page object it is the block of does with it (notion-lite's `activate`: a formula block, which has
1070
+ // no text, its editor open), each testing the element itself, else the caret at the end of its first
1071
+ // editable text, as notion-lite's (block-keys.js: the end of the block's text).
1072
+ function editBlock(unit) {
1073
+ if (pageObjects().some((page) => page.activate?.(unit))) return;
1074
+ const host = isHost(unit) ? unit : firstHost(unit);
1075
+ if (host) caretAt(host, "end");
1076
+ else say(t("这一块里没有可以直接修改的文字。"));
1077
+ }
1078
+
1079
+ // ---------- the parts, and a workbench around the page ----------
1080
+
1081
+ // How many of what is held the file cannot hold (conflicts.mjs unfit), no conflicts with a newer version.
1082
+ const unfitCount = () => [...(commits?.issues.values() ?? [])].filter(unfit).length;
1083
+
1084
+ // Ctrl+S: whatever waits goes now.
1085
+ async function saveNow() {
1086
+ checkAll();
1087
+ await annotate?.flush();
1088
+ const outcome = await autosave?.flush();
1089
+ const { held, drafts } = view();
1090
+ const unfitHeld = unfitCount();
1091
+ if (outcome === "saved" || outcome === "nothing") say(unfitHeld === 0 && held > 0 ? t("其余修改已保存;红框处的冲突还没有保存。") : held > unfitHeld ? t("其余修改已保存;红框处的内容还没有保存。") : held > 0 ? t("其余修改已保存;红框处的内容文档存不下,没有保存。") : drafts > 0 ? t("其余修改已保存;还没写完的块(红框或空标题)没有保存。") : t("已保存。"));
1092
+ else if (outcome === "offline") say(t("{0}:修改暂存在浏览器里,连上后自动保存。", OFFLINE), "error");
1093
+ return outcome;
1094
+ }
1095
+
1096
+ // The last keystrokes, counted now (not after the pause): what is typed for a menu is text now.
1097
+ function checkNow() {
1098
+ if (!autosave) return;
1099
+ closeTyped();
1100
+ checkPending = true;
1101
+ checkChanges();
1102
+ }
1103
+
1104
+ // All the page holds, as it is saved or left: the last keystrokes, and what is in a formula's box or a component's editor.
1105
+ function checkAll() {
1106
+ keepPanels();
1107
+ checkNow();
1108
+ }
1109
+
1110
+ // The report built from the saved source, once whatever waits is saved.
1111
+ async function exportNow() {
1112
+ await saveNow();
1113
+ return exportReport(MAP);
1114
+ }
1115
+
1116
+ function post(route, body) {
1117
+ return postAnswer(route, MAP.token, body);
1118
+ }
1119
+
1120
+ let workbench = null; // the session with a workbench embedding the page (session.mjs), if any
1121
+ let released = false; // the workbench closes the page: what does not get saved in time stays in the crash buffer
1122
+
1123
+ // A new page under this one (a page block's box: links/), the page staying as it is: the sidebar
1124
+ // hears of it; as the workbench's 新建 makes one, its title typed. Throws what stopped it.
1125
+ async function newSubpage(title) {
1126
+ const answer = await post("/__gallery/workspace/create", { title, description: t("新文档"), parent: MAP.slug, language });
1127
+ if (!answer.ok) throw new Error(failure(answer, t("没能新建页面")));
1128
+ workbench?.workspace();
1129
+ return answer.report;
1130
+ }
1131
+
1132
+ // The report's title as the reader sees it now (while editing, the title block's text).
1133
+ const currentTitle = () => (document.querySelector('[data-ge-kind="title"]') ?? document.querySelector("h1.doc-title, .doc-header h1"))?.textContent.replace(/\s+/g, " ").trim() ?? "";
1134
+
1135
+ // This page, as the list of pages takes it (links/pages.mjs).
1136
+ const thisPage = () => ({ slug: MAP.slug, title: currentTitle() });
1137
+
1138
+ // What the page tells the workbench of its state (session.mjs).
1139
+ const sessionState = () => {
1140
+ const state = view();
1141
+ return { editing, pending: state.waiting > 0 || state.syncing, saving: state.syncing, offline: state.offline, conflicts: state.held, unfit: unfitCount(), changed: state.foreign };
1142
+ };
1143
+
1144
+ // What the workbench can ask of the page (session.mjs).
1145
+ const COMMANDS = {
1146
+ // As Ctrl+S.
1147
+ async save() {
1148
+ const outcome = await saveNow();
1149
+ return { ok: outcome === "saved" || outcome === "nothing", outcome };
1150
+ },
1151
+ // The page may always be closed: it saves what waits first (for a moment), and what does not get
1152
+ // saved in time stays in the crash buffer, for the next page of this report to send.
1153
+ async "prepare-close"() {
1154
+ checkAll();
1155
+ annotate?.flush();
1156
+ await Promise.race([autosave?.flush(), new Promise((resolve) => setTimeout(resolve, 1000))]);
1157
+ autosave?.store();
1158
+ released = true;
1159
+ return { ok: true, canClose: true };
1160
+ },
1161
+ // The report built from its saved source (once whatever waits is saved), or with `copy` the view
1162
+ // copy with the reading state (the kit's "导出当前视图副本").
1163
+ async export({ copy = false }) {
1164
+ if (copy) {
1165
+ const control = document.querySelector("[data-doc-export-copy]");
1166
+ if (!control) return { ok: false, reason: "unavailable" };
1167
+ if (control.disabled) return { ok: false, reason: "busy" };
1168
+ const done = new Promise((resolve) => addEventListener("doc-export-copy-done", (event) => resolve(event.detail), { once: true }));
1169
+ control.click();
1170
+ return done;
1171
+ }
1172
+ return exportNow();
1173
+ },
1174
+ // 搜索文档 opened the report at a hit: its words scrolled into view and marked for a moment (find/).
1175
+ async reveal(message) {
1176
+ return revealText(message, (node) => modalOps().show(node));
1177
+ },
1178
+ // A link or an address opened the report at a block.
1179
+ async "reveal-block"({ block }) {
1180
+ return revealBlock(block);
1181
+ },
1182
+ // The way back or forth to a place a link to a block of the page left (jump).
1183
+ async "show-place"({ place }) {
1184
+ return showPlace(place);
1185
+ },
1186
+ // 批注 (annotate/): what there is read and drawn ({ refresh }), and the workbench's list, 复制批注, 全部删除 and 撤销.
1187
+ async annotate(message) {
1188
+ if (!editing || !annotate) return { ok: false, reason: "editing" };
1189
+ return annotate.command(message);
1190
+ },
1191
+ async annotations(message) {
1192
+ if (!editing || !annotate) return { ok: false, reason: "editing" };
1193
+ return annotate.action(message);
1194
+ },
1195
+ // Rename the report: its title, selected for typing (saving it updates <title> and report.json).
1196
+ async "edit-title"() {
1197
+ const title = document.querySelector('[data-ge-kind="title"]');
1198
+ if (!editing || !title || !isHost(title)) return { ok: false, reason: "no-title" };
1199
+ title.focus({ preventScroll: true });
1200
+ const range = document.createRange();
1201
+ range.selectNodeContents(title);
1202
+ select(range);
1203
+ title.scrollIntoView({ block: "nearest" });
1204
+ return { ok: true };
1205
+ },
1206
+ // Rename the report from outside the page (the workbench's sidebar): the title block takes the new
1207
+ // text as a step of its own, undone like one and saved at once (with <title> and report.json).
1208
+ async "set-title"({ title: text }) {
1209
+ const title = document.querySelector('[data-ge-kind="title"]');
1210
+ if (!editing || !title || !isHost(title)) return { ok: false, reason: "no-title" };
1211
+ const name = typeof text === "string" ? text.replace(/\s+/g, " ").trim() : "";
1212
+ if (!name) return { ok: false, reason: "title" };
1213
+ if (title.textContent === name) return { ok: true };
1214
+ const before = ownHTML(title);
1215
+ title.textContent = name;
1216
+ recordStep(title, before);
1217
+ typing.updatePlaceholder(title);
1218
+ return { ok: true };
1219
+ },
1220
+ };
1221
+
1222
+ // ---------- links to blocks and documents ----------
1223
+
1224
+ // The block of this id, once the page is drawn (what its charts move is moved by then), or an element
1225
+ // of this id (an address may name a heading's): what hides it opened (the kit's DocKit.reveal: a
1226
+ // detail card that does not hold it closed, a disclosure, a tab, the card it is in), in the middle of
1227
+ // the window and selected, as notion-lite's revealBlock, and the keys are the page's. What was just
1228
+ // shown lays itself out in the frames after (a tab's tables) and a card closed gives the focus back
1229
+ // then, so it is brought there again before it is selected. A block that is gone is said, unless
1230
+ // `quiet`, the page staying as it is; #top with no such element is the top of the page, as for a browser.
1231
+ // A later step (a link, the way back or forth, an address) started past its waits leaves it there.
1232
+ async function revealBlock(id, { quiet = false } = {}) {
1233
+ const step = ++stepping;
1234
+ await reached("data-doc-drawn");
1235
+ if (step !== stepping) return { ok: false, reason: "moved" };
1236
+ const block = document.querySelector(`[data-block-id="${CSS.escape(id)}"]`) ?? document.getElementById(id);
1237
+ if (!block) {
1238
+ if (id === "top") scrollTo(0, 0);
1239
+ else if (!quiet) say(t("这个块已经不在了。"));
1240
+ return { ok: false, reason: "missing" };
1241
+ }
1242
+ globalThis.DocKit.reveal(block);
1243
+ block.scrollIntoView({ block: "center" });
1244
+ await painted();
1245
+ await painted();
1246
+ if (step !== stepping) return { ok: false, reason: "moved" };
1247
+ block.scrollIntoView({ block: "center" });
1248
+ if (block.hasAttribute("data-block-id")) blocks.select(block, { quiet: true });
1249
+ window.focus();
1250
+ return { ok: true };
1251
+ }
1252
+
1253
+ let stepping = 0; // the newest step to a block or a place (revealBlock, showPlace): one a later step started past does nothing more
1254
+
1255
+ // A link to a block of this page (links/, goTo), a step of the history as a browser's jump to an
1256
+ // anchor is, so that the way back comes to where the reader was: in a workbench the workbench's
1257
+ // (`jump`, its address staying the page's), a page alone in a tab its own, the address as it is
1258
+ // (`docPlace` kept in the entry left, `docBlock` the one made, as the kit's for a reader); a page
1259
+ // opened in a frame on its own (?solo) makes none, nor a link to a block that is not there, nor one to
1260
+ // the block the page has just gone to (its entry the one made), as the browser makes none for the
1261
+ // address it is at.
1262
+ function jumpTo(id) {
1263
+ const there = Boolean(document.querySelector(`[data-block-id="${CSS.escape(id)}"]`) ?? document.getElementById(id) ?? (id === "top" || null));
1264
+ if (there && !SOLO) {
1265
+ const place = { ...viewNow(), ...placeIn(window) };
1266
+ if (workbench) workbench.jump({ block: id, place });
1267
+ else if (window.parent === window && window.history.state?.docBlock !== id) { // the browser's history: `history` is the page's undo
1268
+ const state = window.history.state;
1269
+ try {
1270
+ window.history.replaceState({ ...(state && typeof state === "object" ? state : {}), docPlace: place }, "");
1271
+ window.history.pushState({ docBlock: id }, "");
1272
+ } catch { /* no history to write */ }
1273
+ }
1274
+ }
1275
+ return revealBlock(id);
1276
+ }
1277
+
1278
+ // Back where a jump to a block left the reader (the way back, `show-place` in a workbench): the
1279
+ // detail cards that were open open again and those the jump opened close, the block that stood at the
1280
+ // top of the window stands there again (what the jump opened above it moving the rest) and the caret
1281
+ // is back in the block it was in, else no block is selected (the jump selected its target).
1282
+ async function showPlace(place) {
1283
+ if (!place || typeof place !== "object") return { ok: false, reason: "place" };
1284
+ const step = ++stepping;
1285
+ await reached("data-doc-drawn");
1286
+ if (step !== stepping) return { ok: false, reason: "moved" };
1287
+ const cards = modalOps();
1288
+ const kept = Array.isArray(place.modals) ? place.modals : Array.isArray(place.cards) ? place.cards.filter((card) => typeof card?.id === "string") : []; // the kit's entry (runtime/reveal.js), a jump taken before the page edited
1289
+ const keep = new Set(kept.map((card) => card.id));
1290
+ while (cards.open().some((card) => !keep.has(card.id)) && cards.closeTop()) { /* the cards opened since */ }
1291
+ const open = new Set(cards.open().map((card) => card.id));
1292
+ cards.reopen(kept.filter((card) => !open.has(card.id)));
1293
+ blocks.clear();
1294
+ const unit = typeof place.unit === "string" ? document.querySelector(`[data-block-id="${CSS.escape(place.unit)}"]`) : null;
1295
+ const host = unit && (place.self ? unit : [...unit.querySelectorAll("[contenteditable='true']")].find(isHost));
1296
+ if (host && isHost(host) && Number.isInteger(place.offset)) {
1297
+ host.focus({ preventScroll: true });
1298
+ caretAtOffset(host, place.offset);
1299
+ }
1300
+ goBackTo(window, place);
1301
+ const put = [scrollX, scrollY];
1302
+ await painted();
1303
+ if (step !== stepping) return { ok: false, reason: "moved" };
1304
+ if (unmoved(window, place, put)) goBackTo(window, place); // what was laid out since, or a browser putting the entry's scroll position back (a page alone), moved it; the reader scrolling meanwhile did not
1305
+ window.focus();
1306
+ return { ok: true };
1307
+ }
1308
+
1309
+ // What an address's hash asks of a page alone in a tab, on load and as it changes (in a workbench the
1310
+ // workbench asks: reveal-block). One that changes while the page is open may be no block's, a report's
1311
+ // script keeping its state there, and what it finds nothing for is `quiet`.
1312
+ function followHash(options) {
1313
+ if (location.hash.length > 1) revealBlock(decoded(location.hash.slice(1)), options);
1314
+ }
1315
+
1316
+ // The id `element` has in the file, or is given now, by a commit that goes at once (commit.mjs idFor),
1317
+ // so that asking for it is what writes it; null for what cannot be named yet.
1318
+ function blockId(element) {
1319
+ const named = element.getAttribute("data-block-id");
1320
+ const id = commits?.idFor(element) ?? null;
1321
+ if (!named) markChanged(true);
1322
+ return id;
1323
+ }
1324
+
1325
+ // 复制块链接 (the block menu): the block's address, which opens it in a workbench from anywhere, by
1326
+ // the link the address bar shows for this page, the title it shows counted; in the click on the row,
1327
+ // the clipboard's own rule, so by the list held ready (links/pages.mjs).
1328
+ function copyBlockLink(unit) {
1329
+ const id = blockId(unit);
1330
+ if (id) copyAddress(fullAddress(location.origin, { slug: MAP.slug, block: id }, heldList().named), say);
1331
+ else say(t("这一块还没有保存,保存后才能复制它的链接。"));
1332
+ }
1333
+
1334
+ // Where a link leads when that is a document of the workspace (links/, shared/page-address.mjs): a
1335
+ // block of this one is gone to where it stands; another opens in the workbench around the page, which
1336
+ // has this page save what waits, as for a click in its sidebar, and reads a workbench's address
1337
+ // (`hash`) on its own list. A page alone in a tab goes there itself, once what waits is saved
1338
+ // (prepare-close), so that nothing holds it back on beforeunload, and to a workbench's address only
1339
+ // when the list holds it, as a page in a workbench stays where a link leads nowhere; a page opened on
1340
+ // its own in a frame (?solo: the 写作偏好 dialog's) opens it in a new tab, the dialog staying.
1341
+ async function goTo({ slug = null, block = null, hash = null }, href) {
1342
+ if (slug === MAP.slug) {
1343
+ if (block) jumpTo(block);
1344
+ } else if (SOLO) window.open(href, "_blank", "noopener");
1345
+ else if (window.parent !== window) workbench?.open(hash === null ? { target: slug, block } : { hash });
1346
+ else if (hash === null || (await leadsToPage(hash))) COMMANDS["prepare-close"]().then(() => location.assign(hash === null ? `/generated/${slug}.html${block ? `#${encodeURIComponent(block)}` : ""}` : href));
1347
+ }
1348
+
1349
+ function connectParts() {
1350
+ holdPages(thisPage);
1351
+ addEventListener("focus", () => holdPages(thisPage));
1352
+ connectLinks({
1353
+ say, recordStep, hideFormatBar: () => formatting.hide(), active: () => editing && !composing,
1354
+ served: (file) => mediaUrl("file", MAP.slug, file), // a link to a file of the folder: where the preview hands it out
1355
+ closing: () => menuOpen() || popupOpen() || Boolean(formulaBoxNode() || panelOpen()),
1356
+ slug: MAP.slug, own: thisPage, reveal: jumpTo, go: goTo,
1357
+ subpage: SOLO ? null : newSubpage, // 写作偏好 stands outside the tree of pages
1358
+ });
1359
+ connectViews({
1360
+ say, recordStep, struct, assetUrl, slug: MAP.slug, unfurl: readPage,
1361
+ blocksAt: (host, range, made, kinds) => clipboard.blocksAt(host, range, made, kinds),
1362
+ });
1363
+ connectFormula({ say, recordStep, active: () => editing && !composing });
1364
+ connectChipFills(() => editing);
1365
+ connectHints({ editing: () => editing });
1366
+ }
1367
+
1368
+ // Opening: first what this tab left unsaved when it reloaded or crashed (the crash buffer), then
1369
+ // editing, where the reader was when the page reloaded by itself; what pages that are gone left
1370
+ // comes after, once the open tabs have answered.
1371
+ // The kit holds the page's first paint until the report is set up (the kit's styles/base.css; ui.js sets
1372
+ // data-doc-ready on a frame after DOMContentLoaded, which may come after the editor starts).
1373
+ // Until then nothing on the page can take the focus, so a workbench hears the page is ready only
1374
+ // once it shows (else "edit-title" would select the title of a hidden page and keys would go
1375
+ // nowhere). The kit shows it after 1.2s whatever happens; so does this.
1376
+ // The kit also says when its drawing is done (data-doc-drawn: charts drawn, nothing left to move).
1377
+ function reached(attribute) {
1378
+ const root = document.documentElement;
1379
+ if (root.hasAttribute(attribute)) return Promise.resolve();
1380
+ return new Promise((resolve) => {
1381
+ const done = () => {
1382
+ observer.disconnect();
1383
+ clearTimeout(timer);
1384
+ resolve();
1385
+ };
1386
+ const observer = new MutationObserver(() => { if (root.hasAttribute(attribute)) done(); });
1387
+ observer.observe(root, { attributes: true, attributeFilter: [attribute] });
1388
+ const timer = setTimeout(done, 1300);
1389
+ });
1390
+ }
1391
+
1392
+ // A frame drawn (the outline shown before enter(): start), or 200ms gone by: a page put out of sight
1393
+ // meanwhile draws no frame, and its editor does not wait for one.
1394
+ function painted() {
1395
+ return new Promise((resolve) => {
1396
+ const timer = setTimeout(resolve, 200);
1397
+ requestAnimationFrame(() => setTimeout(() => {
1398
+ clearTimeout(timer);
1399
+ resolve();
1400
+ }, 0));
1401
+ });
1402
+ }
1403
+
1404
+ // In a workbench, showing the report again is the workbench's: this page stops (it commits and
1405
+ // stores nothing more) and asks; the workbench swaps in a page it loaded out of sight meanwhile, so
1406
+ // the reader never sees a reload (session.mjs reload). The preview's live reload asks the same way
1407
+ // when the page does not edit (src/server/routes.mjs).
1408
+ function handOff() {
1409
+ window.__galleryEditReload = () => reloadView(MAP.slug);
1410
+ keepWith(() => ({ modals: modalOps().kept() }));
1411
+ if (!workbench) return;
1412
+ setReloader(() => {
1413
+ autosave?.close();
1414
+ workbench.reload();
1415
+ });
1416
+ }
1417
+
1418
+ // The workbench around the page, if any: it hears the page's state and notices, and asks it to save,
1419
+ // close and show the report again.
1420
+ function connect() {
1421
+ workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: currentTitle(), icon: pageIcon(), state: sessionState(), annotations: annotate?.count() }), commands: COMMANDS, modals: modalOps() });
1422
+ relayTo(workbench ? workbench.notice : null, workbench ? workbench.noticeRoom : null); // the workbench shows the page's notices, above the page's bottom panels
1423
+ handOff();
1424
+ // A page on its own in a frame (?solo: the 写作偏好 dialog's, which no session asks to save) is saved
1425
+ // by the frame's owner through this, quietly (the dialog's 保存, which writes its file once the page
1426
+ // has saved): what waits goes now; the outcome as Ctrl+S's.
1427
+ window.__galleryEditFlush = async () => {
1428
+ checkAll();
1429
+ return (await autosave?.flush()) ?? "nothing";
1430
+ };
1431
+ }
1432
+
1433
+ async function start() {
1434
+ hideBlocks(MAP.hidden);
1435
+ const view = takeView(MAP.slug);
1436
+ const own = await recover({ map: MAP, storage: local, keys: [pendingKey(MAP.slug, TAB)] }); // what this tab left unsaved, put back first
1437
+ const notice = own.outdated ? outdatedNotice() : view?.notice; // what a component wrote by a manifest that changed, given up
1438
+ const note = (words) => say(words, "note", 6000);
1439
+ if (own.sent) return reloadView(MAP.slug, view, own.outdated ? notice : null); // show what was put back
1440
+ // The outline (目录) comes with the page, before its blocks are made editable: it reads the headings
1441
+ // by their units' kinds, which enter() marks (captureLayout), marked here first from the map (the same
1442
+ // marks); drawn at once while the page's layout is still clean (after enter() its first reading of
1443
+ // the window's width and the headings' places laid the whole page out: 24-128 ms), and shown in a
1444
+ // frame of its own before enter() holds the main thread (100 ms to 1.5 s on a long report). What
1445
+ // enter() changes in the headings its observer sees: it reads them again on the next frame. Where the
1446
+ // reader was (a page shown again) is scrolled to first, so that frame has the page and the outline's
1447
+ // current row there (restoreView scrolls there again once the page edits).
1448
+ if (!SOLO) {
1449
+ if (view) scrollTo(view.x, view.y);
1450
+ markKinds(document, MAP);
1451
+ outline = createOutline({ slug: MAP.slug });
1452
+ if (outline.shown && !document.hidden) await painted();
1453
+ }
1454
+ enter();
1455
+ const restored = view ? restoreView(view) : null;
1456
+ if (notice) note(notice);
1457
+ checkLeft(local, MAP.slug, commits.texts()); // words a page it recovered showed that this one does not
1458
+ showLost(); // what could not be put back, at every opening until the reader is done with it
1459
+ if (own.offline) say(t("有上次没保存的修改,但现在连不上预览服务器;连上后刷新页面即可放回。"), "error");
1460
+ else if (own.left) say(t("有上次没保存的修改还没能放回(文件正在被修改,或预览服务器出错);稍后刷新页面会再试。"), "error");
1461
+ await reached("data-doc-ready");
1462
+ await restored; // ready (a workbench may show it now) once where the reader was
1463
+ connect();
1464
+ if (!view) followHash(); // a page shown again stays where the reader was
1465
+ addEventListener("hashchange", () => followHash({ quiet: true }));
1466
+ // The way back or forth over a page alone's jumps (jumpTo), its address the same all along.
1467
+ addEventListener("popstate", (event) => {
1468
+ const state = event.state;
1469
+ if (workbench || SOLO || !state || typeof state !== "object") return;
1470
+ if (state.docPlace && typeof state.docPlace === "object") showPlace(state.docPlace);
1471
+ else if (typeof state.docBlock === "string") revealBlock(state.docBlock, { quiet: true });
1472
+ });
1473
+ annotate?.start();
1474
+ const open = await openTabs();
1475
+ if (open.has(TAB)) { // a duplicated tab: its own id from now on
1476
+ TAB = newTab();
1477
+ session.set(keys.tab, TAB);
1478
+ }
1479
+ const prefix = pendingKey(MAP.slug, "");
1480
+ const left = local.keys().filter((key) => key.startsWith(prefix) && key !== pendingKey(MAP.slug, TAB) && !open.has(key.slice(prefix.length)));
1481
+ if (left.length === 0) return;
1482
+ const others = await recover({ map: MAP, storage: local, keys: left });
1483
+ if (others.outdated) note(outdatedNotice());
1484
+ if (others.sent) autosave.heard(null, true); // shown once nothing waits here (checked then)
1485
+ else checkLeft(local, MAP.slug, commits.texts());
1486
+ if (readLost(local, MAP.slug).length > 0) showLost();
1487
+ }
1488
+
1489
+ // Back where the reader was: the scroll position, the detail cards open and the caret in the block
1490
+ // it was in. Done once the kit has drawn (its charts, which may have moved things) and the scroll
1491
+ // position is put again. Where the page before this one did not hold the focus (`data-no-focus`, the
1492
+ // workbench's: a dialog stands open over it, a field holds it) what holds it keeps it, and the keys;
1493
+ // the caret comes back when the focus does, by anything but a press, which puts it where it lands.
1494
+ function restoreView({ x, y, top = null, unit, self, offset, modals = [] }) {
1495
+ const put = () => (top ? goBackTo(window, { x, y, top }) : scrollTo(x, y)); // the way back from another document: by the place the workbench kept (reading-place.mjs)
1496
+ put();
1497
+ const outside = window.frameElement?.hasAttribute("data-no-focus") ? window.frameElement.ownerDocument.activeElement : null;
1498
+ modalOps().reopen(modals);
1499
+ outside?.focus();
1500
+ const block = unit ? document.querySelector(`[data-block-id="${CSS.escape(unit)}"]`) : null;
1501
+ const host = block && (self ? block : [...block.querySelectorAll("[contenteditable='true']")].find(isHost));
1502
+ if (host && isHost(host) && offset !== null) {
1503
+ const caretBack = () => {
1504
+ host.focus({ preventScroll: true });
1505
+ caretAtOffset(host, offset);
1506
+ };
1507
+ if (outside) addEventListener("focus", () => { if (!pressing && host.isConnected && document.activeElement === document.body) caretBack(); }, { once: true });
1508
+ else caretBack();
1509
+ }
1510
+ return reached("data-doc-drawn").then(put); // charts drawn since may have moved things
1511
+ }
1512
+
1513
+ // What pages of this report could not put back into the file (recover: lostKey), the reader's
1514
+ // words, listed at every opening until the reader says they are dealt with; closing the panel only
1515
+ // hides it.
1516
+ const LOST = {
1517
+ changed: t("文件里的这一段已被修改,你的版本如下:"),
1518
+ missing: t("文件里的这一块已被删除,你的版本如下:"),
1519
+ insert: t("新建的这一块放不进文件(周围的块已被改动或删除):"),
1520
+ draft: t("还没写完、没能保存的块:"),
1521
+ reverted: t("被 AI 的写入改回去的修改,你的版本如下:"),
1522
+ rebase: t("上次没等到保存结果的新块里,后来写的内容:"),
1523
+ refused: t("没能保存的修改:"),
1524
+ unreadable: t("读不出来的暂存内容:"),
1525
+ };
1526
+ function showLost() {
1527
+ const lost = readLost(local, MAP.slug);
1528
+ if (lost.length === 0) return;
1529
+ showPanel(t("{0} 处修改没能放回", lost.length));
1530
+ for (const item of lost) {
1531
+ // What a component wrote, where its block stands on the page: that block holding it, to copy and paste
1532
+ // back, as a conflict's card offers it; its parts' insides are no text to read.
1533
+ const instance = typeof item.unit === "string" && item.parts ? document.querySelector(`main [data-block-id="${CSS.escape(item.unit)}"]`) : null;
1534
+ const block = instance ? pageOfUnit(instance)?.withData?.(instance, item.parts) ?? null : null;
1535
+ if (block) {
1536
+ panelItem(
1537
+ element("p", { text: t("文件里的这一块已被修改:复制你的版本,再粘贴到页面上。") }),
1538
+ element("div", { class: "ge-actions" }, button(t("复制这一块"), () => { if (!clipboard.copyBlocks([block])) say(t("没能完成,请再试一次。"), "error"); })),
1539
+ );
1540
+ continue;
1541
+ }
1542
+ const text = item.text ?? String(item.html ?? "").split(/\n/).map((line) => plainText(line)).filter(Boolean).join("\n");
1543
+ panelItem(
1544
+ element("p", { text: LOST[item.why] ?? LOST.refused }),
1545
+ element("div", { class: "ge-version", text }),
1546
+ element("div", { class: "ge-actions" }, button(t("复制文字"), () => navigator.clipboard?.writeText(text).then(() => say(t("已复制。"))))),
1547
+ );
1548
+ }
1549
+ panelItem(element("div", { class: "ge-actions" }, button(t("都处理好了,不再保留"), () => {
1550
+ local.remove(lostKey(MAP.slug));
1551
+ closePanel();
1552
+ }, { primary: true })));
1553
+ say(t("有 {0} 处修改没能放回文件,列在右下方:复制保存好再标记处理完。", lost.length), "error"); // said over the panel, which stands first
1554
+ }
1555
+
1556
+ function boot() {
1557
+ connectParts();
1558
+ addStyle(STYLE);
1559
+ formatting = createFormatting({
1560
+ active: () => editing && !composing, say, recordStep, struct, convert: convertHost, cross: crossText,
1561
+ turnIntoAll: groupTargets, convertAll: (hosts, type) => convertAll(hosts, type, { select: false }), selectBlocks: (list) => blocks.select(list, { quiet: true }),
1562
+ annotates: () => Boolean(annotate?.canSelection()), annotate: () => annotate?.annotateSelection(), // the bar's 批注
1563
+ });
1564
+ slash = createSlashMenu({
1565
+ convert: convertHost, insertBelow, inline: inlineInsert, composing: () => composing,
1566
+ // The "+"'s menu closed: its block's hint is its own again (where it still shows one).
1567
+ closed: (host) => { if (host.hasAttribute("data-ge-placeholder")) typing.updatePlaceholder(host); },
1568
+ });
1569
+ atMenu = createAtMenu({ composing: () => composing, page: thisPage, recordText, recordStep });
1570
+ markdown = createMarkdown({ slash, at: atMenu, convert: convertHost, recordStep });
1571
+ blocks = createBlocks({
1572
+ units, boxes, active, struct, look: lookStep, say, edit: editBlock, turnInto, convert: convertUnit, turnIntoAll: groupTargets, convertAll, add: insert.addAfter, after: insert.paragraphAfter,
1573
+ below: insertBelow, belowTypes: belowTargets,
1574
+ blank: insert.clickBlank, nest: (units, delta) => pageObjects().some((page) => page.nestBlocks?.(units, delta)),
1575
+ keepsHandle: (node) => tables?.holds(node) ?? false,
1576
+ addNear,
1577
+ // 批注 on blocks: the button beside one (annotate/).
1578
+ notes: { can: (units) => annotate?.canBlocks(units) ?? false, at: (unit) => annotate?.markerSpot(unit) ?? null, write: (units) => annotate?.annotateBlocks(units) },
1579
+ copyLink: SOLO ? null : copyBlockLink, // what ?solo shows (写作偏好) only its dialog opens: no address leads there
1580
+ linkAhead: () => holdPages(thisPage),
1581
+ });
1582
+ resize = createResize({ units, active, look: lookStep, say, owns: (node) => tables?.owns(node) ?? false });
1583
+ gaps = createColumnGaps({ active, struct, look: lookStep, say });
1584
+ for (const page of pageObjects()) page.install?.(); // the round "+" at a flowchart node's right, renaming Sheet tabs
1585
+ tables = createTableEditing({ active, struct, look: lookStep, selectBlock: (unit) => blocks.select(unit), resizeTable: (event, unit, axis) => resize.startTable(event, unit, axis), say, ownCells: (data) => clipboard.ownCells(data), typedKey });
1586
+ selectAll = createSelectAll({ blocks, say, cells: { table: () => tables.selectedTable, clear: () => tables.clear() } });
1587
+ createFrame(() => editing);
1588
+ createFlags(() => editing);
1589
+ // A page of its own in a frame (?solo) has no session, and so no 批注: nothing would show them.
1590
+ if (!SOLO) annotate = createAnnotate({
1591
+ slug: MAP.slug, token: MAP.token, isEditing: () => editing, say,
1592
+ picked: () => blocks?.picked ?? [], wholePage: () => topStep(document).units, crossTexts: () => crossText.texts(),
1593
+ unpick() {
1594
+ crossText.letGo();
1595
+ if (blocks?.picked.length > 0) blocks.clear();
1596
+ if (messageKind() === "note") settle();
1597
+ },
1598
+ // The blocks it is about are named with the next commit (commit.mjs idFor), at once.
1599
+ idFor(element) {
1600
+ const named = element.getAttribute("data-block-id");
1601
+ const id = commits?.idFor(element) ?? null;
1602
+ if (!named) markChanged(true); // a commit names it now
1603
+ return id;
1604
+ },
1605
+ changed: () => workbench?.update(),
1606
+ removed: (items) => workbench?.annotationsRemoved(items),
1607
+ });
1608
+ document.addEventListener("pointerup", (event) => {
1609
+ pressing = false;
1610
+ formatting.released();
1611
+ // A click at an edge of inline code: the caret on the side it was made on.
1612
+ const selection = getSelection();
1613
+ const host = editing && event.button === 0 && selection.rangeCount && selection.isCollapsed ? editable(selection.anchorNode) : null;
1614
+ if (host && outOfAtom(event.clientX)) return; // a click beside a formula that put the caret in it
1615
+ if (host && textFilters(host)) codeClick(host, event.clientX);
1616
+ }, true);
1617
+ document.addEventListener("focusin", passTab, true);
1618
+ document.addEventListener("compositionstart", (event) => {
1619
+ // On an empty formula block (an input method that sends no key before it): the block selected, as a
1620
+ // key there selects it (onKeydown), and the composition has no text to go into. On one drawn, whose
1621
+ // chip is all it holds, the browser begins none, and what it puts in is refused as anything typed
1622
+ // there is (components/math/page.mjs).
1623
+ const host = editing ? hostOf(event) : null;
1624
+ if (texOnly(host)) return void blocks.select(unitElement(host));
1625
+ composing = true;
1626
+ formatting.hide();
1627
+ atMenu.follow(); // its hint goes
1628
+ }, true);
1629
+ addEventListener("resize", () => {
1630
+ formatting.schedule();
1631
+ atMenu.follow();
1632
+ });
1633
+ // The workbench took the focus: no menu stays, holding commits back, where no press reaches it, and a
1634
+ // formula's box goes as for a press elsewhere. Another window taking it leaves the box, for what is copied there.
1635
+ addEventListener("blur", () => {
1636
+ closeTyped();
1637
+ if (window.frameElement?.ownerDocument.hasFocus()) keepPanels();
1638
+ });
1639
+ document.addEventListener("keydown", onKeydown, true);
1640
+ document.addEventListener("beforeinput", onBeforeInput, true);
1641
+ document.addEventListener("input", onInput, true);
1642
+ document.addEventListener("compositionend", onCompositionEnd, true);
1643
+ document.addEventListener("paste", (event) => {
1644
+ compositionOver(event);
1645
+ if (annotate?.holds(event.target) || keptByComponent(event.target)) return; // a 批注's card is a field of its own, and so is what a component keeps
1646
+ if (!atMenu.paste(event)) clipboard.paste(event);
1647
+ }, true);
1648
+ const copied = (event) => { if (!annotate?.holds(event.target) && !keptByComponent(event.target)) clipboard.copy(event); };
1649
+ document.addEventListener("copy", copied, true);
1650
+ document.addEventListener("cut", copied, true);
1651
+ document.addEventListener("selectionchange", onSelectionChange);
1652
+ document.addEventListener("mouseover", scheduleHint, true);
1653
+ document.documentElement.addEventListener("mouseleave", hideHint); // a pointer gone into a workbench moves nothing here
1654
+ document.addEventListener("mousemove", (event) => {
1655
+ trackPointer(event);
1656
+ }, { passive: true, capture: true });
1657
+ document.addEventListener("pointerdown", onPointerDown, true);
1658
+ // A press beside a line of text on its block's empty space puts the caret where that line is
1659
+ // nearest, where the browser would put it elsewhere (caret.mjs besideLine); one at an edge of
1660
+ // inline code, on the side of the edge it was made on at once (typing/code-edges.mjs codePress);
1661
+ // one in a block holding chips of the text, for a drag to go by whole chips (chipsPress). The press
1662
+ // is the editor's then, and so is a drag from it (followPress).
1663
+ document.addEventListener("mousedown", (event) => {
1664
+ const host = editing && event.button === 0 && !event.shiftKey && event.detail < 2 ? editable(event.target) : null;
1665
+ if (!host) return;
1666
+ const place = besideLine(host, event.clientX, event.clientY) ?? chipsPress(host, event.clientX, event.clientY);
1667
+ if (place) {
1668
+ event.preventDefault();
1669
+ host.focus({ preventScroll: true });
1670
+ selectAt(place, event.clientY);
1671
+ followPress(host, place);
1672
+ return;
1673
+ }
1674
+ const stop = textFilters(host) && codePress(host, event.clientX, event.clientY);
1675
+ if (!stop) return;
1676
+ event.preventDefault();
1677
+ followPress(host, stop);
1678
+ }, true);
1679
+ addEventListener("scroll", (event) => {
1680
+ hideHint();
1681
+ if (formatting.visible) formatting.schedule();
1682
+ if (slash.host && !slash.contains(event.target)) slash.update(); // it follows its block
1683
+ if (atMenu.host && !atMenu.contains(event.target)) atMenu.follow();
1684
+ }, { passive: true, capture: true });
1685
+ document.addEventListener("drop", (event) => { if (editing && editable(event.target)) event.preventDefault(); }, true);
1686
+ // A click goes first to the component of what it came down on: a code block's fold arrow or wrap
1687
+ // button (an edit), a disclosure's title being typed in, a tab being renamed.
1688
+ document.addEventListener("click", (event) => {
1689
+ if (editing && pageAt(event.target)?.click?.(event)) return;
1690
+ const link = editing ? linkOf(event.target) : null;
1691
+ if (link) followLink(event, link);
1692
+ }, true);
1693
+ // Keep the editor UI usable while a modal dialog is open.
1694
+ new MutationObserver(() => {
1695
+ const target = layer();
1696
+ for (const node of [noticeNode(), formatting.node, panelNode(), ...linkNodes(), formulaBoxNode(), panelOpen()?.panel, conflicts?.node(), ...slash.nodes, ...atMenu.nodes, ...blocks.nodes, ...resize.nodes, ...gaps.nodes, ...tables.nodes, ...pageObjects().flatMap((page) => page.nodes ?? [])]) if (node && node.parentNode !== target) target.append(node);
1697
+ }).observe(document.body, { subtree: true, attributes: true, attributeFilter: ["open"] });
1698
+ window.addEventListener("doc-source-changed", (event) => autosave?.heard(event.detail?.version ?? null));
1699
+ window.addEventListener("doc-assets-changed", () => say(t("页面的样式或编辑器已更新,刷新页面即可看到。"), "note", 6000));
1700
+ // A component package the page holds an instance of changed: what it runs of it is another page's.
1701
+ window.addEventListener("doc-components-changed", () => autosave?.reloadSoon());
1702
+ // What the reader's settings offer changed: the menus follow, and no page is shown again for it.
1703
+ window.addEventListener("doc-settings-changed", (event) => hideBlocks(event.detail.hidden));
1704
+ // Only while something waits: it is in the crash buffer too, so this is a courtesy.
1705
+ window.addEventListener("beforeunload", (event) => {
1706
+ const state = autosave?.state;
1707
+ if (state && (state.waiting > 0 || state.syncing) && !state.reloading && !released) event.preventDefault();
1708
+ });
1709
+ // Closing the page: keep what waits and try a last commit. Hiding it: commit now.
1710
+ window.addEventListener("pagehide", () => {
1711
+ checkAll();
1712
+ autosave?.leave();
1713
+ annotate?.leave();
1714
+ });
1715
+ document.addEventListener("visibilitychange", () => {
1716
+ if (!document.hidden || !autosave) return;
1717
+ checkNow();
1718
+ autosave.flush();
1719
+ });
1720
+ // Once the shared kit has rendered formulas.
1721
+ setTimeout(start, 0);
1722
+ }
1723
+
1724
+ if (document.readyState === "complete") boot();
1725
+ else window.addEventListener("load", boot, { once: true });