@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
@@ -0,0 +1,209 @@
1
+ import { Z } from "../../../shared/layers.mjs";
2
+ import { language, t } from "../../../shared/i18n/index.mjs";
3
+ import { createMenu } from "../menu.mjs";
4
+ import { placeBox } from "../placement.mjs";
5
+ import { addStyle, button, element, mount } from "../ui.mjs";
6
+
7
+ // A card's 设置… (docs/render.md "A custom page"): a small form of the fields its block's manifest
8
+ // lists (`settings`: text, number, select, date, switch, document), Notion's popup look (the editor's
9
+ // palette, look.mjs), standing under the card's grip (its right edge at the grip's). What it shows is what the card's settings part holds,
10
+ // each field its manifest's default where it holds none; 保存 (or Enter in a field) hands the values
11
+ // that changed to `save`, 取消, Escape or a press outside it nothing. Its buttons are the editor's flat
12
+ // ones (CLAUDE.md 界面): no line round either, 取消 with no fill until the pointer is on it (grey), 保存 on
13
+ // a pale blue fill of its own. A document is chosen from the
14
+ // workspace's documents by title (the workbench's outline: GET /__gallery/workspace/outline, its web
15
+ // links no documents), the slug what is kept. A choice from a list (select, document) is a field that
16
+ // opens the editor's own menu card under it (menu.mjs: rows, the chosen one checked, ↑/↓ Enter Escape from
17
+ // the field), not a native <select>: the page cannot style that one's popup, which a browser draws in
18
+ // the system's colours (a white list with pale words in a dark page, the user's picture of 2026-11-09).
19
+ // While the form stands, the page is not shown again under it (the board's idle).
20
+
21
+ const STYLE = `
22
+ .ge-board-form { position: fixed; left: 0; top: 0; z-index: ${Z.menu}; box-sizing: border-box; width: 300px; max-height: calc(100vh - 24px); overflow-y: auto; padding: 12px 12px 10px;
23
+ border-radius: 10px; background: var(--ge-bg); color: var(--ge-tx); box-shadow: var(--ge-shadow-pop);
24
+ font: 14px/1.4 ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Display", "Segoe UI", Helvetica, "PingFang SC", "Microsoft YaHei", Arial, sans-serif; }
25
+ .ge-board-form-title { display: flex; align-items: baseline; gap: 8px; margin: 0 0 10px; font-size: 14px; font-weight: 600; }
26
+ .ge-board-form-title small { min-width: 0; overflow: hidden; color: var(--ge-tx3); font-size: 12px; font-weight: 400; text-overflow: ellipsis; white-space: nowrap; }
27
+ .ge-board-field { display: block; margin: 0 0 10px; }
28
+ .ge-board-field > span { display: block; margin: 0 0 4px; color: var(--ge-tx2); font-size: 12px; }
29
+ .ge-board-field :is(input, .ge-board-pick) { box-sizing: border-box; width: 100%; height: 28px; margin: 0; padding: 0 8px; border: 0; border-radius: 6px; outline: none;
30
+ background: var(--ge-field); color: var(--ge-tx); font: inherit; box-shadow: inset 0 0 0 1px var(--ge-edge); }
31
+ .ge-board-field :is(input, .ge-board-pick):focus, .ge-board-pick[aria-expanded="true"] { box-shadow: inset 0 0 0 1px var(--ge-focus), 0 0 0 2px var(--ge-halo); }
32
+ .ge-board-pick { display: flex; align-items: center; gap: 6px; text-align: left; cursor: pointer; }
33
+ .ge-board-pick-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
34
+ .ge-board-pick svg { flex: none; display: block; width: 16px; height: 16px; fill: none; stroke: var(--ge-tx3); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
35
+ .ge-board-switch { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 10px; }
36
+ .ge-board-switch > span { color: var(--ge-tx); font-size: 14px; }
37
+ .ge-board-switch button { position: relative; flex: none; width: 30px; height: 18px; padding: 0; border: 0; border-radius: 9px; background: var(--ge-line-strong); cursor: pointer; transition: background .12s; }
38
+ .ge-board-switch button::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgb(15 15 15 / 20%); transition: transform .12s; }
39
+ .ge-board-switch button[aria-checked="true"] { background: var(--ge-accent); }
40
+ .ge-board-switch button[aria-checked="true"]::after { transform: translateX(12px); }
41
+ .ge-board-switch button:focus-visible { outline: 2px solid var(--ge-halo); }
42
+ .ge-board-form-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
43
+ .ge-board-form-foot button { height: 28px; padding: 0 12px; border: 0; border-radius: 6px; background: transparent; color: var(--ge-tx); font: inherit; cursor: pointer; }
44
+ .ge-board-form-foot button:hover { background: var(--ge-hover); }
45
+ .ge-board-form-foot button[data-primary] { background: var(--ge-btn-primary); color: var(--ge-btn-primary-text); }
46
+ .ge-board-form-foot button[data-primary]:hover { background: var(--ge-btn-primary-hover); }
47
+ .ge-board-form-foot button:focus-visible { outline: 2px solid var(--ge-halo); }
48
+ @media (prefers-reduced-motion: reduce) { .ge-board-switch :is(button, button::after) { transition: none; } }
49
+ `;
50
+
51
+ const CHEVRON = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>';
52
+ const said = (words) => (typeof words === "string" ? words : words?.[language] ?? words?.zh ?? words?.en ?? "");
53
+ let shown = null; // the form standing now: { node, close }
54
+
55
+ /** Whether the form stands now. */
56
+ export const formOpen = () => shown !== null;
57
+
58
+ /** The workspace's documents, by title: [{ slug, title }] (none where the outline cannot be read). */
59
+ async function documents() {
60
+ try {
61
+ const answer = await (await fetch("/__gallery/workspace/outline")).json();
62
+ return Object.values(answer.reports ?? {}).filter((report) => !report.instructions && report.kind !== "link").map(({ slug, title }) => ({ slug, title: title || slug }))
63
+ .sort((a, b) => a.title.localeCompare(b.title, "zh-CN"));
64
+ } catch {
65
+ return [];
66
+ }
67
+ }
68
+
69
+ /**
70
+ * The form for a card: `fields`, its manifest's settings; `values`, what its settings part holds;
71
+ * `name`, its block's name; `anchor`, the rect it stands by; `save(changed)`, the values that changed
72
+ * by key. Closes another that stands.
73
+ */
74
+ export async function openSettings({ fields, values, name, anchor, save }) {
75
+ addStyle(STYLE);
76
+ shown?.close();
77
+ const kinds = new Map();
78
+ const pick = createMenu(); // the list of the field being chosen from (one at a time)
79
+ let picking = null; // the field it stands under
80
+ const node = element("form", { class: "ge-board-form", role: "dialog", "aria-label": t("卡片设置"), novalidate: "" });
81
+ node.append(element("div", { class: "ge-board-form-title" }, element("span", { text: t("设置") }), element("small", { text: said(name) })));
82
+ const listed = fields.some((field) => field.type === "document") ? await documents() : [];
83
+ const now = (field) => (Object.hasOwn(values ?? {}, field.key) ? values[field.key] : field.default);
84
+ for (const field of fields) {
85
+ const label = said(field.label) || field.key;
86
+ const value = now(field);
87
+ if (field.type === "switch") {
88
+ const toggle = element("button", { type: "button", role: "switch", "aria-checked": String(value === true), "aria-label": label, "data-key": field.key });
89
+ toggle.addEventListener("click", () => toggle.setAttribute("aria-checked", String(toggle.getAttribute("aria-checked") !== "true")));
90
+ node.append(element("div", { class: "ge-board-switch" }, element("span", { text: label }), toggle));
91
+ kinds.set(field.key, { field, read: () => toggle.getAttribute("aria-checked") === "true" });
92
+ continue;
93
+ }
94
+ let control;
95
+ const choice = field.type === "select" || field.type === "document"; // chosen from a list
96
+ if (choice) {
97
+ const options = field.type === "select" ? field.options.map((option) => [String(option), String(option)]) : [["", t("(未选择)")], ...listed.map(({ slug, title }) => [slug, title])];
98
+ if (field.type === "document" && value && !listed.some(({ slug }) => slug === value)) options.push([String(value), String(value)]);
99
+ const text = element("span", { class: "ge-board-pick-text" });
100
+ control = element("button", { type: "button", class: "ge-board-pick", "data-key": field.key, "aria-haspopup": "menu", "aria-expanded": "false" }, text, element("span", { html: CHEVRON }));
101
+ // What the field holds is its `value` (the key of an option), shown as the option's words.
102
+ const show = (key) => {
103
+ control.value = key;
104
+ text.textContent = options.find(([option]) => option === key)?.[1] ?? key;
105
+ };
106
+ show(value === undefined || value === null ? (field.type === "select" ? String(field.options[0]) : "") : String(value));
107
+ const open = () => {
108
+ const wide = control.getBoundingClientRect();
109
+ picking = control;
110
+ control.setAttribute("aria-expanded", "true");
111
+ pick.open(options.map(([key, words]) => ({ label: words, selected: key === control.value, run: () => show(key) })), wide, {
112
+ label, width: Math.round(wide.width), icons: false, height: 280,
113
+ onClose: () => { if (picking === control) picking = null; control.setAttribute("aria-expanded", "false"); },
114
+ });
115
+ // The chosen row highlighted where there is one (the first otherwise: the menu's own).
116
+ const chosen = options.findIndex(([key]) => key === control.value);
117
+ for (let step = 0; step < Math.max(0, chosen); step++) pick.move(1);
118
+ };
119
+ control.addEventListener("click", () => { if (picking === control) pick.close(); else open(); });
120
+ control.addEventListener("keydown", (event) => {
121
+ if (event.isComposing || event.keyCode === 229) return;
122
+ if (picking !== control) {
123
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
124
+ event.preventDefault();
125
+ open();
126
+ }
127
+ return;
128
+ }
129
+ if (pick.arrow(event)) event.preventDefault();
130
+ else if (event.key === "Enter" || event.key === " ") {
131
+ event.preventDefault();
132
+ pick.choose();
133
+ } else if (event.key === "Escape") {
134
+ event.preventDefault();
135
+ event.stopPropagation(); // the list closes; the form stays
136
+ pick.close();
137
+ } else if (event.key === "Tab") pick.close();
138
+ });
139
+ } else {
140
+ control = element("input", { type: field.type === "number" ? "number" : field.type === "date" ? "date" : "text", "data-key": field.key, autocomplete: "off" });
141
+ control.value = value === undefined || value === null ? "" : String(value);
142
+ }
143
+ // A field to choose from is no <label> (a click on its words would open its list): its words name it by aria-labelledby.
144
+ const words = element("span", { text: label });
145
+ if (choice) {
146
+ words.id = `ge-board-label-${field.key}`;
147
+ control.setAttribute("aria-labelledby", words.id);
148
+ }
149
+ node.append(element(choice ? "div" : "label", { class: "ge-board-field" }, words, control));
150
+ kinds.set(field.key, {
151
+ field,
152
+ read: () => {
153
+ if (field.type === "number") return control.value.trim() === "" ? null : Number(control.value);
154
+ if (field.type === "select") return field.options.find((option) => String(option) === control.value) ?? field.options[0];
155
+ return control.value;
156
+ },
157
+ });
158
+ }
159
+ const close = () => {
160
+ if (shown?.node !== node) return;
161
+ shown = null;
162
+ pick.close();
163
+ node.remove();
164
+ removeEventListener("pointerdown", outside, true);
165
+ };
166
+ // What a field showed as it opened, as it reads it back (a field the part holds nothing of shows its default, or nothing).
167
+ const shownAt = (field) => now(field) ?? (field.type === "switch" ? false : field.type === "select" ? field.options[0] : field.type === "number" ? null : "");
168
+ // Only what the reader changed goes: a field left as it showed stays as the part holds it (its default unwritten).
169
+ const done = () => {
170
+ const changed = {};
171
+ for (const [key, { field, read }] of kinds) {
172
+ const value = read();
173
+ if (field.type === "number" && value !== null && !Number.isFinite(value)) continue;
174
+ if (JSON.stringify(value) !== JSON.stringify(shownAt(field))) changed[key] = value;
175
+ }
176
+ close();
177
+ if (Object.keys(changed).length) save(changed);
178
+ };
179
+ const outside = (event) => {
180
+ if (!node.contains(event.target) && !pick.contains(event.target)) close();
181
+ };
182
+ node.addEventListener("submit", (event) => {
183
+ event.preventDefault();
184
+ done();
185
+ });
186
+ node.addEventListener("keydown", (event) => {
187
+ if (event.isComposing || event.keyCode === 229) return;
188
+ if (event.key === "Escape") {
189
+ event.preventDefault();
190
+ event.stopPropagation();
191
+ close();
192
+ } else if (event.key === "Enter" && event.target.localName === "input") {
193
+ event.preventDefault();
194
+ done();
195
+ }
196
+ });
197
+ node.append(element("div", { class: "ge-board-form-foot" }, button(t("取消"), close), element("button", { type: "submit", "data-primary": "", text: t("保存") })));
198
+ mount(node);
199
+ const { left, top } = placeBox(anchor, { width: node.offsetWidth, height: node.offsetHeight }, { placement: "bottom", align: "end", gap: 6 });
200
+ node.style.left = `${Math.round(left)}px`;
201
+ node.style.top = `${Math.round(top)}px`;
202
+ shown = { node, close };
203
+ addEventListener("pointerdown", outside, true);
204
+ node.querySelector("input, .ge-board-pick, button[role=switch]")?.focus({ preventScroll: true });
205
+ return node;
206
+ }
207
+
208
+ /** The form taken away, whatever it holds. */
209
+ export const closeSettings = () => shown?.close();
@@ -105,7 +105,7 @@ export function caretAtOffset(host, offset) {
105
105
  rest -= node.length;
106
106
  last = node;
107
107
  }
108
- if (last) caretAt(host, "end");
108
+ caretAt(host, last ? "end" : "start"); // past its text, or in a block with none (emptied)
109
109
  }
110
110
 
111
111
  /** The selection when it is non-empty and inside a single block: { host, range }, or null. */
@@ -1,13 +1,14 @@
1
1
  import { t } from "../../../shared/i18n/index.mjs";
2
2
  import { Z } from "../../../shared/layers.mjs";
3
3
  import { PANEL_STYLE, onPanels, panelGone, panelShown } from "../panel.mjs";
4
- import { addStyle, button, element, fadeIn, fadeOut, mount } from "../ui.mjs";
4
+ import { addStyle, element, fadeIn, fadeOut, mount, textButton } from "../ui.mjs";
5
5
 
6
6
  // The bar of the Agent's changes (docs/editor.md "Agent's changes"), Cursor's review bar: the third of
7
7
  // the editor's bottom panels (panel.mjs), in the middle of the window's bottom, so that a notice
8
8
  // stands above it as above the others. It says how many blocks the Agent added and changed (+) and
9
9
  // changed and deleted (−), the numbers in the conflict card's green and red, and offers 撤回 and 确认
10
- // (the primary one), buttons with no border, as the format bar's. Reverted, it says so and offers
10
+ // (the primary one: the pale blue), in the editor's one button look (look.mjs BUTTONS: no line, no fill
11
+ // until the pointer is on it). Reverted, it says so and offers
11
12
  // 撤销 for UNDO_SHOWN. A conflict card at the left or the lost texts at the right reaching under it
12
13
  // lift it 8px above them.
13
14
 
@@ -21,15 +22,6 @@ const STYLE = `
21
22
  .ge-changes-count { display: inline-flex; gap: 6px; margin-right: 4px; font-variant-numeric: tabular-nums; font-weight: 500; }
22
23
  .ge-changes-plus { color: var(--ge-diff-add-text); }
23
24
  .ge-changes-minus { color: var(--ge-diff-del-text); }
24
- /* Its buttons as the editor's floating bars' (the format bar's 转换为): no border, 6px corners, a gray under the
25
- pointer, darker pressed; 确认 the primary blue (the workbench dialog's, the media menu's 嵌入链接). */
26
- .ge-changes button { height: 28px; padding: 0 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-weight: 500; white-space: nowrap; cursor: pointer; transition: background 20ms ease-in; }
27
- .ge-changes button:hover:not(:disabled) { background: var(--ge-hover); }
28
- .ge-changes button:active:not(:disabled) { background: var(--ge-press); }
29
- .ge-changes button[data-primary] { background: var(--ge-accent); color: #fff; }
30
- .ge-changes button[data-primary]:hover:not(:disabled) { background: var(--ge-accent-hover); }
31
- .ge-changes button[data-primary]:active:not(:disabled) { background: var(--ge-accent-press); }
32
- .ge-changes button:disabled { opacity: .45; cursor: default; }
33
25
  @media print { .ge-changes { display: none; } }
34
26
  `;
35
27
 
@@ -80,15 +72,15 @@ export function createBar({ revert, accept }) {
80
72
  element("span", { class: "ge-changes-count", title: t("Agent 新增 {0} 块、改动 {1} 块、删除 {2} 块", added, changed, removed) },
81
73
  ...(plus ? [element("span", { class: "ge-changes-plus", text: `+${plus}` })] : []),
82
74
  ...(minus ? [element("span", { class: "ge-changes-minus", text: `−${minus}` })] : [])),
83
- button(t("撤回"), revert, { title: t("撤回 Agent 的这些修改,你自己的修改会保留"), "data-action": "revert" }),
84
- button(t("确认"), accept, { primary: true, title: t("保留这些修改,不再标出"), "data-action": "accept" }),
75
+ textButton(t("撤回"), revert, { title: t("撤回 Agent 的这些修改,你自己的修改会保留"), "data-action": "revert" }),
76
+ textButton(t("确认"), accept, { primary: true, title: t("保留这些修改,不再标出"), "data-action": "accept" }),
85
77
  );
86
78
  },
87
79
  // Reverted `left` ms ago at most: 撤销 offered until UNDO_SHOWN is over.
88
80
  reverted(left, undo) {
89
81
  show(
90
82
  element("span", { class: "ge-changes-what", text: t("已撤回 Agent 的修改") }),
91
- button(t("撤销"), undo, { "data-action": "undo" }),
83
+ textButton(t("撤销"), undo, { "data-action": "undo" }),
92
84
  );
93
85
  timer = setTimeout(() => this.hide(), Math.max(0, left));
94
86
  },
@@ -361,7 +361,7 @@ export function createChanges({ slug, token, commits, autosave, flush, say, sess
361
361
  answer = await postAnswer(ROUTE, token, { slug, action: "revert", stamp });
362
362
  if (!answer.ok || answer.none) return false;
363
363
  session?.set(undoKey, JSON.stringify({ undo: answer.undo, at: Date.now() }));
364
- return true;
364
+ return answer.version || true; // the version it wrote: the page shown again on another asks what that put back
365
365
  });
366
366
  if (shown) return; // the page goes: the one shown again offers 撤销
367
367
  busy = false;
@@ -381,7 +381,7 @@ export function createChanges({ slug, token, commits, autosave, flush, say, sess
381
381
  let answer = null;
382
382
  const shown = await autosave().showOwn(async () => {
383
383
  answer = await postAnswer(ROUTE, token, { slug, action: "undo", undo: id });
384
- return Boolean(answer.ok);
384
+ return answer.ok ? answer.version || true : false;
385
385
  });
386
386
  if (shown) return;
387
387
  busy = false;
@@ -332,7 +332,11 @@ function equationOf(el) {
332
332
  return el.getAttribute(ATTR.equation) || el.getAttribute(ATTR.inlineEquation) || annotation || /^\$\$([\s\S]+?)\$\$\s*$/.exec(text)?.[1].trim() || /^\$([\s\S]+?)\$$/.exec(text)?.[1].trim();
333
333
  }
334
334
 
335
- function richTextOf(node, { stripText = false, stripSurroundingWhitespace = false, ignoreCodeBlock = false, preserveLineBreaks = false } = {}) {
335
+ // Besides Notion's: a picture in a table's cell (`pictures`) is a token of its own, PICTURE annotated
336
+ // ["img", its address, its alt, "", its width] (inline.mjs's pieces, the width of an <img width>), which
337
+ // a cell of the page shows (to-blocks.mjs richHtml).
338
+ const PICTURE = String.fromCodePoint(0xfffc);
339
+ function richTextOf(node, { stripText = false, stripSurroundingWhitespace = false, ignoreCodeBlock = false, preserveLineBreaks = false, pictures = false } = {}) {
336
340
  const doc = node.ownerDocument;
337
341
  if (!stripText) {
338
342
  // Paragraphs, headings and items as lines with their bullets (the structure kept in one text).
@@ -354,17 +358,18 @@ function richTextOf(node, { stripText = false, stripSurroundingWhitespace = fals
354
358
  all.push(n);
355
359
  for (const child of n.childNodes) walk(child);
356
360
  })(node);
357
- const nodes = all.filter((n) => (n.nodeType !== 3 ? preserveLineBreaks && tagOf(n) === "br" : !(stripText && BLANK_LINES.test(n.data))));
361
+ const picture = (n) => pictures && tagOf(n) === "img" && Boolean(n.getAttribute("src"));
362
+ const nodes = all.filter((n) => (n.nodeType !== 3 ? (preserveLineBreaks && tagOf(n) === "br") || picture(n) : !(stripText && BLANK_LINES.test(n.data))));
358
363
  const tokens = [];
359
364
  const seenEquations = new Set();
360
365
  nodes.forEach((n, i) => {
361
- let text = n.nodeType === 3 ? n.data : "\n";
366
+ let text = n.nodeType === 3 ? n.data : picture(n) ? PICTURE : "\n";
362
367
  if (stripText) text = text.replace(/\[ \] /g, "").replace(/\[x\] /g, "").replace(/^[-*•]\s?/g, "").replace(/(\s)+/g, "$1");
363
368
  if (i === 0 && stripSurroundingWhitespace && !node.hasAttribute?.("confluenceTabSpaces")) text = text.trimStart();
364
369
  text = clean(text);
365
370
  if (i === nodes.length - 1 && stripSurroundingWhitespace) text = text.trimEnd();
366
371
  const up = ancestors(n).filter((a) => a.nodeType === 1);
367
- const annotations = [];
372
+ const annotations = picture(n) ? [["img", n.getAttribute("src"), n.getAttribute("alt") ?? "", "", n.getAttribute("width") ?? ""]] : [];
368
373
  if (!stripText && up.some((a) => HEADINGS.includes(tagOf(a)))) annotations.push(["b"]);
369
374
  if (up.some((a) => tagOf(a) === "strong")) annotations.push(["b"]);
370
375
  if (up.some((a) => tagOf(a) === "em")) annotations.push(["i"]);
@@ -526,7 +531,8 @@ const int = (value, fallback) => {
526
531
  };
527
532
 
528
533
  // 760354 table: row and column spans spread into empty cells; a first row all th a header row, every
529
- // row starting with a th a header column (both: the side with more th's).
534
+ // row starting with a th a header column (both: the side with more th's). Its cells' pictures kept
535
+ // (richTextOf `pictures`, besides Notion's).
530
536
  function tableBlock(node) {
531
537
  const rows = [...node.querySelectorAll("tr")].filter((row) => row.closest("table") === node);
532
538
  let headerRow = false;
@@ -550,7 +556,7 @@ function tableBlock(node) {
550
556
  while (taken[r][c]) c++;
551
557
  const rowSpan = int(cell.getAttribute("rowspan"), 1);
552
558
  const colSpan = int(cell.getAttribute("colspan"), 1);
553
- grid[r][c] = richTextOf(cell, { stripSurroundingWhitespace: true, preserveLineBreaks: true });
559
+ grid[r][c] = richTextOf(cell, { stripSurroundingWhitespace: true, preserveLineBreaks: true, pictures: true });
554
560
  taken[r][c] = true;
555
561
  for (let dr = 0; dr < rowSpan && r + dr < rows.length; dr++) {
556
562
  for (let dc = 0; dc < colSpan; dc++) {
@@ -716,6 +722,23 @@ function blockOf(node) {
716
722
 
717
723
  const blocksOf = (nodes) => nodes.flatMap(blockOf);
718
724
 
725
+ /**
726
+ * Besides Notion's (another program's cells pasted into a table's: page/clipboard/ cellsOf): the
727
+ * HTML's table, when it holds one and no words outside it, as rows of rich text, its cells' as
728
+ * tableBlock reads them (spans spread into empty cells, pictures kept); a table of one column stays
729
+ * one (normalizing makes Notion's paragraphs of it). Null for anything else.
730
+ */
731
+ export function tableRows(root, options = {}) {
732
+ const tables = [...root.querySelectorAll("table")].filter((table) => !table.parentElement?.closest("table"));
733
+ const outside = (n) => (n.nodeType === 3 ? n.data.trim() !== "" : n.nodeType === 1 && tagOf(n) !== "table" && !REMOVED.has(tagOf(n)) && [...n.childNodes].some(outside));
734
+ if (tables.length !== 1 || [...root.childNodes].some(outside)) return null;
735
+ tables[0].classList.add("simple-table"); // kept a table (firstPass)
736
+ normalizeHTML(root, { ...options, inline: false });
737
+ const table = root.querySelector("table");
738
+ const block = table ? tableBlock(table) : null;
739
+ return block ? block.children.map((row) => block.props.table_block_column_order.map((column) => row.props.cells[column] ?? [])) : null;
740
+ }
741
+
719
742
  // ---------- entry points ----------
720
743
 
721
744
  /** 760354 U: the HTML's blocks. */
@@ -27,8 +27,14 @@
27
27
  // and a bare a@b.c (GFM's); $$…$$ amid words a display formula (annotated display too, which
28
28
  // markdown.mjs makes a block of its own in a paragraph).
29
29
  // Besides Notion's annotations, these three: sup (superscript), sub (subscript) and display.
30
+ //
31
+ // Yuque's Markdown (its toolbar's 复制为 Markdown, read as a whole document with `yuque`: yuque.mjs)
32
+ // reads two more: a formula as Yuque writes one, $ … $ with a space inside each $ (Pandoc's rule
33
+ // refuses those), and the colors of a <font style="color: …; background-color: …"> (or a <span> so
34
+ // styled) as the page's nearest (colors.mjs: Yuque's colors are meant, as Word's are).
30
35
 
31
36
  import { addressOf } from "../../links/address.mjs";
37
+ import { backgroundColor, textColor } from "./colors.mjs";
32
38
 
33
39
  const WORD = /[\p{L}\p{N}]/u;
34
40
  const TRAILING = /[.,:;!?'")\]}>,。:;!?、)】」』]+$/u;
@@ -100,6 +106,20 @@ const attribute = (tag, name) => {
100
106
  };
101
107
  const decodeEntities = (text) => text.replace(/&(#\d{1,7}|#[xX][0-9a-fA-F]{1,6}|[A-Za-z][A-Za-z0-9]{1,31});/g, (all, name) => entity(name) ?? all);
102
108
 
109
+ // Yuque's Markdown: $ … $ with a space inside each $ a formula (its words trimmed), and a <font> or
110
+ // <span> whose style colors its words those colors, the nearest of the page's (none: its words alone).
111
+ const styleValue = (style, name) => new RegExp(`(?:^|;)\\s*${name}\\s*:\\s*([^;]+)`, "i").exec(style ?? "")?.[1].trim() ?? "";
112
+ const YUQUE_RULES = [
113
+ [/^\$ +([^$\n]*?[^\s$]) +\$(?!\d)/, (m) => ["⁍", ["e", m[1]], false]],
114
+ [/^<(font|span)\s([^<>]*)>([\s\S]*?)<\/\1\s*>/i, (m) => {
115
+ const style = attribute(`<${m[1]} ${m[2]}>`, "style");
116
+ const text = textColor(styleValue(style, "color"), { nearest: true });
117
+ const background = backgroundColor(styleValue(style, "background(?:-color)?"), { nearest: true });
118
+ const colors = [text, background].filter(Boolean).map((name) => ["h", name]);
119
+ return [m[3], colors.length ? colors : null, true];
120
+ }],
121
+ ];
122
+
103
123
  /** A picture's piece: U+FFFC annotated ["img", its address, its alt, its title]. */
104
124
  const picture = (src, alt, title) => ["\u{FFFC}", ["img", src.replace(/^<|>$/g, ""), alt ?? "", title ?? ""], false];
105
125
  const unquoted = (text) => (text ? text.slice(1, -1) : "");
@@ -123,6 +143,7 @@ function documentRules(doc) {
123
143
  }
124
144
  };
125
145
  const rules = [
146
+ ...(doc.yuque ? YUQUE_RULES : []),
126
147
  RULES[0], RULES[1],
127
148
  // $$ … $$ amid words: a display formula (markdown.mjs makes it a block of its own in a paragraph).
128
149
  [/^\$\$(.+?)\$\$/, (m) => ["⁍", [["e", m[1].trim()], ["display"]], false]],
@@ -185,6 +206,17 @@ export const parseInlineMath = (text) => parseInline(text, MATH_RULES);
185
206
  */
186
207
  export const parseInlineMarkdown = (text, doc = null) => parseInline(text, doc ? (doc.rules ??= documentRules(doc)) : RULES); // a document's made once
187
208
 
209
+ // A table cell's pictures, a linked one's too (its link left: the cell's picture is what it shows),
210
+ // ahead of Notion's rules (a paste's Markdown table: 单元格里的图片, which a cell holds).
211
+ const CELL_RULES = [
212
+ [new RegExp(String.raw`^\[!\[([^\]]*)\]${LINK_TARGET}\]${LINK_TARGET}`), (m) => picture(m[2], m[1], unquoted(m[3]))],
213
+ [new RegExp(String.raw`^!\[([^\]]*)\]${LINK_TARGET}`), (m) => picture(m[2], m[1], unquoted(m[3]))],
214
+ ...RULES,
215
+ ];
216
+
217
+ /** A pasted Markdown table's cell as rich text: Notion's rules, and its pictures ![alt](src) (inline.mjs's picture pieces). */
218
+ export const parseCellMarkdown = (text) => parseInline(text, CELL_RULES);
219
+
188
220
  /** HTML entities in text as their characters (&amp; &#8212;), one it does not know as written. */
189
221
  export { decodeEntities };
190
222
 
@@ -1,5 +1,6 @@
1
+ import { CELL_ALIGN_CLASSES } from "../../../../shared/components/table.mjs";
1
2
  import { languageFromFence } from "./languages.mjs";
2
- import { decodeEntities, parseInlineMarkdown, unescapeMarkdown } from "./inline.mjs";
3
+ import { decodeEntities, parseCellMarkdown, parseInlineMarkdown, unescapeMarkdown } from "./inline.mjs";
3
4
  import { spec } from "./rich.mjs";
4
5
 
5
6
  // Markdown and plain text as block specs (notion-lite editor/input/clipboard/parse.js). Notion
@@ -52,8 +53,9 @@ import { spec } from "./rich.mjs";
52
53
  // which holds paragraphs alone, noted; GitHub's alerts (> [!NOTE], [!TIP],
53
54
  // [!IMPORTANT], [!WARNING], [!CAUTION]) callouts
54
55
  // pictures a paragraph's pictures blocks of their own, the text around them paragraphs; an
55
- // item's blocks it holds; elsewhere (a heading, a cell, a disclosure's title) a
56
- // picture of the web a link, any other its alt text, noted
56
+ // item's blocks it holds; a table cell's its own, amid its words (单元格里的图片: the
57
+ // import makes each a file of the page's, transfer/import.mjs); elsewhere (a heading,
58
+ // a disclosure's title) a picture of the web a link, any other its alt text, noted
57
59
  // HTML <details><summary> … </details> a toggle, its blocks read as Markdown; an HTML block
58
60
  // of pictures alone (<p align="center"><img …></p>, a link around them) its picture
59
61
  // blocks, of an <hr> a divider, of line breaks or empty tags nothing; any other HTML
@@ -71,6 +73,18 @@ import { spec } from "./rich.mjs";
71
73
  // `firstLine`): `notes` [{ line, kind, text }]; what the import tries or looks for afterwards is
72
74
  // returned with its line too: the formulas (`formulas`, KaTeX draws them or not) and the links to
73
75
  // addresses that are no web ones (`links`: a file beside the document, a place in it).
76
+ //
77
+ // Yuque's Markdown (`yuque`: its toolbar's 复制为 Markdown, which yuque.mjs reads so; never the
78
+ // import's) is that, and what Yuque writes besides:
79
+ // :::info … ::: its 提示 (info, tips, success, warning, danger, color1–5; up to a line of ::: alone),
80
+ // a spec { type: "report_callout", props: { alert } } of the blocks between
81
+ // HTML every HTML block a spec { type: "html", props: { html } } (yuque.mjs reads it as the
82
+ // paste reads HTML: a picture's alt is no caption, Yuque's being a file's name)
83
+ // tables a row of more cells than its header's (Yuque writes a | in a cell as it is) its cells
84
+ // past the header's last one that one's, joined by |; the delimiter row's alignments
85
+ // each cell's (props.looks: doc-align-left / center / right)
86
+ // code a ```mermaid fence code, never a flow
87
+ // inline inline.mjs's: $ … $ with spaces inside, <font style> colors
74
88
 
75
89
  // A callout's first line: an emoji first is its icon, then its text.
76
90
  const EMOJI = /^(\p{Extended_Pictographic}(?:[\u{FE0F}\u{200D}\u{1F3FB}-\u{1F3FF}]|\p{Extended_Pictographic})*)\s*/u;
@@ -81,14 +95,23 @@ function callout(head, children, inline = parseInlineMarkdown) {
81
95
  return spec("callout", [], match ? { icon: match[1] } : {}, blocks.length ? blocks : [spec("text")]);
82
96
  }
83
97
 
84
- /** A table spec of rows of rich text (columns c0, c1, …). */
85
- export function tableSpec(rows, header) {
98
+ /** A table spec of rows of rich text (columns c0, c1, …); `looks`, each column's look of its cells (a class, or none); `lines`: each row's line in a document (what the import notes of its pictures is at it). */
99
+ export function tableSpec(rows, header, looks = [], lines = null) {
86
100
  const width = Math.max(1, ...rows.map((row) => row.length));
87
101
  const columns = Array.from({ length: width }, (_, i) => `c${i}`);
102
+ const looked = Object.fromEntries(columns.map((column, i) => [column, looks[i]]).filter(([, look]) => look));
88
103
  return spec("table", [], { table_block_column_order: columns, table_block_column_format: {}, table_block_column_header: header, table_block_row_header: false },
89
- rows.map((row) => spec("table_row", [], { cells: Object.fromEntries(row.map((cell, i) => [columns[i], cell])) })));
104
+ rows.map((row, index) => spec("table_row", [], { cells: Object.fromEntries(row.map((cell, i) => [columns[i], cell])), ...(Object.keys(looked).length ? { looks: looked } : {}), ...(lines ? { line: lines[index] } : {}) })));
90
105
  }
91
106
 
107
+ // A delimiter row's cell's alignment (:--, :-:, --:) as a cell's look (the table's classes: left,
108
+ // center, right), or null.
109
+ const [LEFT, CENTER, RIGHT] = CELL_ALIGN_CLASSES;
110
+ const alignOf = (cell) => {
111
+ const text = cell.trim();
112
+ return text.startsWith(":") && text.endsWith(":") ? CENTER : text.startsWith(":") ? LEFT : text.endsWith(":") ? RIGHT : null;
113
+ };
114
+
92
115
  const indentOf = (raw) => raw.match(/^[ \t]*/)[0].replace(/\t/g, " ").length;
93
116
  const dedent = (lines, n) => lines.map((line) => line.replace(new RegExp(`^[ \\t]{0,${n}}`), ""));
94
117
 
@@ -167,6 +190,8 @@ function parseLines(lines, doc = null, base = 0) {
167
190
  const lineOf = (i) => (doc ? doc.firstLine + base + i : 0);
168
191
  // Inline text: Notion's rules for a paste; a document's rules, its pictures links or words (textOnly), for a document.
169
192
  const inline = (text, i) => (doc ? textOnly(docInline(doc, text, lineOf(i)), lineOf(i), doc) : parseInlineMarkdown(text));
193
+ // A table's cell, which holds pictures (单元格里的图片): a document's rules or a paste's, its pictures kept.
194
+ const cellInline = (text, i) => (doc ? docInline(doc, text, lineOf(i)) : parseCellMarkdown(text));
170
195
  // A list item: Notion's rules for a paste (a page export's line of no words, "&nbsp;", none); a
171
196
  // document's, its pictures blocks it holds (before the blocks indented under it).
172
197
  const item = (type, text, i, props = {}) => {
@@ -196,6 +221,18 @@ function parseLines(lines, doc = null, base = 0) {
196
221
  const language = m[2] ? languageFromFence(m[2]) ?? m[2][0].toUpperCase() + m[2].slice(1) : undefined;
197
222
  block = spec("code", code ? [[code]] : [], language ? { language } : {});
198
223
  }
224
+ } else if (doc?.yuque && (m = YUQUE_ALERT.exec(line))) {
225
+ // Yuque's 提示: its blocks up to a line of ::: alone (one opened in it closing first).
226
+ const start = i;
227
+ const body = [];
228
+ for (let depth = 1, at = i + 1; at < lines.length; at++) {
229
+ const each = lines[at].trim();
230
+ depth += YUQUE_ALERT.test(each) ? 1 : each === ":::" ? -1 : 0;
231
+ i = at;
232
+ if (depth === 0) break;
233
+ body.push(lines[at]);
234
+ }
235
+ block = spec("report_callout", [], { alert: m[1].toLowerCase() }, parseLines(dedent(body, indent), doc, base + start + 1));
199
236
  } else if ((math = displayMathStart(line))) {
200
237
  if (math.single) {
201
238
  block = spec("equation", mathTitle([math.rest.slice(0, -math.close.length)], math.unescape));
@@ -312,7 +349,8 @@ function parseLines(lines, doc = null, base = 0) {
312
349
  }
313
350
  const written = dedent(body, indent).join("\n").trim();
314
351
  const bare = written.replace(/<!--[\s\S]*?-->/g, "").replace(PICTURE_TAGS, "").trim();
315
- if (!bare && /<img\s/i.test(written)) blocks = paragraphBlocks(docInline(doc, written.replace(/\s+/g, " "), lineOf(start)), lineOf(start)); // pictures alone (<p align="center"><img …></p>): picture blocks
352
+ if (doc.yuque) block = spec("html", [], { html: written, line: lineOf(start) }); // read as the paste reads HTML (yuque.mjs)
353
+ else if (!bare && /<img\s/i.test(written)) blocks = paragraphBlocks(docInline(doc, written.replace(/\s+/g, " "), lineOf(start)), lineOf(start)); // pictures alone (<p align="center"><img …></p>): picture blocks
316
354
  else if (!bare && /<hr\b/i.test(written)) block = spec("divider");
317
355
  else if (!bare) continue; // line breaks or empty tags alone: a viewer's spacing
318
356
  else block = spec("html", [], { html: written, line: lineOf(start) });
@@ -342,9 +380,13 @@ function parseLines(lines, doc = null, base = 0) {
342
380
  // GFM's table: its header row, its delimiter row, then its rows.
343
381
  // Each row's cells read at its line (what they leave out noted there).
344
382
  const rows = [[tableCells(lines[i], true), i]];
383
+ const delimiter = tableCells(lines[i + 1], true);
345
384
  for (i += 2; i < lines.length && lines[i].trim() && (/^\s*\|/.test(lines[i]) || !startsBlock(lines[i].trim())); i++) rows.push([tableCells(lines[i], true), i]);
346
385
  i--;
347
- block = tableSpec(rows.map(([row, at]) => row.map((cell) => inline(cell, at))), true);
386
+ // Yuque's: a | it wrote as it is joins the cells past the header's last one to that one; its alignments its cells'.
387
+ const width = rows[0][0].length;
388
+ const fitted = doc.yuque ? rows.map(([row, at]) => [row.length > width ? [...row.slice(0, width - 1), row.slice(width - 1).join("|")] : row, at]) : rows;
389
+ block = tableSpec(fitted.map(([row, at]) => row.map((cell) => cellInline(cell, at))), true, doc.yuque ? delimiter.map(alignOf) : [], fitted.map(([, at]) => lineOf(at)));
348
390
  } else if (/^\|.*\|$/.test(line)) {
349
391
  const rows = [];
350
392
  let header = false;
@@ -354,7 +396,7 @@ function parseLines(lines, doc = null, base = 0) {
354
396
  if (rows.length === 1) header = true;
355
397
  continue;
356
398
  }
357
- rows.push(cells.map((cell) => (doc ? inline(cell, i) : parseInlineMarkdown(cell))));
399
+ rows.push(cells.map((cell) => cellInline(cell, i)));
358
400
  }
359
401
  i--;
360
402
  block = tableSpec(rows, header);
@@ -409,6 +451,8 @@ const DROPPED_HTML = new Set(["script", "style", "textarea"]);
409
451
  // The tags an HTML block of pictures alone is made of (a README's <p align="center"><img …></p>).
410
452
  const PICTURE_TAGS = /<\/?(?:p|div|a|center|picture|source|span|br|img|hr)(?=[\s/>])[^>]*>/gi;
411
453
  const ALERTS = { note: "ℹ️", tip: "💡", important: "❗", warning: "⚠️", caution: "🚨" };
454
+ // Yuque's 提示 opened (its Markdown's): ::: and its kind, alone on its line.
455
+ const YUQUE_ALERT = /^:::[ \t]*([A-Za-z][\w-]*)[ \t]*$/;
412
456
  const LINK_DEFINITION = /^\[([^\]^][^\]]*)\]:\s*(<[^>]*>|\S+)(?:\s+("[^"]*"|'[^']*'|\([^)]*\)))?\s*$/;
413
457
  const FOOTNOTE_DEFINITION = /^\[\^([^\]\s]+)\]:\s?(.*)$/;
414
458
  // East Asian wide characters (CJK, kana, Hangul, their punctuation and full-width forms): a line
@@ -583,7 +627,7 @@ function documentCode(name, meta, code, line, doc) {
583
627
  const braces = /^\{([^}]*)\}/.exec(meta.trim());
584
628
  const fence = name || /\.([\w+#-]+)/.exec(braces?.[1] ?? "")?.[1] || "";
585
629
  const title = /(?:^|[\s{])title=(?:"([^"]*)"|'([^']*)'|([^\s}]+))/.exec(meta);
586
- if (fence.toLowerCase() === "mermaid") {
630
+ if (fence.toLowerCase() === "mermaid" && !doc.yuque) {
587
631
  const steps = flowSteps(code);
588
632
  if (steps) return spec("flow", [], { steps, line });
589
633
  doc.notes.push({ line, kind: "mermaid", text: "a mermaid diagram that is not one chain of steps is kept as its code" });
@@ -729,11 +773,12 @@ function takeDefinitions(lines, doc) {
729
773
  * title }] (in order), notes: [{ line, kind, text }], formulas: [{ tex, line, display }] (each
730
774
  * formula read, for the import to try), links: [{ href, line }] (each link to an address that is no
731
775
  * web one: a file beside it, a place in it) }. `firstLine`: the number of its first line in its file
732
- * (after a front matter the import took off).
776
+ * (after a front matter the import took off). `yuque`: Yuque's Markdown, read with what Yuque
777
+ * writes besides (this file's header; a paste's, yuque.mjs).
733
778
  */
734
- export function readMarkdownDocument(text, { firstLine = 1 } = {}) {
779
+ export function readMarkdownDocument(text, { firstLine = 1, yuque = false } = {}) {
735
780
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
736
- const doc = { firstLine, refs: new Map(), definitions: new Map(), order: [], notes: [], formulas: [], links: [], at: firstLine };
781
+ const doc = { firstLine, yuque, refs: new Map(), definitions: new Map(), order: [], notes: [], formulas: [], links: [], at: firstLine };
737
782
  doc.footnote = (label) => {
738
783
  const definition = doc.definitions.get(label.toLowerCase());
739
784
  if (!definition) {
@@ -4,6 +4,7 @@ import { mathifyRichText, mathifySpecs } from "./math.mjs";
4
4
  import { fromMarkdown } from "./markdown.mjs";
5
5
  import { parseInlineMarkdown } from "./inline.mjs";
6
6
  import { vscodeLanguage } from "./languages.mjs";
7
+ import { readYuque } from "./yuque.mjs";
7
8
  import { addressOf } from "../../links/address.mjs";
8
9
 
9
10
  // What a paste from another program is (Notion 860313 clipboardActions' external content, notion-lite
@@ -20,7 +21,8 @@ import { addressOf } from "../../links/address.mjs";
20
21
  // (\[ … \]) → a formula block.
21
22
  // Besides Notion's: formulas AI answers copy (math.mjs, markdown.mjs, inline.mjs) and colors
22
23
  // (feishu-lite: html.mjs `colors`; nearest for Word, Google Docs and Notion, whose styles are meant,
23
- // as Feishu's 129153 · P; else only the page's own).
24
+ // as Feishu's 129153 · P; else only the page's own); and first of all Yuque's (yuque.mjs): its
25
+ // document's nodes (text/ne-inode, its HTML's <ne-clipboard>, text/lake) or its toolbar's Markdown.
24
26
 
25
27
  const OFFICE = /schemas-microsoft-com:office|xmlns:m="http:\/\/schemas\.microsoft\.com\/office\/2004\/12\/omml"/;
26
28
  const WORDPRESS = /<!-- wp:/;
@@ -92,6 +94,9 @@ function sameAddress(one, other) {
92
94
  * text } (notion-lite readClipboard, after the editor's own formats).
93
95
  */
94
96
  export function readExternal(data, options = {}) {
97
+ // Yuque's (yuque.mjs): its document's structure, which its HTML says only in part, or its Markdown.
98
+ const yuque = readYuque(data, options);
99
+ if (yuque) return yuque;
95
100
  const external = externalContent(data, options);
96
101
  const { text, html } = external;
97
102
  const convert = { colors: external.colors };