@heycys/gallery 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/THIRD-PARTY-NOTICES.md +1 -1
  2. package/browser/editor/components/columns/page.mjs +4 -4
  3. package/browser/editor/components/embed/index.mjs +2 -2
  4. package/browser/editor/components/flowchart/add.mjs +7 -6
  5. package/browser/editor/components/flowchart/page.mjs +1 -1
  6. package/browser/editor/components/icons.mjs +8 -0
  7. package/browser/editor/components/image/crop.mjs +6 -9
  8. package/browser/editor/components/installed/index.mjs +11 -4
  9. package/browser/editor/components/installed/page.mjs +27 -16
  10. package/browser/editor/components/installed/served.mjs +6 -5
  11. package/browser/editor/components/instances.mjs +18 -0
  12. package/browser/editor/components/list/page.mjs +3 -6
  13. package/browser/editor/components/registry.mjs +13 -4
  14. package/browser/editor/components/table/index.mjs +59 -2
  15. package/browser/editor/components/table/page.mjs +1 -1
  16. package/browser/editor/page/annotate/card.mjs +3 -6
  17. package/browser/editor/page/autosave.mjs +310 -37
  18. package/browser/editor/page/board/client.mjs +57 -0
  19. package/browser/editor/page/board/hint.mjs +145 -0
  20. package/browser/editor/page/board/history.mjs +119 -0
  21. package/browser/editor/page/board/index.mjs +970 -0
  22. package/browser/editor/page/board/settings.mjs +209 -0
  23. package/browser/editor/page/caret.mjs +1 -1
  24. package/browser/editor/page/changes/bar.mjs +6 -14
  25. package/browser/editor/page/changes/index.mjs +2 -2
  26. package/browser/editor/page/clipboard/import/html.mjs +29 -6
  27. package/browser/editor/page/clipboard/import/inline.mjs +32 -0
  28. package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
  29. package/browser/editor/page/clipboard/import/read.mjs +6 -1
  30. package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
  31. package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
  32. package/browser/editor/page/clipboard/index.mjs +124 -13
  33. package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
  34. package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
  35. package/browser/editor/page/clipboard/own/selection.mjs +1 -1
  36. package/browser/editor/page/columns/index.mjs +48 -46
  37. package/browser/editor/page/commit.mjs +80 -22
  38. package/browser/editor/page/conflicts.mjs +7 -7
  39. package/browser/editor/page/core.mjs +31 -15
  40. package/browser/editor/page/custom-page.mjs +106 -0
  41. package/browser/editor/page/editor.mjs +51 -14
  42. package/browser/editor/page/emoji-picker/styles.mjs +7 -12
  43. package/browser/editor/page/emoji-picker/upload.mjs +2 -2
  44. package/browser/editor/page/handle/index.mjs +5 -8
  45. package/browser/editor/page/history.mjs +24 -4
  46. package/browser/editor/page/links/index.mjs +5 -3
  47. package/browser/editor/page/links/pages.mjs +27 -9
  48. package/browser/editor/page/look.mjs +54 -3
  49. package/browser/editor/page/margin.mjs +18 -3
  50. package/browser/editor/page/media-menu/index.mjs +6 -7
  51. package/browser/editor/page/menu.mjs +18 -7
  52. package/browser/editor/page/notices.mjs +2 -2
  53. package/browser/editor/page/page-objects.mjs +13 -2
  54. package/browser/editor/page/panel.mjs +4 -9
  55. package/browser/editor/page/present-links.mjs +11 -2
  56. package/browser/editor/page/resize/index.mjs +42 -29
  57. package/browser/editor/page/served.mjs +14 -0
  58. package/browser/editor/page/session.mjs +48 -4
  59. package/browser/editor/page/slash-menu/index.mjs +3 -3
  60. package/browser/editor/page/table/cells.mjs +102 -46
  61. package/browser/editor/page/table/icons.mjs +2 -0
  62. package/browser/editor/page/table/index.mjs +29 -10
  63. package/browser/editor/page/table/pictures.mjs +299 -0
  64. package/browser/editor/page/table/views.mjs +150 -0
  65. package/browser/editor/page/text-select/index.mjs +26 -12
  66. package/browser/editor/page/todo-writes.mjs +75 -0
  67. package/browser/editor/page/typing/index.mjs +53 -3
  68. package/browser/editor/page/ui.mjs +13 -0
  69. package/browser/shared/block-packs.mjs +404 -0
  70. package/browser/shared/board-places.mjs +224 -0
  71. package/browser/shared/card-size.mjs +59 -0
  72. package/browser/shared/colors.mjs +3 -2
  73. package/browser/shared/component-catalog.mjs +323 -46
  74. package/browser/shared/component-source.mjs +37 -0
  75. package/browser/shared/components/index.mjs +12 -3
  76. package/browser/shared/components/installed.mjs +35 -3
  77. package/browser/shared/components/page.mjs +18 -8
  78. package/browser/shared/components/state.mjs +5 -1
  79. package/browser/shared/document-look.mjs +10 -5
  80. package/browser/shared/drafts.mjs +432 -0
  81. package/browser/shared/extension-sidebar.mjs +126 -0
  82. package/browser/shared/extensions.mjs +79 -0
  83. package/browser/shared/file-table.mjs +101 -0
  84. package/browser/shared/i18n/en.mjs +307 -27
  85. package/browser/shared/json-layout.mjs +47 -0
  86. package/browser/shared/net-cache.mjs +133 -0
  87. package/browser/shared/net-keys.mjs +32 -0
  88. package/browser/shared/net-policy.mjs +177 -0
  89. package/browser/shared/outline-shape.mjs +128 -0
  90. package/browser/shared/outline.mjs +331 -67
  91. package/browser/shared/pack-manifest.mjs +77 -0
  92. package/browser/shared/page-address.mjs +6 -3
  93. package/browser/shared/page-cards.mjs +308 -0
  94. package/browser/shared/page-links.mjs +69 -6
  95. package/browser/shared/pages.mjs +388 -0
  96. package/browser/shared/paths.mjs +6 -0
  97. package/browser/shared/preview-server.mjs +3 -7
  98. package/browser/shared/session.mjs +8 -1
  99. package/browser/shared/shipped-blocks.mjs +299 -0
  100. package/browser/shared/table-chart.mjs +33 -1
  101. package/browser/shared/todos.mjs +83 -0
  102. package/browser/shared/user-directory.mjs +24 -0
  103. package/browser/shared/versions.mjs +14 -0
  104. package/browser/shared/web-links.mjs +134 -0
  105. package/browser/shared/workspace-columns.mjs +120 -0
  106. package/browser/shared/workspace-name.mjs +154 -0
  107. package/browser/shared/workspace-query.mjs +125 -0
  108. package/browser/shared/workspace-registry.mjs +272 -0
  109. package/browser/shared/workspace.mjs +171 -20
  110. package/dist/chunk-2NRBY6AJ.mjs +2 -0
  111. package/dist/chunk-32ZUEFZ7.mjs +2 -0
  112. package/dist/chunk-35NQ4PLQ.mjs +2 -0
  113. package/dist/chunk-3KJHLHUO.mjs +2 -0
  114. package/dist/chunk-3NJACR4W.mjs +2 -0
  115. package/dist/chunk-4AV456RQ.mjs +2 -0
  116. package/dist/chunk-4OZWXZE5.mjs +5 -0
  117. package/dist/chunk-5XYHDBJ5.mjs +2 -0
  118. package/dist/chunk-5ZJA7CXT.mjs +2 -0
  119. package/dist/chunk-6A53MZYZ.mjs +2 -0
  120. package/dist/chunk-6EXH3ORZ.mjs +166 -0
  121. package/dist/chunk-6EZ7FNIK.mjs +3 -0
  122. package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
  123. package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
  124. package/dist/chunk-B4P2CM2J.mjs +3 -0
  125. package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
  126. package/dist/chunk-BVEFGE4B.mjs +2 -0
  127. package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
  128. package/dist/chunk-CRJEG7MT.mjs +2 -0
  129. package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
  130. package/dist/chunk-D4EURAX4.mjs +4 -0
  131. package/dist/chunk-DQCE7TJY.mjs +5 -0
  132. package/dist/chunk-DR73Y2UJ.mjs +3 -0
  133. package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
  134. package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
  135. package/dist/chunk-EURLTOTZ.mjs +4 -0
  136. package/dist/chunk-FJYN7RTH.mjs +4 -0
  137. package/dist/chunk-FWNRPMCJ.mjs +12 -0
  138. package/dist/chunk-FWW4CSAZ.mjs +2 -0
  139. package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
  140. package/dist/chunk-GY3FAUVM.mjs +2 -0
  141. package/dist/chunk-HLYV7RIH.mjs +2 -0
  142. package/dist/chunk-HMYTM3GT.mjs +15 -0
  143. package/dist/chunk-IA4PSIMD.mjs +2 -0
  144. package/dist/chunk-JEUK3ZO7.mjs +2 -0
  145. package/dist/chunk-KTKHRC6H.mjs +2 -0
  146. package/dist/chunk-L7UP52JO.mjs +2 -0
  147. package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
  148. package/dist/chunk-N76HWBA3.mjs +10 -0
  149. package/dist/chunk-N7AZZLGT.mjs +548 -0
  150. package/dist/chunk-NAMN3RAF.mjs +2 -0
  151. package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
  152. package/dist/chunk-NG375ILQ.mjs +2 -0
  153. package/dist/chunk-NVRNLK25.mjs +6 -0
  154. package/dist/chunk-NWFBE2YA.mjs +2 -0
  155. package/dist/chunk-NWR2LT5B.mjs +2 -0
  156. package/dist/chunk-OJKMINIU.mjs +20 -0
  157. package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
  158. package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
  159. package/dist/chunk-PAREWQGB.mjs +4 -0
  160. package/dist/chunk-POQSE4C2.mjs +2 -0
  161. package/dist/chunk-PUOQ65NC.mjs +2 -0
  162. package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
  163. package/dist/chunk-PZDLJJTJ.mjs +3 -0
  164. package/dist/chunk-Q47PLKIJ.mjs +15 -0
  165. package/dist/chunk-Q4UEPUWF.mjs +2 -0
  166. package/dist/chunk-QKXMPUXC.mjs +2 -0
  167. package/dist/chunk-QOUEFUCH.mjs +16 -0
  168. package/dist/chunk-RFDZXFJF.mjs +13 -0
  169. package/dist/chunk-RKPWLGZ3.mjs +4 -0
  170. package/dist/chunk-RMECZA2K.mjs +2 -0
  171. package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
  172. package/dist/chunk-SX4LJYT6.mjs +8 -0
  173. package/dist/chunk-SXHBYZST.mjs +16 -0
  174. package/dist/chunk-TANZRINB.mjs +2 -0
  175. package/dist/chunk-TEZAWDSQ.mjs +2 -0
  176. package/dist/chunk-UBBRTJ2R.mjs +24 -0
  177. package/dist/chunk-UFNYFFKO.mjs +20 -0
  178. package/dist/chunk-UGWYKHBL.mjs +170 -0
  179. package/dist/chunk-UKSHDZZ6.mjs +2 -0
  180. package/dist/chunk-UNOYVIYR.mjs +2 -0
  181. package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
  182. package/dist/chunk-VJPFVNNW.mjs +15 -0
  183. package/dist/chunk-VS3ZMVGX.mjs +5 -0
  184. package/dist/chunk-VTKNEYT3.mjs +6 -0
  185. package/dist/chunk-VXACTLO3.mjs +3 -0
  186. package/dist/chunk-YI43RG4P.mjs +2 -0
  187. package/dist/chunk-YQGT4AB2.mjs +6 -0
  188. package/dist/chunk-ZB4H7ZN4.mjs +2 -0
  189. package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
  190. package/dist/chunk-ZTHICSSF.mjs +2 -0
  191. package/dist/chunk-ZUNVAXLH.mjs +2 -0
  192. package/dist/chunk-ZWPMCK7P.mjs +2 -0
  193. package/dist/cli.mjs +1 -1
  194. package/dist/server.mjs +1 -1
  195. package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
  196. package/dist/worker.mjs +1 -1
  197. package/kit/board.css +95 -0
  198. package/kit/board.js +250 -0
  199. package/kit/charts.js +5 -71
  200. package/kit/code.js +14 -14
  201. package/kit/report.css +255 -17
  202. package/kit/runtime.js +1600 -125
  203. package/kit/state.js +10 -1
  204. package/kit/tables.js +128 -43
  205. package/kit/ui.js +77 -35
  206. package/package.json +2 -1
  207. package/packs/gallery/card/component.json +14 -0
  208. package/packs/gallery/card/example.html +10 -0
  209. package/packs/gallery/card/preview.svg +1 -0
  210. package/packs/gallery/card/style.css +6 -0
  211. package/packs/gallery/card/template.html +6 -0
  212. package/packs/gallery/card/writing.md +14 -0
  213. package/packs/gallery/chart-card/component.json +15 -0
  214. package/packs/gallery/chart-card/example.html +14 -0
  215. package/packs/gallery/chart-card/kit.js +130 -0
  216. package/packs/gallery/chart-card/preview.svg +1 -0
  217. package/packs/gallery/chart-card/style.css +42 -0
  218. package/packs/gallery/chart-card/template.html +13 -0
  219. package/packs/gallery/chart-card/writing.md +20 -0
  220. package/packs/gallery/chart-view/component.json +20 -0
  221. package/packs/gallery/chart-view/example.html +13 -0
  222. package/packs/gallery/chart-view/kit.js +355 -0
  223. package/packs/gallery/chart-view/preview.svg +1 -0
  224. package/packs/gallery/chart-view/style.css +94 -0
  225. package/packs/gallery/chart-view/template.html +3 -0
  226. package/packs/gallery/chart-view/writing.md +22 -0
  227. package/packs/gallery/countdown/component.json +18 -0
  228. package/packs/gallery/countdown/example.html +3 -0
  229. package/packs/gallery/countdown/kit.js +156 -0
  230. package/packs/gallery/countdown/preview.svg +1 -0
  231. package/packs/gallery/countdown/style.css +130 -0
  232. package/packs/gallery/countdown/template.html +3 -0
  233. package/packs/gallery/countdown/writing.md +22 -0
  234. package/packs/gallery/habit/component.json +14 -0
  235. package/packs/gallery/habit/example.html +3 -0
  236. package/packs/gallery/habit/kit.js +295 -0
  237. package/packs/gallery/habit/preview.svg +1 -0
  238. package/packs/gallery/habit/style.css +246 -0
  239. package/packs/gallery/habit/template.html +3 -0
  240. package/packs/gallery/habit/writing.md +21 -0
  241. package/packs/gallery/heatmap/component.json +18 -0
  242. package/packs/gallery/heatmap/example.html +3 -0
  243. package/packs/gallery/heatmap/kit.js +351 -0
  244. package/packs/gallery/heatmap/preview.svg +1 -0
  245. package/packs/gallery/heatmap/style.css +201 -0
  246. package/packs/gallery/heatmap/template.html +3 -0
  247. package/packs/gallery/heatmap/writing.md +21 -0
  248. package/packs/gallery/kanban/component.json +18 -0
  249. package/packs/gallery/kanban/example.html +12 -0
  250. package/packs/gallery/kanban/kit.js +540 -0
  251. package/packs/gallery/kanban/preview.svg +1 -0
  252. package/packs/gallery/kanban/style.css +212 -0
  253. package/packs/gallery/kanban/template.html +3 -0
  254. package/packs/gallery/kanban/writing.md +20 -0
  255. package/packs/gallery/metric/component.json +22 -0
  256. package/packs/gallery/metric/example.html +15 -0
  257. package/packs/gallery/metric/kit.js +248 -0
  258. package/packs/gallery/metric/preview.svg +1 -0
  259. package/packs/gallery/metric/style.css +92 -0
  260. package/packs/gallery/metric/template.html +3 -0
  261. package/packs/gallery/metric/writing.md +23 -0
  262. package/packs/gallery/note/component.json +14 -0
  263. package/packs/gallery/note/example.html +3 -0
  264. package/packs/gallery/note/kit.js +102 -0
  265. package/packs/gallery/note/preview.svg +1 -0
  266. package/packs/gallery/note/style.css +76 -0
  267. package/packs/gallery/note/template.html +3 -0
  268. package/packs/gallery/note/writing.md +18 -0
  269. package/packs/gallery/pack.json +8 -0
  270. package/packs/gallery/recent/component.json +13 -0
  271. package/packs/gallery/recent/example.html +1 -0
  272. package/packs/gallery/recent/kit.js +238 -0
  273. package/packs/gallery/recent/preview.svg +1 -0
  274. package/packs/gallery/recent/style.css +158 -0
  275. package/packs/gallery/recent/template.html +1 -0
  276. package/packs/gallery/recent/writing.md +15 -0
  277. package/packs/gallery/static/component.json +13 -0
  278. package/packs/gallery/static/example.html +11 -0
  279. package/packs/gallery/static/preview.svg +1 -0
  280. package/packs/gallery/static/style.css +138 -0
  281. package/packs/gallery/static/template.html +1 -0
  282. package/packs/gallery/static/writing.md +18 -0
  283. package/packs/gallery/stats/component.json +13 -0
  284. package/packs/gallery/stats/example.html +1 -0
  285. package/packs/gallery/stats/kit.js +166 -0
  286. package/packs/gallery/stats/preview.svg +1 -0
  287. package/packs/gallery/stats/style.css +151 -0
  288. package/packs/gallery/stats/template.html +1 -0
  289. package/packs/gallery/stats/writing.md +11 -0
  290. package/packs/gallery/today/component.json +12 -0
  291. package/packs/gallery/today/example.html +1 -0
  292. package/packs/gallery/today/kit.js +185 -0
  293. package/packs/gallery/today/preview.svg +1 -0
  294. package/packs/gallery/today/style.css +120 -0
  295. package/packs/gallery/today/template.html +1 -0
  296. package/packs/gallery/today/writing.md +14 -0
  297. package/packs/gallery/todos/component.json +19 -0
  298. package/packs/gallery/todos/example.html +3 -0
  299. package/packs/gallery/todos/kit.js +342 -0
  300. package/packs/gallery/todos/preview.svg +1 -0
  301. package/packs/gallery/todos/style.css +267 -0
  302. package/packs/gallery/todos/template.html +3 -0
  303. package/packs/gallery/todos/writing.md +30 -0
  304. package/packs/gallery/weather/component.json +19 -0
  305. package/packs/gallery/weather/example.html +3 -0
  306. package/packs/gallery/weather/kit.js +312 -0
  307. package/packs/gallery/weather/preview.svg +1 -0
  308. package/packs/gallery/weather/style.css +258 -0
  309. package/packs/gallery/weather/template.html +3 -0
  310. package/packs/gallery/weather/writing.md +26 -0
  311. package/packs/gallery-soft/pack.json +8 -0
  312. package/release.json +12 -10
  313. package/sdk/doc-kit.d.ts +396 -5
  314. package/sdk/doc-state.d.ts +2 -0
  315. package/skill/SKILL.md +56 -23
  316. package/skill/references/block-script.md +140 -0
  317. package/skill/references/building-blocks.md +257 -0
  318. package/skill/references/extensions.md +59 -0
  319. package/skill/references/pages-and-data.md +221 -0
  320. package/skill/references/writing-html.md +6 -4
  321. package/skill/sdk/doc-kit.d.ts +396 -5
  322. package/skill/sdk/doc-state.d.ts +2 -0
  323. package/web/assets/index-BK2e_07w.js +13 -0
  324. package/web/assets/index-CWFI83mF.css +1 -0
  325. package/web/index.html +2 -2
  326. package/dist/chunk-3HIRPVLU.mjs +0 -2
  327. package/dist/chunk-5MHMDLZD.mjs +0 -4
  328. package/dist/chunk-64K7IPLO.mjs +0 -255
  329. package/dist/chunk-6QXGZM36.mjs +0 -8
  330. package/dist/chunk-6RGJOMJP.mjs +0 -16
  331. package/dist/chunk-AJ73XJF3.mjs +0 -2
  332. package/dist/chunk-AUZGSZJH.mjs +0 -13
  333. package/dist/chunk-BFE45AKL.mjs +0 -2
  334. package/dist/chunk-BXO2ZY5P.mjs +0 -2
  335. package/dist/chunk-CEH4MRCC.mjs +0 -2
  336. package/dist/chunk-CY2FNNAB.mjs +0 -16
  337. package/dist/chunk-DFUDV4LX.mjs +0 -2
  338. package/dist/chunk-DKXG4XDG.mjs +0 -2
  339. package/dist/chunk-EZQ34M3F.mjs +0 -124
  340. package/dist/chunk-EZY3WC6Z.mjs +0 -15
  341. package/dist/chunk-FUKQNOQS.mjs +0 -9
  342. package/dist/chunk-JPOSLGOC.mjs +0 -6
  343. package/dist/chunk-L3QKH4OK.mjs +0 -26
  344. package/dist/chunk-LAE5NQ4B.mjs +0 -2
  345. package/dist/chunk-LEF6DRCK.mjs +0 -59
  346. package/dist/chunk-NO5NJCHO.mjs +0 -2
  347. package/dist/chunk-NQPVAU63.mjs +0 -2
  348. package/dist/chunk-NSKUTQ5J.mjs +0 -5
  349. package/dist/chunk-O35FIXRB.mjs +0 -2
  350. package/dist/chunk-P2CAZPDP.mjs +0 -3
  351. package/dist/chunk-PL6GHLGD.mjs +0 -15
  352. package/dist/chunk-QJIR2BLP.mjs +0 -6
  353. package/dist/chunk-QUPLVPB2.mjs +0 -7
  354. package/dist/chunk-R7BNENLY.mjs +0 -4
  355. package/dist/chunk-S6O4UHLC.mjs +0 -2
  356. package/dist/chunk-SGJWQKEI.mjs +0 -2
  357. package/dist/chunk-TX6JH7TR.mjs +0 -2
  358. package/dist/chunk-TYK6VNAB.mjs +0 -10
  359. package/dist/chunk-UALRNVSE.mjs +0 -4
  360. package/dist/chunk-V3HXVQEM.mjs +0 -2
  361. package/dist/chunk-V5VXUJKG.mjs +0 -2
  362. package/dist/chunk-VBXMADMP.mjs +0 -2
  363. package/dist/chunk-VNV55RUA.mjs +0 -2
  364. package/dist/chunk-VPD2PS35.mjs +0 -2
  365. package/dist/chunk-W4VKN3GF.mjs +0 -7
  366. package/dist/chunk-W5BXXPDJ.mjs +0 -3
  367. package/dist/chunk-XUNHRCOG.mjs +0 -6
  368. package/skill/references/building-components.md +0 -367
  369. package/web/assets/index-Bh74DfsV.css +0 -1
  370. package/web/assets/index-UW0RyFFt.js +0 -13
@@ -1,13 +1,13 @@
1
1
  import { escapeText } from "../../../shared/escape.mjs";
2
2
  import { BLOCK_COLORS, cellColorOf, colorLook } from "../../components/colors.mjs";
3
- import { CELLS_MIME as MIME, CELL_BOLD, OLD_CELLS_MIME, cellsData } from "../../components/table/index.mjs";
3
+ import { CELLS_MIME as MIME, CELL_BOLD, OLD_CELLS_MIME, pictureFile } from "../../components/table/index.mjs";
4
4
  import { alignItems, alignLook } from "./aligns.mjs";
5
5
  import { insertColumn, insertRow, tableParts, tableSlots, tableState } from "../../components/table/page.mjs";
6
6
  import { atEdge, editable } from "../caret.mjs";
7
7
  import { pastedLines, serializeInner } from "../core.mjs";
8
8
  import { formulaMaker, loadKatex } from "../formula/index.mjs";
9
9
  import { FORMAT_KEYS } from "../format-bar/index.mjs";
10
- import { formatRange, markValue } from "../marks.mjs";
10
+ import { formatRange, markValue, rangeAt, rangeOffsets } from "../marks.mjs";
11
11
  import { gestureLock } from "../gesture.mjs";
12
12
  import { addStyle } from "../ui.mjs";
13
13
  import { createCellBar } from "./cell-bar.mjs";
@@ -31,9 +31,12 @@ import { t } from "../../../shared/i18n/index.mjs";
31
31
  // a typed character goes at the end of the last cell, editing it; Ctrl+D fills down (copies rows or
32
32
  // columns), Ctrl+R right; Ctrl+B / I / U / E and Ctrl+Shift+S format whole cells.
33
33
  // - Copied, cells are the page's own (their inline HTML), an HTML table and lines of tab-separated
34
- // text; pasted onto selected cells, the page's own, another program's table (its words) or lines
35
- // of tab-separated text fill them from the top left (repeated over a selection a whole number of
36
- // times larger), rows and columns put in where they overflow; one undo step.
34
+ // text (a cell in quotes holding its lines, as Excel writes one); pasted onto selected cells or with
35
+ // the caret in one, what the clipboard holds as cells (clipboard/ cellsOf: the page's own, another
36
+ // program's table with its marks and pictures, a Markdown table, tab-separated lines) fills them
37
+ // from the top left or the caret's cell (repeated over a selection a whole number of times larger),
38
+ // rows and columns put in where they overflow; one undo step. With the caret in a cell a single
39
+ // value is the editor's paste at the caret (but one holding a picture: pictures.mjs).
37
40
  // A cell's color (one, as a block's: components/colors.mjs) and alignment are its classes (its look:
38
41
  // components/table/index.mjs); its text changes are the editor's (struct, history.mjs). Where cells
39
42
  // are merged (shared/table-slots.mjs), Tab goes from cell to cell, the arrows and Enter step
@@ -407,6 +410,17 @@ export function createCells(tools) {
407
410
 
408
411
  // A cell's inline HTML as copied (a locked cell's words alone: what a script drew is not its text).
409
412
  const htmlOf = (cell) => (writable(cell) ? serializeInner(cell) : escapeText(cell.textContent));
413
+ // A cell's words as text: its lines (its line breaks, its list's items) apart, a picture its alt.
414
+ const textOf = (cell) => {
415
+ const copy = cell.cloneNode(true);
416
+ copy.querySelectorAll("[data-doc-dev]").forEach((node) => node.remove());
417
+ copy.querySelectorAll("br").forEach((node) => node.replaceWith("\n"));
418
+ copy.querySelectorAll("li").forEach((node, index) => index > 0 && node.prepend("\n"));
419
+ copy.querySelectorAll("img").forEach((node) => node.replaceWith(node.getAttribute("alt") ?? ""));
420
+ return copy.textContent.replace(/[ \t]*\n[ \t]*/g, "\n").trim();
421
+ };
422
+ // A cell of tab-separated text as Excel writes one: in quotes when it holds a tab, a line break or a quote, its quotes doubled.
423
+ const tsvCell = (text) => (/[\t\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text);
410
424
 
411
425
  function copy(event) {
412
426
  const sel = tools.sel;
@@ -426,64 +440,108 @@ export function createCells(tools) {
426
440
  }
427
441
  }
428
442
  const html = grid.map((row) => row.map((cell) => (cell ? htmlOf(cell) : "")));
429
- const payload = JSON.stringify({ v: 1, cells: html });
443
+ // The report it was copied from: a paste in another one copies its pictures' files.
444
+ const payload = JSON.stringify({ v: 1, cells: html, report: document.querySelector(".doc-page")?.dataset.docId ?? null });
430
445
  event.clipboardData.setData(MIME, payload);
431
446
  event.clipboardData.setData(OLD_CELLS_MIME, payload); // a page of the old version (before 2026-10-26) reads this one
432
- event.clipboardData.setData("text/html", `<table>${html.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("")}</table>`);
433
- event.clipboardData.setData("text/plain", grid.map((row) => row.map((cell) => (cell?.textContent ?? "").replace(/[\t\n]+/g, " ")).join("\t")).join("\n"));
447
+ // For other programs (Excel, Google Sheets, another editor): an HTML table, its pictures where the
448
+ // server serves them (an address of this computer), and tab-separated text.
449
+ const shown = (cell) => (cell ? htmlOf(cell).replace(/<img src="(assets\/[^"]+)"/g, (all, file) => `<img src="${escapeText(new URL(cell.querySelector(`img[data-ge-src="${file}"]`)?.getAttribute("src") ?? file, location.href).href)}"`) : "");
450
+ event.clipboardData.setData("text/html", `<table>${grid.map((row) => `<tr>${row.map((cell) => `<td>${shown(cell)}</td>`).join("")}</tr>`).join("")}</table>`);
451
+ event.clipboardData.setData("text/plain", grid.map((row) => row.map((cell) => tsvCell(cell ? textOf(cell) : "")).join("\t")).join("\n"));
434
452
  if (event.type === "cut") clearCells(tools, grid.flat().filter(Boolean));
435
453
  else tools.say(t("已复制 {0} 格", grid.length * grid[0].length));
436
454
  }
437
455
  document.addEventListener("copy", copy, true);
438
456
  document.addEventListener("cut", copy, true);
439
457
 
440
- // What is pasted, as rows of cells ({ html } the page's own, { text } another program's), or null:
441
- // the page's cells, its text, table or blocks with their marks (blocks one cell of their words, no
442
- // list's marker or Markdown: tools.ownCells), another program's table, tab-separated lines or text.
443
- function gridOf(data) {
444
- try {
445
- const own = JSON.parse(cellsData(data) || "null");
446
- if (Array.isArray(own?.cells) && own.cells.length > 0 && own.cells.every((row) => Array.isArray(row) && row.length > 0 && row.every((html) => typeof html === "string"))) {
447
- return own.cells.map((row) => row.map((html) => ({ html })));
448
- }
449
- } catch {
450
- // not the page's own
451
- }
452
- const rows = tools.ownCells(data);
453
- if (rows) return rows.map((row) => row.map((html) => ({ html })));
454
- const html = data.getData("text/html");
455
- const table = html ? new DOMParser().parseFromString(html, "text/html").querySelector("table") : null;
456
- if (table?.rows.length) return [...table.rows].map((row) => [...row.cells].map((cell) => ({ text: cell.textContent.replace(/\s+/g, " ").trim() })));
457
- const text = (data.getData("text/plain") || "").replace(/\r\n?/g, "\n").replace(/\n$/, "");
458
- const lines = text.split("\n");
459
- if (lines.length > 1) {
460
- const width = lines[0].split("\t").length;
461
- if (width > 1 && lines.every((line) => line.split("\t").length === width)) return lines.map((line) => line.split("\t").map((part) => ({ text: part })));
462
- }
463
- return text ? [[{ text: text.replace(/\s*\n\s*/g, " ") }]] : null;
458
+ // The cell the caret (or a selection within it) is in, where a paste goes (`target`: the event's,
459
+ // the cell or in it), in a table the tools manage, with where it is: { unit, at, cell }, or null.
460
+ function caretCell(target) {
461
+ const selection = getSelection();
462
+ const node = selection.rangeCount ? selection.getRangeAt(0).commonAncestorContainer : null;
463
+ const cell = (node?.nodeType === 1 ? node : node?.parentElement)?.closest?.("td, th");
464
+ const unit = cell && writable(cell) && cell.contains(target) ? tools.tableAt(cell) : null;
465
+ const at = unit ? tools.placeOf(tableSlots(unit), cell) : null;
466
+ return at ? { unit, at, cell } : null;
464
467
  }
468
+ // Whether a pasted cell holds nothing: no words, no picture.
469
+ const blank = (html) => !html.includes("<img") && !html.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").trim();
465
470
 
471
+ // A paste into the cells (notion-lite's 872952, the user's 2026-11-09: cells pasted with the caret in
472
+ // a cell are cells, not one cell's text): what the clipboard holds as cells (tools.cellsOf: the
473
+ // page's own, another program's table, a Markdown table, tab-separated lines, else its text) goes
474
+ // from the selected cells' top left, or the caret's cell. With the caret in a cell, a single value
475
+ // is the editor's paste at the caret (its marks, an address a link: clipboard/), but one holding a
476
+ // picture, which goes in at the caret here (its file copied first); with cells selected, it fills
477
+ // them (notion-lite's), as do picture files pasted on them (in the first one).
466
478
  function paste(event) {
467
479
  const sel = tools.sel;
468
- if (!sel || sel.editing || !tools.active() || !sel.unit.isConnected) return;
480
+ if (!tools.active()) return;
481
+ const caret = !sel || sel.editing ? caretCell(event.target) : null;
482
+ if (!(caret || (sel && !sel.editing && sel.unit.isConnected))) return;
483
+ const data = event.clipboardData;
484
+ const pasted = data ? tools.cellsOf(data) : null;
485
+ const many = pasted && (pasted.rows.length > 1 || pasted.rows[0].length > 1);
486
+ const files = [...(data?.files ?? [])].filter((file) => /^image\//.test(file.type));
487
+ if (caret && !many && !(pasted?.rows[0][0].includes("<img") && !files.length)) return; // the editor's
469
488
  event.preventDefault();
470
489
  event.stopImmediatePropagation();
471
- const grid = gridOf(event.clipboardData);
472
- if (!grid) return;
473
- // Formulas in what is pasted are drawn at once, KaTeX loaded first where the page has none yet.
474
- if (grid.some((row) => row.some((cell) => cell.html?.includes("doc-math-inline")))) loadKatex().then(() => pasteGrid(sel, grid, formulaMaker()));
475
- else pasteGrid(sel, grid, null);
490
+ const target = caret ? { unit: caret.unit, type: "cell", anchor: caret.at, focus: caret.at, editing: false } : sel;
491
+ if (!pasted && !files.length) return;
492
+ const offsets = caret && !many ? rangeOffsets(caret.cell, getSelection().getRangeAt(0)) : null;
493
+ (async () => {
494
+ let rows = pasted?.rows;
495
+ let found = new Map();
496
+ if (!rows) {
497
+ // Picture files on selected cells: the first one's pictures.
498
+ const made = await tools.pictures.fromFiles(files);
499
+ if (!made.length) return;
500
+ rows = [[made.map((img) => `<img src="${pictureFile(img)}" alt="">`).join("")]];
501
+ made.forEach((img) => found.set(pictureFile(img), { path: pictureFile(img) }));
502
+ } else found = await tools.pictures.resolve(rows.flat(), pasted.from);
503
+ // Formulas in what is pasted are drawn at once, KaTeX loaded first where the page has none yet.
504
+ const formula = rows.some((row) => row.some((html) => html.includes("doc-math-inline"))) && (await loadKatex()) ? formulaMaker() : null;
505
+ const lines = (html) => {
506
+ const made = pastedLines(document, html, { links: true, formula, picture: tools.pictures.maker(found) });
507
+ return made.flatMap((line, index) => (index > 0 ? [document.createElement("br"), ...line] : line));
508
+ };
509
+ if (offsets) atCaret(caret, offsets, lines(rows[0][0]));
510
+ else pasteGrid(target, rows, lines);
511
+ })();
512
+ }
513
+
514
+ // A value holding a picture pasted at the caret in a cell (its file copied first): in at the caret,
515
+ // one undo step, the caret after it.
516
+ function atCaret({ unit, cell }, offsets, nodes) {
517
+ if (!cell.isConnected || nodes.length === 0) return;
518
+ let last = null;
519
+ const made = tools.change(unit, () => {
520
+ const range = rangeAt(cell, offsets);
521
+ range.deleteContents();
522
+ const fragment = document.createDocumentFragment();
523
+ fragment.append(...nodes);
524
+ last = nodes.at(-1);
525
+ range.insertNode(fragment);
526
+ return true;
527
+ }, null, [cell]);
528
+ if (made === null || !last?.isConnected) return;
529
+ const range = document.createRange();
530
+ range.setStartAfter(last);
531
+ range.collapse(true);
532
+ getSelection().removeAllRanges();
533
+ getSelection().addRange(range);
476
534
  }
477
535
 
478
- function pasteGrid(sel, pasted, formula) {
536
+ // `pasted`: rows of cells' inline HTML; `lines(html)`: a cell's nodes of one of them.
537
+ function pasteGrid(sel, pasted, lines) {
479
538
  if (!sel.unit.isConnected) return;
480
539
  let grid = pasted;
481
540
  // Its empty last rows and columns go.
482
- const empty = (cell) => !(cell.html ?? cell.text).replace(/<[^>]*>/g, "").trim();
483
- while (grid.length > 1 && grid.at(-1).every(empty)) grid = grid.slice(0, -1);
541
+ while (grid.length > 1 && grid.at(-1).every(blank)) grid = grid.slice(0, -1);
484
542
  const width = Math.max(...grid.map((row) => row.length));
485
- grid = grid.map((row) => [...row, ...Array.from({ length: width - row.length }, () => ({ text: "" }))]);
486
- while (grid[0].length > 1 && grid.every((row) => empty(row.at(-1)))) grid = grid.map((row) => row.slice(0, -1));
543
+ grid = grid.map((row) => [...row, ...Array.from({ length: width - row.length }, () => "")]);
544
+ while (grid[0].length > 1 && grid.every((row) => blank(row.at(-1)))) grid = grid.map((row) => row.slice(0, -1));
487
545
  const { unit } = sel;
488
546
  const { r0, r1, c0, c1 } = tools.bounds(sel);
489
547
  const times = [Math.max(1, Math.floor((r1 - r0 + 1) / grid.length)), Math.max(1, Math.floor((c1 - c0 + 1) / grid[0].length))];
@@ -504,9 +562,7 @@ export function createCells(tools) {
504
562
  // A merged cell takes what goes in its top left slot.
505
563
  const place = tools.placeOf(slotsNow, cell);
506
564
  if (!cell || !writable(cell) || place[0] !== r0 + r || place[1] !== c0 + c) continue;
507
- const from = grid[r % grid.length][c % grid[0].length];
508
- if (from.html !== undefined) cell.replaceChildren(...pastedLines(document, from.html, { links: true, formula }).flatMap((line, index) => (index > 0 ? [document.createTextNode(" "), ...line] : line)));
509
- else cell.textContent = from.text;
565
+ cell.replaceChildren(...lines(grid[r % grid.length][c % grid[0].length]));
510
566
  }
511
567
  }
512
568
  return true;
@@ -37,4 +37,6 @@ export const TABLE_ICONS = {
37
37
  // 一键优化: a magic wand and its sparks; 识别数字列: a number sign.
38
38
  magic: svg('<path d="m3.75 16.25 9-9"/><path d="m11.25 5.75 3 3"/><path d="M15.25 2.5v3M13.75 4h3M5.5 3.25v2M4.5 4.25h2M16 10.75v2M15 11.75h2"/>'),
39
39
  numbers: svg('<path d="M8.25 4 6.75 16M13.75 4l-1.5 12M4.5 8h12M3.75 12.25h12"/>'),
40
+ // 视图: three columns of cards, a board drawn from the table.
41
+ view: svg('<rect x="3.25" y="3.75" width="3.5" height="7.5" rx="1"/><rect x="8.25" y="3.75" width="3.5" height="12.5" rx="1"/><rect x="13.25" y="3.75" width="3.5" height="5" rx="1"/>'),
40
42
  };
@@ -19,7 +19,9 @@ import { MENU_WIDTH } from "../type-grid.mjs";
19
19
  import { addStyle, element } from "../ui.mjs";
20
20
  import { alignItems, alignLook } from "./aligns.mjs";
21
21
  import { clearCells, createCells } from "./cells.mjs";
22
+ import { createCellPictures } from "./pictures.mjs";
22
23
  import { TABLE_ICONS } from "./icons.mjs";
24
+ import { createViews } from "./views.mjs";
23
25
  import { createColumnWidths } from "./widths.mjs";
24
26
  import { t } from "../../../shared/i18n/index.mjs";
25
27
 
@@ -54,7 +56,8 @@ import { t } from "../../../shared/i18n/index.mjs";
54
56
  // A table wider or taller than its frame scrolls in it: the tools stand by what the frame shows of
55
57
  // it (the geometry's view), the grips and + points of the rows and columns in sight.
56
58
  // Each change is one undo step; the page writes it as one operation of the table (grid.mjs). The
57
- // keys and the cells' bar are cells.mjs's.
59
+ // keys and the cells' bar are cells.mjs's; what a view of the table asks (a custom block reading it:
60
+ // set a cell, move, add or take out a row) and 视图 › of its menu, views.mjs's.
58
61
 
59
62
  const STYLE = `
60
63
  .ge-tgrip { display: flex; align-items: center; justify-content: center; cursor: grab; pointer-events: auto; }
@@ -78,7 +81,7 @@ const STYLE = `
78
81
  .ge-tplus:hover > i { width: 18px; height: 18px; background: var(--ge-accent); box-shadow: 0 0 0 2px light-dark(#ffffff, #191919); }
79
82
  .ge-tplus:hover > i > svg { display: block; }
80
83
  .ge-tline { border-radius: 1px; background: var(--ge-accent); pointer-events: none; }
81
- .ge-tgrow { box-sizing: border-box; display: grid; place-items: center; border: 1px solid var(--ge-line-strong); border-radius: 50%; background: var(--ge-bg); color: var(--ge-tx3); box-shadow: 0 1px 2px rgb(0 0 0 / 6%); cursor: nwse-resize; pointer-events: auto; transition: background 80ms, color 80ms; }
84
+ .ge-tgrow { box-sizing: border-box; display: grid; place-items: center; border: 0; border-radius: 50%; background: var(--ge-bg); color: var(--ge-tx3); box-shadow: 0 1px 3px light-dark(rgb(25 25 25 / 18%), rgb(0 0 0 / 45%)); cursor: nwse-resize; pointer-events: auto; transition: background 80ms, color 80ms; }
82
85
  .ge-tgrow:hover, .ge-tgrow[data-on] { background: light-dark(#f1f1ef, #2f2f2f); color: light-dark(#55534e, #c4c1bb); }
83
86
  .ge-tgrow > svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
84
87
  .ge-tframe { box-sizing: border-box; border: 2px solid var(--ge-accent); border-radius: 2px; background: color-mix(in srgb, var(--ge-accent) 9%, transparent); pointer-events: none; }
@@ -105,11 +108,15 @@ const REACH = { out: 12, in: 8 }; // the pointer this near the table's top (left
105
108
  * active(): whether the tools may show. struct(parents, touched, mutate): the editor's undoable
106
109
  * structural change; look(elements, change, select): a change of classes and sizes as one undo step. selectBlock(unit):
107
110
  * select the table as a block (Esc). resizeTable(event, unit, axis): a drag of the table's width
108
- * ("x") or scroll height ("y") from an armed edge (resize/). say: the status line. ownCells(data): the
109
- * page's own text or blocks on the clipboard as rows of cells' inline HTML, or null (clipboard/).
111
+ * ("x") or scroll height ("y") from an armed edge (resize/). say: the status line. cellsOf(data): what
112
+ * the clipboard holds as rows of cells, { rows, from } (clipboard/ cellsOf), or null. upload(kind,
113
+ * body): a file sent to the report's folder (POST /__gallery/edit/upload), a picture of a cell's
114
+ * (pictures.mjs).
110
115
  * typedKey(event): a key that is the "/" or "@" menu's, standing on a cell's text (slash-menu/, at-menu/), comes before the cell's.
116
+ * recordStep(host, before): a text step of a text block, what a view's set is (history.mjs). insertBelow(unit, key,
117
+ * { attributes, done }): a block of a menu's key put in below a block in one undo step (editor.mjs), what 视图 › makes.
111
118
  */
112
- export function createTableEditing({ active, struct, look, selectBlock, resizeTable = () => false, say, ownCells = () => null, typedKey = () => false }) {
119
+ export function createTableEditing({ active, struct, look, selectBlock, resizeTable = () => false, say, cellsOf = () => null, upload = async () => { throw new Error("upload"); }, typedKey = () => false, recordStep = () => {}, insertBelow = () => {} }) {
113
120
  addStyle(STYLE);
114
121
  const menu = createMenu();
115
122
  let hover = null; // { unit, row, column, edges: { row, column } }: the cell under the pointer, and whether it is at the table's left (top) edge (edgesAt)
@@ -266,7 +273,7 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
266
273
  menuOpen: () => menu.isOpen,
267
274
  typedKey,
268
275
  resizeTable,
269
- ownCells,
276
+ cellsOf,
270
277
  dragging: () => drag !== null,
271
278
  keys: (on) => {
272
279
  keyboard = on;
@@ -274,6 +281,8 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
274
281
  },
275
282
  draw: () => draw(),
276
283
  };
284
+ const pictures = createCellPictures({ active, recordStep, say, upload });
285
+ tools.pictures = pictures;
277
286
  const cells = createCells(tools);
278
287
  const widths = createColumnWidths(tools);
279
288
 
@@ -918,10 +927,15 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
918
927
 
919
928
  addEventListener("scroll", () => { if (!drag) draw(); }, { passive: true, capture: true });
920
929
 
930
+ // What a view of a table asks, and 视图 › (views.mjs).
931
+ const views = createViews({ active, change, tableAt, recordStep, insertBelow, drawn: () => draw() });
932
+
921
933
  // The rows of a table the tools manage. 一键优化 is a split row (menu.mjs): its words run it, its ›
922
- // opens two of its parts to run alone, 自动调整列宽 and 识别数字列 (the user's, 2026-11-07).
934
+ // opens two of its parts to run alone, 自动调整列宽 and 识别数字列 (the user's, 2026-11-07); 视图 › last,
935
+ // where a custom block reads tables (views.mjs).
923
936
  function toolRows(unit) {
924
937
  const state = tableState(unit);
938
+ const view = views.menuRow(unit);
925
939
  return [
926
940
  { label: t("标题行"), icon: ICONS.headerRow, checked: state.headerRow, run: () => headerRow(unit, state) },
927
941
  { label: t("标题列"), icon: ICONS.headerColumn, checked: state.headerColumn, run: () => change(unit, () => setHeaderColumn(unit, !state.headerColumn), state.headerColumn ? t("已取消标题列") : t("已设为标题列")) },
@@ -933,6 +947,7 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
933
947
  ],
934
948
  },
935
949
  { label: t("均分列宽"), icon: ICONS.even, run: () => widths.even(unit) },
950
+ ...(view ? [view] : []),
936
951
  ];
937
952
  }
938
953
 
@@ -958,7 +973,10 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
958
973
  menu.close();
959
974
  clear();
960
975
  cells.update();
976
+ pictures.hide();
961
977
  },
978
+ /** The pictures of cells (pictures.mjs): files pasted into a cell, those of pasted cells made the folder's. */
979
+ pictures,
962
980
  /** The table whose cells, rows or columns are selected (no caret in one of them), or null: Ctrl+A's step to the table whole (select-all/). */
963
981
  get selectedTable() { return sel && !sel.editing && sel.unit.isConnected ? sel.unit : null; },
964
982
  /** The selection let go (blocks selected instead: select-all/). */
@@ -973,14 +991,15 @@ export function createTableEditing({ active, struct, look, selectBlock, resizeTa
973
991
  return Boolean(unit) && /^(TD|TH)$/.test(from.tagName) && /^(TD|TH)$/.test(to?.tagName ?? "") && tableAt(to) === unit;
974
992
  },
975
993
  /** The editor's UI nodes to keep in the top layer when a modal dialog opens. */
976
- get nodes() { return [...nodes, ...cells.nodes]; },
994
+ get nodes() { return [...nodes, ...cells.nodes, ...pictures.nodes]; },
977
995
  /** Whether a node is one of the tools' (a grip, an edge's strip, the corner's +, the cells' bar). */
978
- holds: (node) => Boolean(node) && [...nodes, ...cells.nodes].some((each) => each.contains(node)),
996
+ holds: (node) => Boolean(node) && [...nodes, ...cells.nodes, ...pictures.nodes].some((each) => each.contains(node)),
979
997
  /**
980
998
  * A table's rows of its block menu (notion-lite's 表格 section, under the menu's one title): a
981
999
  * table of the kind these tools manage has 标题行 and 标题列, switches (checked when on); then
982
1000
  * actions, each applied once (no check): 一键优化 › (自动调整列宽, 识别数字列 in its submenu),
983
- * 均分列宽; any table in its frame has its size's rows after them (layoutRows). [] for any other block.
1001
+ * 均分列宽, 视图 › (where a custom block reads tables: views.mjs); any table in its frame has its
1002
+ * size's rows after them (layoutRows). [] for any other block.
984
1003
  */
985
1004
  menuItems(target) {
986
1005
  if (!target?.matches?.('[data-ge-unit-kind="table"]')) return [];
@@ -0,0 +1,299 @@
1
+ import { UPLOAD_LIMITS, mediaUrl, sizeLimit } from "../../../shared/media.mjs";
2
+ import { cellPicture, pictureFile } from "../../components/table/index.mjs";
3
+ import { ownHTML } from "../core.mjs";
4
+ import { gestureLock } from "../gesture.mjs";
5
+ import { drawAt, drawBox, layerOf } from "../overlay-layer.mjs";
6
+ import { onSettle, own } from "../overlays.mjs";
7
+ import { blobBase64 } from "../post.mjs";
8
+ import { addStyle, element } from "../ui.mjs";
9
+ import { t } from "../../../shared/i18n/index.mjs";
10
+
11
+ // 单元格里的图片 (the user's, 2026-11-09: no image block, a light one; Markdown tables hold pictures):
12
+ // an <img> amid a table cell's words, a picture of the document's folder (components/table/ cellPicture; the
13
+ // file takes it as source/blocks.mjs cellPictureProblem says, the kit lays it out: table.css). notion-
14
+ // lite has none; this is the plainest that works, flat:
15
+ // - A press on one selects it (the selection around it, a 2px blue frame over it, no caret): Delete
16
+ // or Backspace then takes it out, a typed character takes its place, Esc puts the caret after it;
17
+ // the arrows go past it as past a character (the browser's: an <img> in an editable text is one).
18
+ // - A gray bar at its right edge (the image block's handle) under the pointer or while it is
19
+ // selected: dragged, the picture's width in pixels (its height by its ratio), at least MIN_WIDTH,
20
+ // at most its table's frame, written as its width attribute; one undo step on letting go, Esc
21
+ // putting it back.
22
+ // - Where pictures come from (clipboard/, table/cells.mjs): a picture file pasted into a cell (a
23
+ // screenshot, a copied picture) sent to the document's folder as an image block's (fromFiles); the
24
+ // pictures of cells pasted (another program's table, a Markdown table, the page's own cells of
25
+ // another document) each made a file of the folder first (resolve: one of the folder as it is,
26
+ // another document's, a data: one and a web one copied in, the server downloading a web one as it
27
+ // does an image block's link), then put in by what pastedLines makes of each (maker): a picture of
28
+ // no file a link to it (a web one) or its alt words.
29
+
30
+ const MIN_WIDTH = 16;
31
+ const MAX_PICTURES = 50; // the pictures a paste copies in at most (each a download)
32
+ const WIDTH = /^[1-9]\d{0,3}$/;
33
+ const WEB = /^https?:\/\/[^\s"<>]+$/i;
34
+ const DATA = /^data:image\/[a-z0-9.+-]+;base64,/i;
35
+ // A file of a document's folder as the preview serves it (a copy of the page's HTML), or as a cell's text writes it.
36
+ const SERVED = /^(?:https?:\/\/[^/]+)?\/__gallery\/edit\/asset\/([^/?#]+)\/(assets\/[^/?#]+)$/;
37
+ const FOLDER_FILE = /^assets\/[A-Za-z0-9][A-Za-z0-9._-]{0,80}$/;
38
+ const CELL = '[data-ge-kind="cell"]';
39
+ // The name of a picture's file in its address (its last part, its escapes read).
40
+ const nameOf = (src) => {
41
+ const name = src.split(/[?#]/)[0].split("/").pop() || src;
42
+ try {
43
+ return decodeURIComponent(name);
44
+ } catch {
45
+ return name;
46
+ }
47
+ };
48
+
49
+ const STYLE = `
50
+ [data-ge-editing] ${CELL}[contenteditable="true"] img { cursor: default; }
51
+ .ge-layer > .ge-cellpic-handle { pointer-events: auto; }
52
+ .ge-cellpic-frame { box-sizing: border-box; border: 2px solid var(--ge-accent); border-radius: 2px; pointer-events: none; }
53
+ /* The image block's handle (image/page.mjs): a plain gray bar, no border. */
54
+ .ge-cellpic-handle { display: flex; align-items: center; justify-content: center; width: 12px; cursor: col-resize; }
55
+ .ge-cellpic-handle::after { content: ""; width: 4px; height: 32px; max-height: 70%; border-radius: 4px; background: light-dark(rgb(55 53 47 / 35%), rgb(255 255 255 / 38%)); transition: background 120ms ease; }
56
+ .ge-cellpic-handle:hover::after, html[data-ge-cellpic-resize] .ge-cellpic-handle::after { background: light-dark(rgb(55 53 47 / 60%), rgb(255 255 255 / 62%)); }
57
+ ${gestureLock("data-ge-cellpic-resize", { cursor: "col-resize" })}
58
+ `;
59
+
60
+ // The report this page shows (its main's data-doc-id: its folder's name).
61
+ const thisReport = () => document.querySelector(".doc-page")?.dataset.docId ?? null;
62
+
63
+ /**
64
+ * active(): whether the tools may act. recordStep(host, before): a text step of a text block
65
+ * (history.mjs). say: the status line. upload(kind, body): POST /__gallery/edit/upload, the server's
66
+ * answer (an Error when it fails).
67
+ */
68
+ export function createCellPictures({ active, recordStep, say, upload }) {
69
+ addStyle(STYLE);
70
+ let hovered = null; // the picture under the pointer
71
+ let picked = null; // the picture selected
72
+ let drag = null; // its edge dragged: { img, host, x, start, before, had }
73
+ const frame = element("div", { class: "ge-cellpic-frame", "data-doc-dev": "" });
74
+ const handle = element("div", { class: "ge-cellpic-handle", "data-doc-dev": "" });
75
+ // The picture they are drawn for: the frame the selected one's (or the dragged one's), the bar any of them.
76
+ const framed = () => drag?.img ?? picked;
77
+ const barred = () => drag?.img ?? picked ?? hovered;
78
+ own(frame, "cell picture", { alive: () => Boolean(framed()?.isConnected) });
79
+ own(handle, "cell picture", { alive: () => Boolean(barred()?.isConnected) });
80
+ // The cell of the picture drawn for, watched: a picture taken out (Delete, undo, someone else's
81
+ // words merged) takes them away at once, before the page draws again.
82
+ const watch = new MutationObserver(() => {
83
+ if (!barred()?.isConnected) draw();
84
+ });
85
+
86
+ // A picture of a cell the reader writes in, and that cell; or null.
87
+ const pictureAt = (node) => {
88
+ const img = node?.closest?.("img");
89
+ const host = img?.closest(CELL);
90
+ return img && host?.getAttribute("contenteditable") === "true" && host.closest("main") && !host.closest("[data-ge-lock], [inert]") && pictureFile(img) ? { img, host } : null;
91
+ };
92
+
93
+ function draw() {
94
+ if (picked && !picked.isConnected) picked = null;
95
+ if (hovered && !hovered.isConnected) hovered = null;
96
+ const img = barred();
97
+ watch.disconnect();
98
+ if (!img?.isConnected || !active()) {
99
+ frame.remove();
100
+ handle.remove();
101
+ return;
102
+ }
103
+ watch.observe(img.closest(CELL) ?? img.parentNode, { childList: true, subtree: true });
104
+ const layer = layerOf(img);
105
+ const rect = img.getBoundingClientRect();
106
+ if (img === framed()) drawBox(frame, layer, rect);
107
+ else frame.remove();
108
+ const origin = layer.getBoundingClientRect();
109
+ handle.style.height = `${Math.max(20, Math.round(rect.height))}px`;
110
+ drawAt(handle, layer, rect.right - origin.left - 2, rect.top - origin.top + Math.min(0, (rect.height - 20) / 2));
111
+ }
112
+
113
+ // Whether the selection is the picture alone (a press on it, or the browser's own).
114
+ const around = (img) => {
115
+ const selection = getSelection();
116
+ if (!selection.rangeCount || !img.parentNode) return false;
117
+ const range = selection.getRangeAt(0);
118
+ const at = [...img.parentNode.childNodes].indexOf(img);
119
+ return range.startContainer === img.parentNode && range.endContainer === img.parentNode && range.startOffset === at && range.endOffset === at + 1;
120
+ };
121
+ function pick({ img, host }) {
122
+ if (document.activeElement !== host) host.focus({ preventScroll: true });
123
+ const range = document.createRange();
124
+ range.selectNode(img);
125
+ getSelection().removeAllRanges();
126
+ getSelection().addRange(range);
127
+ picked = img;
128
+ draw();
129
+ }
130
+
131
+ // ---------- the pointer ----------
132
+
133
+ document.addEventListener("pointermove", (event) => {
134
+ if (drag) return;
135
+ const at = pictureAt(event.target);
136
+ const next = at ? at.img : event.target === handle ? hovered : null;
137
+ if (next !== hovered) {
138
+ hovered = next;
139
+ draw();
140
+ }
141
+ }, { passive: true });
142
+ // A press on a picture selects it, before the cells' tools (a drag between cells) and the caret hear it.
143
+ window.addEventListener("pointerdown", (event) => {
144
+ const at = event.button === 0 && active() ? pictureAt(event.target) : null;
145
+ if (!at) return;
146
+ event.preventDefault();
147
+ event.stopImmediatePropagation();
148
+ pick(at);
149
+ }, true);
150
+ window.addEventListener("mousedown", (event) => {
151
+ if (event.button === 0 && active() && (pictureAt(event.target) || event.target === handle)) event.preventDefault(); // no caret, no drag of the picture
152
+ }, true);
153
+ document.addEventListener("selectionchange", () => {
154
+ if (picked && !(picked.isConnected && around(picked))) {
155
+ picked = null;
156
+ draw();
157
+ }
158
+ });
159
+ document.addEventListener("keydown", (event) => {
160
+ if (event.key !== "Escape" || event.isComposing) return;
161
+ if (drag) return void (event.preventDefault(), event.stopImmediatePropagation(), end(false));
162
+ if (!picked || !around(picked)) return;
163
+ event.preventDefault();
164
+ event.stopImmediatePropagation();
165
+ const range = document.createRange();
166
+ range.setStartAfter(picked);
167
+ range.collapse(true);
168
+ getSelection().removeAllRanges();
169
+ getSelection().addRange(range);
170
+ }, true);
171
+ for (const type of ["scroll", "resize"]) window.addEventListener(type, () => { if (drag || picked || hovered) draw(); }, { capture: true, passive: true });
172
+ onSettle(draw);
173
+
174
+ // ---------- its width ----------
175
+
176
+ handle.addEventListener("pointerdown", (event) => {
177
+ const img = picked ?? hovered;
178
+ const host = img?.closest(CELL);
179
+ if (event.button !== 0 || !host || !active()) return;
180
+ event.preventDefault();
181
+ event.stopPropagation();
182
+ handle.setPointerCapture(event.pointerId);
183
+ drag = { img, host, x: event.clientX, start: img.getBoundingClientRect().width, before: ownHTML(host), had: img.getAttribute("width") };
184
+ document.documentElement.setAttribute("data-ge-cellpic-resize", "");
185
+ });
186
+ handle.addEventListener("pointermove", (event) => {
187
+ if (!drag) return;
188
+ const room = drag.host.closest(".doc-table-wrap")?.clientWidth || 2000; // never wider than its table's frame
189
+ drag.img.setAttribute("width", String(Math.round(Math.max(MIN_WIDTH, Math.min(room, drag.start + event.clientX - drag.x)))));
190
+ draw();
191
+ });
192
+ function end(keep) {
193
+ if (!drag) return;
194
+ const { img, host, before, had } = drag;
195
+ drag = null;
196
+ document.documentElement.removeAttribute("data-ge-cellpic-resize");
197
+ if (!keep) {
198
+ if (had === null) img.removeAttribute("width");
199
+ else img.setAttribute("width", had);
200
+ } else if (img.getAttribute("width") !== had) recordStep(host, before);
201
+ draw();
202
+ }
203
+ handle.addEventListener("pointerup", () => end(true));
204
+ handle.addEventListener("pointercancel", () => end(false));
205
+
206
+ // ---------- pictures pasted ----------
207
+
208
+ const sent = async (blob, name) => (await upload("image", { name, type: blob.type, data: await blobBase64(blob) })).path;
209
+
210
+ /**
211
+ * Picture files pasted into a cell (a screenshot, a copied picture), each sent to the document's
212
+ * folder as an image block's (10 MB at most): the cell's pictures, in order, those that could not
213
+ * go said.
214
+ */
215
+ async function fromFiles(files) {
216
+ const pictures = [...files].filter((file) => /^image\//.test(file.type));
217
+ if (pictures.some((file) => file.size > UPLOAD_LIMITS.image)) say(t("图片太大(最多 {0})。", sizeLimit("image")), "error");
218
+ const made = await Promise.all(pictures.filter((file) => file.size <= UPLOAD_LIMITS.image).map(async (file) => {
219
+ try {
220
+ return cellPicture(document, await sent(file, file.name || `image.${file.type.split("/")[1] ?? "png"}`), { alt: "" });
221
+ } catch (error) {
222
+ say(t("{0}:{1}", t("无法上传这张图片"), error.message), "error");
223
+ return null;
224
+ }
225
+ }));
226
+ return made.filter(Boolean);
227
+ }
228
+
229
+ // A pasted picture's address as a file of this document's folder ({ path }), or null.
230
+ async function fileOf(src, from, here) {
231
+ const served = SERVED.exec(src);
232
+ const [report, file] = served ? [decodeURIComponent(served[1]), served[2]] : FOLDER_FILE.test(src) ? [from ?? here, src] : [null, null];
233
+ if (file && report === here) return { path: file };
234
+ const copied = async (address, name) => {
235
+ const response = await fetch(address);
236
+ const blob = response.ok ? await response.blob() : null;
237
+ return blob && /^image\//.test(blob.type) && blob.size <= UPLOAD_LIMITS.image ? { path: await sent(blob, name) } : null;
238
+ };
239
+ if (file && report) return copied(mediaUrl("asset", encodeURIComponent(report), file), file.slice("assets/".length));
240
+ if (DATA.test(src)) return copied(src, "image");
241
+ if (WEB.test(src)) return { path: (await upload("image", { url: src })).path };
242
+ return null;
243
+ }
244
+
245
+ /**
246
+ * The pictures of pasted cells (`htmls`: their inline HTML) as files of this document's folder: a
247
+ * Map of each address to { path } (assets/<file>), or null where none could be had. `from`: the
248
+ * report the page's own cells came from (their assets/ that report's).
249
+ */
250
+ async function resolve(htmls, from = null) {
251
+ const addresses = new Set();
252
+ const template = document.createElement("template");
253
+ for (const html of htmls) {
254
+ if (!html.includes("<img")) continue;
255
+ template.innerHTML = html;
256
+ for (const img of template.content.querySelectorAll("img[src]")) addresses.add(img.getAttribute("src"));
257
+ }
258
+ const found = new Map();
259
+ if (addresses.size === 0) return found;
260
+ const here = thisReport();
261
+ const list = [...addresses];
262
+ if (list.some((src) => !(SERVED.exec(src)?.[1] === here || (FOLDER_FILE.test(src) && (from ?? here) === here)))) say(t("正在下载…"));
263
+ await Promise.all(list.slice(0, MAX_PICTURES).map(async (src) => found.set(src, await fileOf(src, from, here).catch(() => null))));
264
+ return found;
265
+ }
266
+
267
+ /**
268
+ * What pastedLines makes of a pasted picture (`found`: resolve's answer): the cell's picture, its
269
+ * alt and width kept; else a web one a link to it (`links`), any other its alt words (undefined:
270
+ * an element that is no picture).
271
+ */
272
+ const maker = (found, links = true) => (img) => {
273
+ if (img.localName !== "img") return undefined;
274
+ const src = img.getAttribute("src") ?? "";
275
+ const alt = img.getAttribute("alt") ?? "";
276
+ const width = img.getAttribute("width") ?? "";
277
+ const file = found.get(src);
278
+ if (file) return cellPicture(document, file.path, { alt, width: WIDTH.test(width) ? Number(width) : null });
279
+ if (links && WEB.test(src)) {
280
+ const link = document.createElement("a");
281
+ link.setAttribute("href", src);
282
+ link.textContent = alt || nameOf(src);
283
+ return link;
284
+ }
285
+ return alt ? document.createTextNode(alt) : null;
286
+ };
287
+
288
+ return {
289
+ fromFiles, resolve, maker,
290
+ /** The tools' nodes (kept in the top layer with a modal dialog). */
291
+ get nodes() { return [frame, handle].filter((node) => node.isConnected); },
292
+ /** Take them away (the page stops editing). */
293
+ hide() {
294
+ hovered = picked = null;
295
+ if (drag) end(false);
296
+ draw();
297
+ },
298
+ };
299
+ }