@heycys/gallery 0.1.0 → 0.1.1

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 (237) hide show
  1. package/THIRD-PARTY-NOTICES.md +51 -1
  2. package/browser/editor/components/callout/index.mjs +40 -15
  3. package/browser/editor/components/columns/page.mjs +41 -28
  4. package/browser/editor/components/details/index.mjs +2 -1
  5. package/browser/editor/components/dialog/index.mjs +2 -1
  6. package/browser/editor/components/dialog/page.mjs +0 -18
  7. package/browser/editor/components/divider/index.mjs +1 -1
  8. package/browser/editor/components/figure/page.mjs +10 -1
  9. package/browser/editor/components/file/page.mjs +2 -2
  10. package/browser/editor/components/flowchart/add.mjs +2 -2
  11. package/browser/editor/components/heading/index.mjs +2 -0
  12. package/browser/editor/components/heading/page.mjs +6 -0
  13. package/browser/editor/components/highlight/index.mjs +4 -20
  14. package/browser/editor/components/icons.mjs +5 -2
  15. package/browser/editor/components/image/page.mjs +2 -2
  16. package/browser/editor/components/installed/index.mjs +71 -25
  17. package/browser/editor/components/installed/page.mjs +122 -21
  18. package/browser/editor/components/kit.mjs +3 -0
  19. package/browser/editor/components/media/page.mjs +44 -14
  20. package/browser/editor/components/pages.mjs +2 -2
  21. package/browser/editor/components/registry.mjs +56 -30
  22. package/browser/editor/components/section/index.mjs +2 -1
  23. package/browser/editor/components/set-apart.mjs +24 -0
  24. package/browser/editor/components/table/index.mjs +5 -3
  25. package/browser/editor/components/table/page.mjs +29 -18
  26. package/browser/editor/page/annotate/anchors.mjs +4 -3
  27. package/browser/editor/page/annotate/index.mjs +61 -34
  28. package/browser/editor/page/autosave.mjs +50 -4
  29. package/browser/editor/page/beside.mjs +5 -5
  30. package/browser/editor/page/block-menu/colors.mjs +3 -1
  31. package/browser/editor/page/block-menu/index.mjs +8 -6
  32. package/browser/editor/page/blocks.mjs +16 -7
  33. package/browser/editor/page/caret.mjs +4 -1
  34. package/browser/editor/page/changes/bar.mjs +102 -0
  35. package/browser/editor/page/changes/ghost.mjs +206 -0
  36. package/browser/editor/page/changes/gone-words.mjs +94 -0
  37. package/browser/editor/page/changes/index.mjs +447 -0
  38. package/browser/editor/page/changes/words.mjs +27 -0
  39. package/browser/editor/page/clipboard/import/colors.mjs +3 -3
  40. package/browser/editor/page/clipboard/index.mjs +32 -13
  41. package/browser/editor/page/clipboard/own/blocks.mjs +14 -5
  42. package/browser/editor/page/clipboard/own/describe.mjs +16 -7
  43. package/browser/editor/page/clipboard/own/export.mjs +6 -6
  44. package/browser/editor/page/clipboard/own/selection.mjs +2 -2
  45. package/browser/editor/page/clipboard/own/writers.mjs +0 -11
  46. package/browser/editor/page/columns/index.mjs +1 -1
  47. package/browser/editor/page/commit.mjs +19 -4
  48. package/browser/editor/page/conflicts.mjs +6 -26
  49. package/browser/editor/page/convert.mjs +8 -7
  50. package/browser/editor/page/core.mjs +55 -17
  51. package/browser/editor/page/drag/index.mjs +40 -24
  52. package/browser/editor/page/drag/preview.mjs +9 -4
  53. package/browser/editor/page/editor.mjs +70 -15
  54. package/browser/editor/page/emoji-picker/emoji-tab.mjs +22 -13
  55. package/browser/editor/page/emoji-picker/grid.mjs +7 -5
  56. package/browser/editor/page/emoji-picker/icon-tab.mjs +4 -4
  57. package/browser/editor/page/emoji-picker/icons.mjs +0 -1
  58. package/browser/editor/page/emoji-picker/index.mjs +11 -9
  59. package/browser/editor/page/emoji-picker/styles.mjs +161 -2
  60. package/browser/editor/page/emoji-picker/support.mjs +6 -4
  61. package/browser/editor/page/emoji-picker/upload.mjs +12 -11
  62. package/browser/editor/page/fills.mjs +5 -3
  63. package/browser/editor/page/fold/index.mjs +348 -0
  64. package/browser/editor/page/fold/rule.mjs +125 -0
  65. package/browser/editor/page/folded.mjs +23 -0
  66. package/browser/editor/page/format-bar/bar.mjs +2 -1
  67. package/browser/editor/page/format-bar/index.mjs +1 -1
  68. package/browser/editor/page/geometry.mjs +8 -6
  69. package/browser/editor/page/handle/index.mjs +17 -6
  70. package/browser/editor/page/hints/index.mjs +1 -1
  71. package/browser/editor/page/insert/index.mjs +30 -17
  72. package/browser/editor/page/long-runs.mjs +75 -0
  73. package/browser/editor/page/look.mjs +15 -3
  74. package/browser/editor/page/margin.mjs +45 -24
  75. package/browser/editor/page/menu.mjs +69 -25
  76. package/browser/editor/page/merge.mjs +24 -0
  77. package/browser/editor/page/notices.mjs +1 -1
  78. package/browser/editor/page/offered.mjs +1 -1
  79. package/browser/editor/page/outline/folds.mjs +11 -10
  80. package/browser/editor/page/outline/index.mjs +131 -45
  81. package/browser/editor/page/outline/list.mjs +23 -11
  82. package/browser/editor/page/outline/read.mjs +1 -1
  83. package/browser/editor/page/outline/strip.mjs +7 -1
  84. package/browser/editor/page/panel.mjs +5 -4
  85. package/browser/editor/page/resize/index.mjs +1 -1
  86. package/browser/editor/page/selection/delete.mjs +8 -6
  87. package/browser/editor/page/selection/index.mjs +10 -3
  88. package/browser/editor/page/session.mjs +54 -3
  89. package/browser/editor/page/table/cell-bar.mjs +4 -1
  90. package/browser/editor/page/table/cells.mjs +1 -1
  91. package/browser/editor/page/table/icons.mjs +1 -1
  92. package/browser/editor/page/table/index.mjs +13 -8
  93. package/browser/editor/page/table/widths.mjs +9 -4
  94. package/browser/editor/page/text-select/index.mjs +18 -3
  95. package/browser/editor/page/tooltip.mjs +1 -1
  96. package/browser/editor/page/type-grid.mjs +4 -3
  97. package/browser/editor/page/typing/code-edges.mjs +63 -17
  98. package/browser/editor/page/typing/index.mjs +12 -6
  99. package/browser/editor/page/typing/leave.mjs +1 -1
  100. package/browser/shared/annotations.mjs +8 -10
  101. package/browser/shared/code-folds.mjs +5 -1
  102. package/browser/shared/colors.mjs +15 -13
  103. package/browser/shared/component-catalog.mjs +24 -10
  104. package/browser/shared/components/callout.mjs +4 -2
  105. package/browser/shared/components/index.mjs +61 -7
  106. package/browser/shared/components/installed.mjs +75 -5
  107. package/browser/shared/components/page.mjs +10 -1
  108. package/browser/shared/components/table.mjs +1 -1
  109. package/browser/shared/components/text.mjs +2 -2
  110. package/browser/shared/document-look.mjs +178 -0
  111. package/browser/shared/emoji/data/LICENSE-lucide.txt +43 -0
  112. package/browser/shared/emoji/data/README.md +4 -2
  113. package/browser/shared/emoji/data/categories.json +1 -1
  114. package/browser/shared/emoji/data/keywords-en.json +1 -1
  115. package/browser/shared/emoji/data/keywords-zh.json +1 -1
  116. package/browser/shared/emoji/data/names.json +1 -1
  117. package/browser/shared/emoji/data/positions.json +1 -1
  118. package/browser/shared/emoji/data/twemoji-index.json +1 -1
  119. package/browser/shared/emoji/data/twemoji-svg.bin +0 -0
  120. package/browser/shared/emoji/data.mjs +10 -12
  121. package/browser/shared/emoji/emoji.mjs +8 -7
  122. package/browser/shared/emoji/icon-value.mjs +4 -3
  123. package/browser/shared/html-tree.mjs +11 -5
  124. package/browser/shared/i18n/en.mjs +67 -8
  125. package/browser/shared/long-runs.mjs +39 -0
  126. package/browser/shared/new-names.mjs +5 -2
  127. package/browser/shared/outline.mjs +194 -0
  128. package/browser/shared/preferences.mjs +10 -4
  129. package/browser/shared/session.mjs +5 -4
  130. package/browser/shared/workspace-look.mjs +91 -0
  131. package/browser/shared/zoom.mjs +5 -5
  132. package/dist/chunk-3HIRPVLU.mjs +2 -0
  133. package/dist/chunk-4FS7Z7WR.mjs +2 -0
  134. package/dist/{chunk-LHTFLRB6.mjs → chunk-5MHMDLZD.mjs} +2 -2
  135. package/dist/{chunk-YXAUQPVK.mjs → chunk-5ZKJ3H4P.mjs} +3 -3
  136. package/dist/{chunk-ZBIM6ARY.mjs → chunk-64K7IPLO.mjs} +55 -16
  137. package/dist/{chunk-QYKCJ7FG.mjs → chunk-6QXGZM36.mjs} +1 -1
  138. package/dist/chunk-6RGJOMJP.mjs +16 -0
  139. package/dist/{chunk-EUX7IC5J.mjs → chunk-7DTINW6U.mjs} +2 -2
  140. package/dist/{chunk-6CYG3Z4V.mjs → chunk-AJ73XJF3.mjs} +1 -1
  141. package/dist/chunk-AUZGSZJH.mjs +13 -0
  142. package/dist/{chunk-7QQBR7PB.mjs → chunk-BFE45AKL.mjs} +1 -1
  143. package/dist/chunk-BGGDRNYQ.mjs +10 -0
  144. package/dist/{chunk-L4DVONOG.mjs → chunk-BXO2ZY5P.mjs} +1 -1
  145. package/dist/chunk-CEH4MRCC.mjs +2 -0
  146. package/dist/chunk-CY2FNNAB.mjs +16 -0
  147. package/dist/{chunk-LC7YNQFG.mjs → chunk-DFUDV4LX.mjs} +1 -1
  148. package/dist/chunk-DKXG4XDG.mjs +2 -0
  149. package/dist/chunk-E7LWZRXY.mjs +4 -0
  150. package/dist/chunk-EZQ34M3F.mjs +124 -0
  151. package/dist/chunk-FDLXYYG4.mjs +4 -0
  152. package/dist/chunk-FUKQNOQS.mjs +9 -0
  153. package/dist/{chunk-DB4WKGYF.mjs → chunk-GB5SOYQJ.mjs} +2 -2
  154. package/dist/{chunk-ACOPDCNB.mjs → chunk-IVDLMMBD.mjs} +1 -1
  155. package/dist/chunk-JPOSLGOC.mjs +6 -0
  156. package/dist/chunk-JVHHHGEA.mjs +3 -0
  157. package/dist/chunk-L3QKH4OK.mjs +26 -0
  158. package/dist/{chunk-B3VTYBYR.mjs → chunk-LCTHTZIL.mjs} +2 -2
  159. package/dist/chunk-LEF6DRCK.mjs +59 -0
  160. package/dist/chunk-LS4F7JUM.mjs +2 -0
  161. package/dist/chunk-NO5NJCHO.mjs +2 -0
  162. package/dist/{chunk-UNUEWH4S.mjs → chunk-NQPVAU63.mjs} +1 -1
  163. package/dist/chunk-O35FIXRB.mjs +2 -0
  164. package/dist/chunk-PL6GHLGD.mjs +15 -0
  165. package/dist/{chunk-G7TOXONE.mjs → chunk-QJIR2BLP.mjs} +1 -1
  166. package/dist/chunk-QUPLVPB2.mjs +7 -0
  167. package/dist/chunk-R7BNENLY.mjs +4 -0
  168. package/dist/chunk-S6O4UHLC.mjs +2 -0
  169. package/dist/{chunk-HMHK3EQ6.mjs → chunk-SGJWQKEI.mjs} +1 -1
  170. package/dist/chunk-SYGN7GLU.mjs +2 -0
  171. package/dist/chunk-TYK6VNAB.mjs +10 -0
  172. package/dist/{chunk-EVJ3BX3M.mjs → chunk-U6KNPJPR.mjs} +1 -1
  173. package/dist/{chunk-YZ5Z6H4W.mjs → chunk-UALRNVSE.mjs} +1 -1
  174. package/dist/chunk-UMHUSOPJ.mjs +2 -0
  175. package/dist/chunk-V3HXVQEM.mjs +2 -0
  176. package/dist/{chunk-UXUK4PMC.mjs → chunk-V5JTW2LH.mjs} +1 -1
  177. package/dist/chunk-V5VXUJKG.mjs +2 -0
  178. package/dist/chunk-VBXMADMP.mjs +2 -0
  179. package/dist/chunk-VNV55RUA.mjs +2 -0
  180. package/dist/chunk-VPD2PS35.mjs +2 -0
  181. package/dist/chunk-W3BGBJQW.mjs +2 -0
  182. package/dist/chunk-W4VKN3GF.mjs +7 -0
  183. package/dist/chunk-W5BXXPDJ.mjs +3 -0
  184. package/dist/{chunk-Z52HTUW7.mjs → chunk-XNROSH2T.mjs} +2 -2
  185. package/dist/chunk-XUNHRCOG.mjs +6 -0
  186. package/dist/{chunk-6U2SLSDN.mjs → chunk-Y6RLRPG3.mjs} +1 -1
  187. package/dist/cli.mjs +1 -1
  188. package/dist/server.mjs +1 -1
  189. package/dist/worker.mjs +1 -1
  190. package/kit/math.js +18 -2
  191. package/kit/report.css +248 -137
  192. package/kit/runtime.js +126 -51
  193. package/kit/tables.js +51 -1
  194. package/kit/ui.js +97 -0
  195. package/package.json +1 -1
  196. package/release.json +8 -8
  197. package/sdk/doc-kit.d.ts +9 -3
  198. package/skill/SKILL.md +22 -10
  199. package/skill/references/building-components.md +46 -6
  200. package/skill/references/example-report.html +1 -1
  201. package/skill/references/writing-html.md +4 -4
  202. package/skill/sdk/doc-kit.d.ts +9 -3
  203. package/web/assets/index-Bh74DfsV.css +1 -0
  204. package/web/assets/index-UW0RyFFt.js +13 -0
  205. package/web/index.html +2 -2
  206. package/browser/editor/components/callout/page.mjs +0 -13
  207. package/dist/chunk-2PBALTR3.mjs +0 -57
  208. package/dist/chunk-3LA3EMDE.mjs +0 -15
  209. package/dist/chunk-5FRMCDPY.mjs +0 -16
  210. package/dist/chunk-5LEK2CBS.mjs +0 -2
  211. package/dist/chunk-5OFN5AHC.mjs +0 -2
  212. package/dist/chunk-5SIYS5HN.mjs +0 -2
  213. package/dist/chunk-5TEK3RAD.mjs +0 -2
  214. package/dist/chunk-7GHT2UCA.mjs +0 -15
  215. package/dist/chunk-ATCJYAR4.mjs +0 -2
  216. package/dist/chunk-C7HWOBIF.mjs +0 -2
  217. package/dist/chunk-E7VYDG2X.mjs +0 -2
  218. package/dist/chunk-FQGQH4KM.mjs +0 -2
  219. package/dist/chunk-GPF4YWWB.mjs +0 -9
  220. package/dist/chunk-GYQV7Y4J.mjs +0 -120
  221. package/dist/chunk-HQQMJCQJ.mjs +0 -10
  222. package/dist/chunk-IERUOGC3.mjs +0 -6
  223. package/dist/chunk-ITNLYXXV.mjs +0 -3
  224. package/dist/chunk-NFIBI4IP.mjs +0 -26
  225. package/dist/chunk-O52JMOCZ.mjs +0 -2
  226. package/dist/chunk-QMQUXTU4.mjs +0 -12
  227. package/dist/chunk-QVWVFC5K.mjs +0 -6
  228. package/dist/chunk-QZY6Q64C.mjs +0 -2
  229. package/dist/chunk-TDQP3DKQ.mjs +0 -10
  230. package/dist/chunk-V4CPWKMZ.mjs +0 -2
  231. package/dist/chunk-VACRB2NE.mjs +0 -3
  232. package/dist/chunk-W63L7367.mjs +0 -7
  233. package/dist/chunk-W7CXPCRV.mjs +0 -6
  234. package/dist/chunk-XINNG4F2.mjs +0 -4
  235. package/dist/chunk-YL2DPWVS.mjs +0 -4
  236. package/web/assets/index-B6Ai3wsa.js +0 -13
  237. package/web/assets/index-SYPC3EZy.css +0 -1
@@ -23,6 +23,7 @@ import sheets from "./sheets/index.mjs";
23
23
  import table from "./table/index.mjs";
24
24
  import widget from "./widget/index.mjs";
25
25
  import { CATEGORIES, componentOf } from "../../shared/components/index.mjs";
26
+ import { slotAround } from "../../shared/components/installed.mjs";
26
27
  import { COLOR_CLASSES } from "./colors.mjs";
27
28
  import { MEDIA_HOSTS } from "./media/parts.mjs";
28
29
  import { t } from "../../shared/i18n/index.mjs";
@@ -34,14 +35,16 @@ import { t } from "../../shared/i18n/index.mjs";
34
35
  // whose category it takes: a basic block, a container, a card or a whole block (CATEGORIES there).
35
36
  // Each describes itself:
36
37
  //
37
- // listed what 设置 › 组件 calls it (a function, as the server says it in both languages), for a
38
+ // listed what 设置 › 组件列表 calls it (a function, as the server says it in both languages), for a
38
39
  // component the reader meets in the "/" menu or as a block of its own; every component
39
- // with a type in that menu says it. hideable: false, it stays offered whatever gallery.json
40
+ // with a type in that menu says it. Its line there shows the icon of its first row in the
41
+ // "/" menu (basic before advanced, by place), or, for a component with no type there (分区),
42
+ // its first unit's `icon`; a listed component with neither throws. hideable: false, it stays offered whatever gallery.json
40
43
  // says, its switch there off and locked (文本, 标题, 列表, 分区): what a page cannot do
41
44
  // without. What is hidden goes from "/", 在下方新建 and the handle's grid and nothing else
42
45
  // (page/offered.mjs); its blocks, its Markdown shortcut and "Blocks at a glance" stay.
43
46
  // units the unit kinds it defines (blocks the reader selects, moves and deletes):
44
- // { [kind]: { name, icon?, match, chapter?, fixed?, block?, handle?, below?, boxed?, beside?, inRow?, sharesHeight?, entersAfter?, resizable?, copy?, clipboard?, sizes?, toggles?, colors?, colorDefaults?, removedEmpty?, turnsEmpty?, canHaveChildren?, emptyIsNothing?, fromWords?, attributes? } }
47
+ // { [kind]: { name, icon?, match, chapter?, fixed?, block?, handle?, below?, boxed?, beside?, inRow?, sharesHeight?, entersAfter?, resizable?, copy?, clipboard?, sizes?, toggles?, colors?, colorDefaults?, removedEmpty?, turnsEmpty?, leadsChapter?, canHaveChildren?, emptyIsNothing?, fromWords?, attributes? } }
45
48
  // match: rules an element matches by its tag (one of `tag`), its class and its
46
49
  // attribute, and by the element it stands in (`parent`: { tag?, class? }, a flowchart's
47
50
  // step being an li whose parent is an ol.doc-flow), whichever are given; every
@@ -80,25 +83,30 @@ import { t } from "../../shared/i18n/index.mjs";
80
83
  // the page, each with the test of its values. icon: what its handle shows, for a
81
84
  // kind no block type stands for (a type's own icon is its `types` entry's). removedEmpty:
82
85
  // Backspace on the one empty block in it deletes it whole (a code block, a highlight block,
83
- // a quote, a disclosure: typing/). turnsEmpty: it holds nothing to carry, so what it is
86
+ // a callout, a quote, a disclosure: typing/). turnsEmpty: it holds nothing to carry, so what it is
84
87
  // turned into is an empty block of the type, in its place (a divider line: convert.mjs).
88
+ // leadsChapter: it may stand first in a chapter as the line above its heading, so a
89
+ // folded heading with only such blocks before it covers the chapter (a divider: fold/rule.mjs).
85
90
  // canHaveChildren: notion-lite's, a block right after it goes under it by Tab, so the
86
91
  // keys of a block stand after it as they stand in what it is in (a list: under its last item).
87
92
  // emptyIsNothing: a block of it left empty counts as nothing written, in a container
88
93
  // that says `removedEmpty`. fromWords: a block of it can be made again from its words alone.
89
94
  // boxes the box kinds it defines (containers whose children are units): { [kind]: { role,
90
- // name?, classes?, find?, owner?, takenBy?, also?, leaveByEnter?, lead?, firstLineActs?, firstBlock?, groups?, holdsOne?, emptyIsNothing?, outer?, selectAll? } }, classes being the kit's classes that make an element one
95
+ // name?, classes?, find?, owner?, takenBy?, also?, leaveByEnter?, endIsPadding?, lead?, firstLineActs?, firstBlock?, groups?, holdsOne?, emptyIsNothing?, outer?, selectAll? } }, classes being the kit's classes that make an element one
91
96
  // (rank order, as above); the others the server knows by their tag (findUnits). find: where
92
97
  // a unit's box of this kind is (a list item's lists, a disclosure's content, a Sheet area's
93
- // panels); owner: the element of its unit that takes the box along (a Sheet panel's tab:
98
+ // panels, an instance's slots by their name: `find(unit, key)`); owner: the element of its unit that takes the box along (a Sheet panel's tab:
94
99
  // boxOwner); takenBy: whether a unit's element is that one, for a box of this key, told from
95
100
  // the element alone, which may be off the page with the box (takesBox);
96
101
  // also: the unit kinds it takes besides what its role takes (a highlight block's icon);
97
102
  // leaveByEnter: Enter on its empty last block moves that block out after it (typing/leave.mjs);
103
+ // endIsPadding: what is under its last block is its own padding (a highlight block, a
104
+ // callout, a disclosure's content): a click there makes no block, it is a gap like any other
105
+ // (geometry.mjs endOf, insert/ clickBlank);
98
106
  // lead: the kind of unit it opens with as its fixed head, which is no block to fall
99
107
  // between (a highlight block's icon: a box selection passes over it, a place before it is
100
108
  // the place after it); firstLineActs: its first block shows the container's handle, not a
101
- // handle of its own (Notion's disableFirstItemHoverMenu: a highlight block's, a quote's);
109
+ // handle of its own (Notion's disableFirstItemHoverMenu: a highlight block's, a callout's, a quote's);
102
110
  // firstBlock: the type its first block (after its lead) always is, its first line (a
103
111
  // flowchart's node: "paragraph", its title), which is turned into no other type (no
104
112
  // 转换为, no Markdown shortcut there) and has nothing made, dropped or pasted before it;
@@ -107,7 +115,7 @@ import { t } from "../../shared/i18n/index.mjs";
107
115
  // groups: a plain <div> with no class in it is a group of blocks, not a block of its own (the
108
116
  // same boxes as the shared descriptions' GROUPING_FLOWS: tests/unit/components.mjs);
109
117
  // holdsOne: a change that leaves it empty puts an empty paragraph in it, so there is always a
110
- // block to write in (a disclosure's content, a highlight block: core.mjs tidyEmptied);
118
+ // block to write in (a disclosure's content, a highlight block, a callout: core.mjs tidyEmptied);
111
119
  // emptyIsNothing: as on a unit, of a box that is an element of its own, by its `classes`;
112
120
  // outer: false, a block straight in it has no block around it for the reader, whatever
113
121
  // stands around its element: no 选中外层 (a detail card's content: block-info.mjs outerOf);
@@ -121,16 +129,17 @@ import { t } from "../../shared/i18n/index.mjs";
121
129
  // sealed a selector: what stands in such an element is no text of a text block around it, as
122
130
  // a list item holding it (caret.mjs editable): its own text blocks are the only hosts in it
123
131
  // readOnly why an element of it stays read-only, on hover and on a click (hints/): rules
124
- // { rank, at, says }, at a selector found from the element (closest), says the reason
132
+ // { rank, at, says }, at a selector found from the element (closest), or a function
133
+ // of the element saying whether the rule is its (an instance's own region, heldBy, which
134
+ // no selector tells from its slots), says the reason
125
135
  // in the reader's words, or null for an element that needs none, having a tool of its
126
136
  // own. Every component's rules are tried in `rank` order, with the page's (PAGE below),
127
137
  // and the first match wins: a rule ranked below the page's 80 goes before any control's,
128
138
  // and is the one that can claim a button as its own.
129
- // hosts the kinds of text block it defines (data-ge-kind): { [kind]: { name, caps, tex?, wrapped?, parts?, whole?, takes?, opensBefore?, opensAfter?, textFilters?, formula?, closed?, inner?, blankSays?, placeholder? } }, caps
139
+ // hosts the kinds of text block it defines (data-ge-kind): { [kind]: { name, caps, tex?, wrapped?, parts?, opensBefore?, textFilters?, formula?, closed?, inner?, blankSays?, placeholder? } }, caps
130
140
  // ({ bound, blockLevel }) giving what such a text block can do (source/); opensBefore:
131
141
  // Enter at its very start, with words in it, puts an empty paragraph before its block
132
- // where it does not split, as one that splits does (typing/); opensAfter: Enter at its
133
- // very end, a paragraph after its block, where it does not split; textFilters: false, what
142
+ // where it does not split, as one that splits does (typing/); textFilters: false, what
134
143
  // is typed in it is taken as it is, no inline code, no space set by itself (notion-lite's);
135
144
  // formula: false, no inline formula is offered in it or pasted as one, the kit drawing
136
145
  // none there (takesFormula);
@@ -139,9 +148,7 @@ import { t } from "../../shared/i18n/index.mjs";
139
148
  // nodes say (a code block's code);
140
149
  // parts: the parts of components ([component id, part name]: a part of its shared
141
150
  // description edited as text, src/shared/components/) whose elements are text blocks
142
- // of this kind, found so by the server's scan; whole: the element (a selector) that is one
143
- // text block of this kind, by the scan too, while it holds inline text alone (a callout); takes: the tag of a
144
- // block that, standing alone in `whole` with nothing of its own, natural HTML makes its words; tex: its text is a formula's TeX alone; wrapped: the page makes the text block a
151
+ // of this kind, found so by the server's scan; tex: its text is a formula's TeX alone; wrapped: the page makes the text block a
145
152
  // span inside the element, which keeps its own behaviour (a Sheet tab's button);
146
153
  // blankSays: what the status line says of one left blank that must hold words, as a
147
154
  // type's says of a block of it (blankSaysOf); placeholder: what one says while it
@@ -152,7 +159,7 @@ import { t } from "../../shared/i18n/index.mjs";
152
159
  // false` says nothing where a block of it would (a list item)
153
160
  // types the blocks the reader can make on the page or turn a block into (the "/" menu,
154
161
  // Markdown shortcuts, the block menu), written by the server in the kit's plain markup:
155
- // { [type]: { tag, className?, kind, host?, box?, leaf?, wraps?, container?, part?, text?, inside?, tex?,
162
+ // { [type]: { tag, className?, kind, box?, leaf?, wraps?, container?, part?, text?, inside?, tex?,
156
163
  // empty?, resettable?, bare?, placeholder?, bar?, label?, icon?, hint?, markdown?, words?, turn?, menu?, fresh?,
157
164
  // corner?, preload?, onSplit?, create?, takesAsIs?, holder?, nests?, joins?, leadIcon?, fromSelection?, clipboard?, glance? } } and, for the blocks the page
158
165
  // and the server write apart from text, title?, content?, keyed?, written?,
@@ -165,7 +172,7 @@ import { t } from "../../shared/i18n/index.mjs";
165
172
  // quote's paragraph, a list item: notion-lite's `resettable`, typing/leave.mjs). leaf: the text block a block turned
166
173
  // into the type becomes; wraps: such a text block sits in a container of the type, found
167
174
  // by `container` (a selector); text: a plain text leaf; empty: a leaf written with no
168
- // text too (a paragraph, a heading, a list item, a code block, a callout: an empty
175
+ // text too (a paragraph, a heading, a list item, a code block: an empty
169
176
  // one stays, as Notion's; the page's core.mjs and the server's source/checks.mjs read
170
177
  // this and nothing else); inside: the element its text is written in, as plain text
171
178
  // (a code block's code element); tex: its text is a formula's TeX alone (a display
@@ -180,8 +187,7 @@ import { t } from "../../shared/i18n/index.mjs";
180
187
  // over (page/markdown/rules.mjs); turn: its place among
181
188
  // the types a text block is turned into; fresh: its place among the types made as an
182
189
  // empty block that stands for an empty one; menu: [group, place], its place in the "/"
183
- // menu (group "basic" or "advanced"), which the block menu's 在下方新建 shows too; host: the
184
- // kind of text block a new one is (`hosts`; "flow" without: core.mjs newHost); corner: the
190
+ // menu (group "basic" or "advanced"), which the block menu's 在下方新建 shows too; corner: the
185
191
  // type in whose cell of a type grid it sits, as a gray corner, when the grid has both
186
192
  // (type-grid.mjs); preload: true, it needs something on the page as soon as a type grid
187
193
  // offers it, and before a block with text is turned into it, which its page object's
@@ -197,7 +203,7 @@ import { t } from "../../shared/i18n/index.mjs";
197
203
  // paragraph. It returns the node the caret or the choice goes to (null: none); a type
198
204
  // without is a text block, made by convert.mjs standIn.
199
205
  // takesAsIs: a block turned into it goes into it as it is, each keeping its type (a
200
- // highlight block, a disclosure); holder: its place among the containers that turn into
206
+ // highlight block, a callout, a disclosure); holder: its place among the containers that turn into
201
207
  // one another, what they hold carried along; nests: false, none inside another of its
202
208
  // kind; joins: false, a block turned into it never joins the one beside it; leadIcon: the
203
209
  // icon a new one opens with; fromSelection: { label, icon }, its row of the format bar's
@@ -295,8 +301,10 @@ import { t } from "../../shared/i18n/index.mjs";
295
301
  // label, width }, or null; reason: why the block may not go, remove(): delete it); rowBeside(target),
296
302
  // a row for a block of another component, beside its color, every page object being asked and each
297
303
  // testing the block itself; acceptsFile(file), whether a file pasted makes a block of it, and
298
- // fromFiles(host, files), that block made below the block of `host` (notion-lite's, which gives the
299
- // data of a new block; the first page object that takes a file in their order); fromLink(figure,
304
+ // fromFiles(put, files), that block made and handed to `put(block)`, the paste's, which puts it where a
305
+ // block pasted at the caret goes (one undo step) and says whether it could (notion-lite's
306
+ // createFromFiles(files, width, place), whose fromFiles gives the data of a new block and whose place
307
+ // the paste gives; the first page object that takes a file in their order); fromLink(figure,
300
308
  // url, options), a block of the component filled from an address (notion-lite's, here the figure
301
309
  // is there first: one pasted with its address, `{ pasted: true }`, a bookmark in an embed's place).
302
310
  // What the rest of the editor asks of a component that does something of its own where every block
@@ -336,8 +344,17 @@ import { t } from "../../shared/i18n/index.mjs";
336
344
  // A new kit component gets a folder of its own and a line below.
337
345
  export const COMPONENTS = [paragraph, heading, list, flowchart, quote, callout, highlight, details, code, divider, section, sheets, table, figure, image, file, bookmark, embed, columns, math, dialog, widget, icon, installed];
338
346
 
339
- /** The built-in components 设置 › 组件 lists: { id, name() (in the language t() says it in), hideable }, by the order above. */
340
- export const LISTED_BLOCKS = COMPONENTS.filter((component) => component.listed).map(({ name, listed, hideable = true }) => ({ id: name, name: listed, hideable }));
347
+ // The "/" menu's groups, in order (MENU_GROUPS below says the same).
348
+ const MENU_GROUP_ORDER = ["basic", "advanced"];
349
+ const menuRank = ({ menu }) => MENU_GROUP_ORDER.indexOf(menu[0]) * 1000 + menu[1];
350
+ // The mark of a component's line in 设置 › 组件列表: the icon of its first row in the "/" menu, the
351
+ // same drawing; one with no type there (分区) shows its first unit's (`icon`, a kind no type stands for).
352
+ const listedIcon = ({ types = {}, units = {} }) => Object.values(types).filter((spec) => spec.menu && spec.icon).sort((a, b) => menuRank(a) - menuRank(b))[0]?.icon
353
+ ?? Object.values(units).find((unit) => unit.icon)?.icon ?? null;
354
+
355
+ /** The built-in components 设置 › 组件列表 lists: { id, name() (in the language t() says it in), icon (svg markup), hideable }, by the order above. */
356
+ export const LISTED_BLOCKS = COMPONENTS.filter((component) => component.listed).map((component) => ({ id: component.name, name: component.listed, icon: listedIcon(component), hideable: component.hideable ?? true }));
357
+ for (const { id, icon } of LISTED_BLOCKS) if (!icon) throw new Error(`component ${id} has a line in the settings (listed), so it needs an icon: a type in the "/" menu, or a unit's icon`);
341
358
 
342
359
  /** The names of those the reader may hide. */
343
360
  export const HIDEABLE = new Set(LISTED_BLOCKS.filter(({ hideable }) => hideable).map(({ id }) => id));
@@ -487,7 +504,7 @@ export const FRESH = inOrder((spec) => spec.fresh);
487
504
  export const HOLDERS = inOrder((spec) => spec.holder);
488
505
 
489
506
  /** The "/" menu's groups of block types, in order: { basic, advanced } (the menu says their names). */
490
- export const MENU_GROUPS = Object.fromEntries(["basic", "advanced"].map((group) => [group, inOrder((spec) => (spec.menu?.[0] === group ? spec.menu[1] : undefined))]));
507
+ export const MENU_GROUPS = Object.fromEntries(MENU_GROUP_ORDER.map((group) => [group, inOrder((spec) => (spec.menu?.[0] === group ? spec.menu[1] : undefined))]));
491
508
 
492
509
  /** The types a block is made of from selected text, each with its row of the format bar's 更多 (`fromSelection`). */
493
510
  export const FROM_SELECTION = Object.entries(BLOCK_TYPES).filter(([, spec]) => spec.fromSelection).map(([type, spec]) => ({ type, ...spec.fromSelection }));
@@ -538,9 +555,12 @@ const ONE_BLOCK_AT = ONE_BLOCK.map(({ at }) => at).join(", ");
538
555
  /** What no text block around it reaches into (`sealed`), a selector, "" where no component says. */
539
556
  export const SEALED = COMPONENTS.map((component) => component.sealed).filter(Boolean).join(", ");
540
557
 
558
+ // What is one block is found inside the slot whose inside is the document's region an element stands
559
+ // in, where it does (an instance's slot holds blocks of their own: installed.mjs slotAround).
541
560
  const oneBlockOf = (element) => {
542
- let whole = element.closest(ONE_BLOCK_AT);
543
- for (let around = whole; around; around = around.parentElement?.closest(ONE_BLOCK_AT)) whole = around;
561
+ const region = slotAround(element);
562
+ let whole = null;
563
+ for (let around = element.closest(ONE_BLOCK_AT); around && (!region || region.contains(around)); around = around.parentElement?.closest(ONE_BLOCK_AT)) whole = around;
544
564
  return whole && ONE_BLOCK.find(({ at }) => whole.matches(at)).name;
545
565
  };
546
566
 
@@ -554,7 +574,8 @@ const textPartOf = (element) => TEXT_PARTS.find(({ match, at }) => [...(element.
554
574
 
555
575
  /**
556
576
  * The component an element is of, by name, or null. Nothing inside an element that is one block is
557
- * anyone else's, whatever its tag or class (a cell's list, a Sheet's tab). Else that of its kind of text
577
+ * anyone else's, whatever its tag or class (a cell's list, a Sheet's tab), but what an instance's slot
578
+ * holds, the document's blocks, each its own. Else that of its kind of text
558
579
  * block, where a component defines the kind (a cell, a tab's name), that of the text part of a
559
580
  * component it is, that of the unit it is (a flowchart's node, though an li) and last that of its type
560
581
  * (a list's item made on the page). What stands in a container is its own component's, not the
@@ -598,7 +619,7 @@ export const takesBox = (element, { kind, key }) => Boolean(element && BOXES[kin
598
619
  const FROM_WORDS = flagged("fromWords");
599
620
  export const PLAIN_TEXT = new Set(Object.keys(BLOCK_TYPES).filter((type) => FROM_WORDS.has(BLOCK_TYPES[type].kind)));
600
621
 
601
- /** Plain text leaves (paragraphs, small text, headings, callouts). */
622
+ /** Plain text leaves (paragraphs, small text, headings). */
602
623
  export const TEXT_TYPES = new Set(Object.keys(BLOCK_TYPES).filter((type) => BLOCK_TYPES[type].text));
603
624
 
604
625
  /** The heading type of each heading tag (H1 -> heading1). */
@@ -682,6 +703,8 @@ const ROLES = Object.fromEntries(Object.entries(BOXES).map(([kind, box]) => [kin
682
703
  /** What a chapter is: the page and Sheet panels hold these besides body text. */
683
704
  export const CHAPTER = flagged("chapter");
684
705
  const FIXED = flagged("fixed"); // never inside a flow
706
+ /** What may stand first in a chapter as the line above its heading (`leadsChapter`: a divider, fold/). */
707
+ export const LEADS_CHAPTER = flagged("leadsChapter");
685
708
 
686
709
  /** The kinds of box a plain <div> without a class is a group of blocks in (`groups`; source/units.mjs boxKind). */
687
710
  export const GROUPING = new Set(Object.keys(BOXES).filter((kind) => BOXES[kind].groups));
@@ -695,6 +718,9 @@ export const HOLDS_ONE = new Set(Object.keys(BOXES).filter((kind) => BOXES[kind]
695
718
  /** The box kinds whose empty last block Enter moves out after the box (`leaveByEnter`). */
696
719
  export const LEAVES_BY_ENTER = new Set(Object.keys(BOXES).filter((kind) => BOXES[kind].leaveByEnter));
697
720
 
721
+ /** Whether what is under the last block of a box of this kind is its own padding (`endIsPadding`): a click there makes no block (geometry.mjs endOf). */
722
+ export const endIsPadding = (boxKind) => Boolean(BOXES[boxKind]?.endIsPadding);
723
+
698
724
  /** The unit kinds Backspace deletes whole once nothing is written in them (`removedEmpty`). */
699
725
  export const REMOVED_EMPTY = Object.keys(UNITS).filter((kind) => UNITS[kind].removedEmpty);
700
726
 
@@ -742,7 +768,7 @@ export const hasOuter = (boxKind) => BOXES[boxKind]?.outer !== false;
742
768
  /** The unit kinds with a frame of their own (`boxed`): a double click between two makes a paragraph (insert/). */
743
769
  export const BOXED = flagged("boxed");
744
770
 
745
- /** The unit kinds a click to the right of one selects: the boxed ones and those that say `beside` (beside.mjs). */
771
+ /** The unit kinds a click to the right of one, past the text area it stands in, selects: the boxed ones and those that say `beside` (beside.mjs). */
746
772
  export const BESIDE = new Set([...BOXED, ...flagged("beside")]);
747
773
 
748
774
  /** Whether a unit of this kind stands beside another now, `element` being that unit (`beside`, `inRow`). */
@@ -1,3 +1,4 @@
1
+ import { SECTION_ICON } from "../icons.mjs";
1
2
  import { HIGHLIGHT } from "../kit.mjs";
2
3
  import { t } from "../../../shared/i18n/index.mjs";
3
4
  // 分区: section.doc-section, a container of ordinary blocks (its title is whatever heading it
@@ -18,7 +19,7 @@ export default {
18
19
  name: "section",
19
20
  listed: () => t("分区"), hideable: false,
20
21
  units: {
21
- section: { name: t("分区"), match: [{ rank: 40, tag: "section" }], chapter: true, fixed: true, handle: "bar" },
22
+ section: { name: t("分区"), icon: SECTION_ICON, match: [{ rank: 40, tag: "section" }], chapter: true, fixed: true, handle: "bar" },
22
23
  },
23
24
  boxes: {
24
25
  section: { role: "flow", name: t("分区"), groups: true },
@@ -0,0 +1,24 @@
1
+ import { escapeText } from "../../shared/escape.mjs";
2
+
3
+ /**
4
+ * How a box of blocks set apart (a highlight block, a callout: `match` its elements) is copied, one
5
+ * rule for both, their clipboard's hooks (registry.mjs `clipboard`) for the block of `type`: its
6
+ * blocks; one with words straight in it (an Agent's, a callout written the old way), a paragraph of
7
+ * what it says. A list with its class is a list, and a text block of a type of its own that type.
8
+ */
9
+ export const setApart = (type, match) => ({
10
+ describe(node, { isHost, holding, colorOf, copiedType, serializeInner, plain }) {
11
+ if (!node.matches(match) || node.matches("ul, ol") || (isHost(node) && copiedType(node))) return undefined;
12
+ const straight = [...node.childNodes].some((child) => child.nodeType === 3 && child.data.trim());
13
+ const words = isHost(node) ? serializeInner(node) : straight ? escapeText(plain(node)) : null;
14
+ const blocks = words === null ? holding(node) : [{ type: "paragraph", html: words }];
15
+ const color = colorOf(node);
16
+ return blocks ? { type, blocks, ...(color ? { color } : {}) } : null;
17
+ },
18
+ endsIn(unit, { blocksIn, boxesOf, colorOf }) {
19
+ if (!unit.matches(match)) return undefined;
20
+ const color = colorOf(unit);
21
+ return [{ type, blocks: blocksIn(boxesOf(unit)), ...(color ? { color } : {}) }];
22
+ },
23
+ check: (block, { container }) => container(block),
24
+ });
@@ -115,9 +115,11 @@ export const CELL_BOLD = CELL_BOLD_CLASS;
115
115
  export const CELL_LOOKS = [...BLOCK_COLORS, ...CELL_ALIGNS.map(([name]) => name), ...CELL_VALIGNS.map(([name]) => name), CELL_BOLD];
116
116
 
117
117
  /**
118
- * The look a cell takes when it becomes a header cell (a th: the header row's or column's toggle, a
119
- * row or column moved there, a cell made there: page.mjs) and loses when it stops being one: bold,
120
- * and the gray fill unless it has a color of its own. Nothing else of the kit's sets a header apart.
118
+ * The look a cell takes when it becomes a header row's cell (a th that is no row's header: 标题行's
119
+ * toggle, a row moved there, a cell made there: page.mjs) and loses when it stops being one: bold,
120
+ * and the gray fill unless it has a color of its own. A header column's cell (th scope="row") has
121
+ * none: 标题列 only makes the first column stay when the frame scrolls sideways (the user's,
122
+ * 2026-11-06). Nothing else of the kit's sets a header apart.
121
123
  */
122
124
  export const HEADER_LOOK = [CELL_BOLD, "doc-highlight-gray"];
123
125
 
@@ -35,7 +35,7 @@ export function sharedWidths(wrap, count, share = 1) {
35
35
  * `numbers` is false (识别数字列: index.mjs NUMBERS). `looks`, `widths` and `spans` as tableData
36
36
  * describes them: each cell's look classes, each column's width, each cell's [rowspan, colspan]
37
37
  * (a cell merged makes a row hold fewer cells); where `looks` is given the cells have just those,
38
- * where not a header cell takes the header's look. `chart`: the key a chart drawn from it reads it
38
+ * where not a header row's cell takes the header's look (a header column's none). `chart`: the key a chart drawn from it reads it
39
39
  * by (its frame's data-doc-chart-source), when it has one.
40
40
  */
41
41
  export function newTable(doc, { rows, header = false, column = false, numbers = true, looks, widths, spans, chart }) {
@@ -55,7 +55,7 @@ export function newTable(doc, { rows, header = false, column = false, numbers =
55
55
  const first = at[r][index] === 0;
56
56
  const cell = newCell(doc, head || (column && first) ? "th" : "td", !head && column && first ? "row" : null);
57
57
  if (looks) cell.classList.add(...(looks[r][index] ?? "").split(" ").filter(Boolean));
58
- else if (cell.localName === "th") headerLook(cell, true);
58
+ else if (head) headerLook(cell, true);
59
59
  const [down, across] = spans?.[r]?.[index] ?? [1, 1];
60
60
  setSpan(cell, down, across);
61
61
  if (Array.isArray(html)) cell.append(...html);
@@ -139,7 +139,7 @@ export function build({ doc, block, inline }) {
139
139
  /**
140
140
  * A table standing for an empty block (its type's `create`): three columns, a header row and two rows
141
141
  * (the plan's D6), the caret in its first cell (notion-lite's). It is two fifths as wide as the text
142
- * it stands in, its columns equal and fixed (typing does not move them); 一键调整列宽 is the reader's
142
+ * it stands in, its columns equal and fixed (typing does not move them); 自动调整列宽 is the reader's
143
143
  * to choose (a pasted table has the kit's widths). Its numeric columns are not read (识别数字列 off):
144
144
  * typing numbers does not center or sort them.
145
145
  */
@@ -164,8 +164,8 @@ export function create({ tables }) {
164
164
 
165
165
  /**
166
166
  * A new cell: a text block made on the page (td, or th with its scope). A header cell made in a
167
- * header row or column takes the look of the header cell beside it (`model`: lookLike), one of a
168
- * new table the header's (newTable).
167
+ * header row takes the look of the header cell beside it (`model`: lookLike), one of a new table
168
+ * the header's (newTable); a header column's cell is made plain.
169
169
  */
170
170
  export function newCell(doc, tag = "td", scope = null, model = null) {
171
171
  const cell = doc.createElement(tag);
@@ -175,9 +175,15 @@ export function newCell(doc, tag = "td", scope = null, model = null) {
175
175
  return cell;
176
176
  }
177
177
 
178
- // A cell that becomes a header cell takes the header's look (index.mjs HEADER_LOOK: bold, and the
179
- // gray fill unless it has a color of its own); one that stops being one loses it. These are looks
180
- // as any other: the reader may change them.
178
+ // Whether a cell of `tag` and `scope` wears the header row's look (index.mjs HEADER_LOOK): a th that is
179
+ // no row's header. A header column's th (scope="row") has no look of its own: 标题列 only makes the
180
+ // column stay at the left when the frame scrolls sideways (the user's, 2026-11-06).
181
+ const wearsHeaderLook = (tag, scope) => tag === "th" && (scope ?? "").trim().toLowerCase() !== "row";
182
+
183
+ // A cell that becomes a header row's cell (wearsHeaderLook) takes the header's look (index.mjs
184
+ // HEADER_LOOK: bold, and the gray fill unless it has a color of its own); one that stops being one
185
+ // loses it; a header column's cell has none (标题列 only sticks). These are looks as any other: the
186
+ // reader may change them.
181
187
  function headerLook(cell, on) {
182
188
  const [bold, fill] = HEADER_LOOK;
183
189
  if (!on) return cell.classList.remove(bold, fill);
@@ -185,7 +191,7 @@ function headerLook(cell, on) {
185
191
  if (!BLOCK_COLORS.some((name) => cell.classList.contains(name))) cell.classList.add(fill);
186
192
  }
187
193
 
188
- // A header cell made beside another of its header row or column: its color and bold as that one's
194
+ // A header cell made beside another of its header row: its color and bold as that one's
189
195
  // (a header the reader made plain, or the Agent wrote plain, stays so).
190
196
  function lookLike(cell, model) {
191
197
  const [bold] = HEADER_LOOK;
@@ -253,10 +259,10 @@ export function columnRange(unit, from, to = from) {
253
259
  }
254
260
 
255
261
  // The cell a new row has in column `c`: as the row it is made beside has it there (a header
256
- // column's th), never a header row's th.
262
+ // column's th, plain), never a header row's th.
257
263
  const cellLike = (doc, slots, r, c) => {
258
264
  const model = r === null ? null : slots.matrix[r]?.[c];
259
- return model?.localName === "th" && model.getAttribute("scope") === "row" && c === 0 ? newCell(doc, "th", "row", model) : newCell(doc);
265
+ return model?.localName === "th" && model.getAttribute("scope") === "row" && c === 0 ? newCell(doc, "th", "row") : newCell(doc);
260
266
  };
261
267
 
262
268
  /** A new row at `at` (0 … rows): in the tbody (in the thead only between its rows). Returns it. */
@@ -304,7 +310,7 @@ export function insertColumn(unit, at) {
304
310
  const head = row.parentElement.localName === "thead";
305
311
  const first = slots.matrix[r][0];
306
312
  const beside = slots.matrix[r][Math.max(0, at - 1)];
307
- const cell = head ? newCell(doc, "th", null, beside) : at === 0 && headerColumn ? newCell(doc, "th", "row", first) : newCell(doc);
313
+ const cell = head ? newCell(doc, "th", null, beside) : at === 0 && headerColumn ? newCell(doc, "th", "row") : newCell(doc);
308
314
  row.insertBefore(cell, cellFrom(slots, row, at));
309
315
  made.push(cell);
310
316
  // A header column stays the first: the cell that was first is a plain one now.
@@ -464,7 +470,7 @@ export function labelColumn(unit) {
464
470
 
465
471
  /**
466
472
  * 一键优化 (the table's block menu; a table pasted from elsewhere comes in so): the kit's column
467
- * widths (一键调整列宽: many columns scroll sideways in the frame), a header row, the first column a
473
+ * widths (自动调整列宽: many columns scroll sideways in the frame), a header row, the first column a
468
474
  * header column when its words are the rows' names, its numeric columns read (识别数字列: centered,
469
475
  * sorted by their header), and a frame shown whole (显示全部行) that is taller than the kit's scroll
470
476
  * height scrolling again (one that scrolls already stays as it is). Inside one undo step (the
@@ -491,7 +497,7 @@ export function optimizeTable(unit) {
491
497
  return changed;
492
498
  }
493
499
 
494
- /** The header column on (each body row's cell in the first column th scope="row") or off (td). */
500
+ /** The header column on (each body row's cell in the first column th scope="row") or off (td); its cells' looks untouched either way (the header column only sticks). */
495
501
  export function setHeaderColumn(unit, on) {
496
502
  const { rows, body, headerColumn, slots } = tableState(unit);
497
503
  if (on === headerColumn) return false;
@@ -596,7 +602,7 @@ export function splitCell(unit, cell) {
596
602
  const before = cellFrom(slots, rows[r], place.c + place.cols);
597
603
  for (let c = place.c; c < place.c + place.cols; c++) {
598
604
  if (r === place.r && c === place.c) continue;
599
- const piece = rowHeader ? (c === 0 ? newCell(doc, "th", "row", cell) : newCell(doc)) : newCell(doc, cell.localName, null, cell);
605
+ const piece = rowHeader ? (c === 0 ? newCell(doc, "th", "row") : newCell(doc)) : newCell(doc, cell.localName, null, cell);
600
606
  rows[r].insertBefore(piece, before);
601
607
  made.push(piece);
602
608
  }
@@ -647,11 +653,16 @@ function keepWidths(unit, mutate) {
647
653
  /**
648
654
  * A cell as a td or th (`scope`: a th's, null for none): a new element of that tag in its place,
649
655
  * taking its attributes, a copy of its content (the old cell keeps its own, for undo to put back)
650
- * and its identity (it stays the same cell to the commits); the header's look taken or lost with it.
656
+ * and its identity (it stays the same cell to the commits); the header row's look taken or lost when
657
+ * the cell comes into or leaves the kind that wears it (wearsHeaderLook): a header column's th wears
658
+ * none, and a cell switched between td and it keeps its looks. A th only given another scope stays
659
+ * itself unless its look changes (a new element then too, so undo puts its classes back).
651
660
  */
652
661
  export function retagCell(cell, tag, scope = null) {
653
662
  if (!cell) return cell;
654
- if (cell.localName === tag) {
663
+ const look = wearsHeaderLook(tag, tag === "th" ? scope : null);
664
+ const changes = wearsHeaderLook(cell.localName, cell.getAttribute("scope")) !== look;
665
+ if (cell.localName === tag && !changes) {
655
666
  if (tag === "th" && scope) cell.setAttribute("scope", scope);
656
667
  else cell.removeAttribute("scope");
657
668
  return cell;
@@ -659,7 +670,7 @@ export function retagCell(cell, tag, scope = null) {
659
670
  const next = cell.ownerDocument.createElement(tag);
660
671
  for (const { name, value } of [...cell.attributes]) if (name !== "scope") next.setAttribute(name, value);
661
672
  if (tag === "th" && scope) next.setAttribute("scope", scope);
662
- headerLook(next, tag === "th");
673
+ if (changes) headerLook(next, look);
663
674
  next.append(...[...cell.childNodes].map((node) => node.cloneNode(true)));
664
675
  cell.replaceWith(next);
665
676
  return next;
@@ -26,9 +26,10 @@ const unitOf = (node) => {
26
26
  /**
27
27
  * The text of `elements` (in page order) as the server reads a block, a line between them: { text,
28
28
  * at }, `at[i]` where character `i` comes from ({ node, offset } in a text node, { element } for a
29
- * formula's TeX, null for a line's end).
29
+ * formula's TeX, null for a line's end). `skip(child)`: an element of theirs read past (a list item's
30
+ * blocks, when its own words alone are read: changes/).
30
31
  */
31
- export function readText(elements) {
32
+ export function readText(elements, { skip = null } = {}) {
32
33
  let text = "";
33
34
  const at = [];
34
35
  const put = (char, place) => { text += char; at.push(place); };
@@ -59,7 +60,7 @@ export function readText(elements) {
59
60
  space({ element: child });
60
61
  continue;
61
62
  }
62
- if (child.matches(SKIP)) continue;
63
+ if (child.matches(SKIP) || skip?.(child)) continue;
63
64
  const words = isMention(child) ? sourceWords(child) : null;
64
65
  if (words !== null) {
65
66
  for (const char of words) (/\s/.test(char) ? space : (place) => put(char, place))({ element: child });