@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
@@ -11,8 +11,12 @@ import { element, mount } from "../ui.mjs";
11
11
  // (theirs stay theirs, a chart's gradients among them). The page's own rules are brought in as one
12
12
  // stylesheet (made once, applied at once: no unstyled first frame), and each copy stands in a frame
13
13
  // of its ancestors as the page has them (shallow copies, display: contents), so a list item keeps its
14
- // marker and its number, and a section's paragraph its section's look. At most MAX blocks, each at
15
- // most TALL of the window (cut, fading out at the bottom).
14
+ // marker and its number, and a section's paragraph its section's look. A frame draws nothing of its
15
+ // own (visibility: hidden, the copy visible): display: contents leaves an element's ::before and
16
+ // ::after and a <details>'s own summary drawn (Chrome's "▶ 详细信息" for one without a summary, an
17
+ // ancestor item's marker, a flowchart step's number, each at the window's left or top), which would
18
+ // follow the pointer (the user's report of 2026-11-06). At most MAX blocks, each at most TALL of the
19
+ // window (cut, fading out at the bottom).
16
20
 
17
21
  const MAX = 12;
18
22
  const TALL = 0.6;
@@ -72,9 +76,9 @@ function copyOf(part, rect, top) {
72
76
  }
73
77
  const cut = rect.height > innerHeight * TALL;
74
78
  if (cut) copy.classList.add("ge-cut");
75
- // Its own size, as it was on the page: no share of a row or of its box (the window is the box here); inline, so the page's rules for the block cannot undo it.
79
+ // Its own size, as it was on the page: no share of a row or of its box (the window is the box here); inline, so the page's rules for the block cannot undo it. Visible, in frames that are not.
76
80
  Object.assign(copy.style, {
77
- position: "absolute", margin: "0", maxWidth: "none", minWidth: "0", maxHeight: "none", flex: "none",
81
+ position: "absolute", margin: "0", maxWidth: "none", minWidth: "0", maxHeight: "none", flex: "none", visibility: "visible",
78
82
  left: `${rect.left}px`, top: `${rect.top}px`, width: `${rect.width}px`,
79
83
  height: `${cut ? Math.round(innerHeight * TALL) : rect.height}px`,
80
84
  });
@@ -82,6 +86,7 @@ function copyOf(part, rect, top) {
82
86
  for (let node = part.parentElement; node && node !== top.parentElement; node = node.parentElement) {
83
87
  const shell = clean(node.cloneNode(false));
84
88
  shell.style.display = "contents";
89
+ shell.style.visibility = "hidden"; // a frame lends the copy its rules and draws nothing of its own
85
90
  shell.append(outer);
86
91
  outer = shell;
87
92
  }
@@ -3,6 +3,7 @@
3
3
  import "../../shared/renamed-storage.mjs";
4
4
  import { OUTER, can, captureLayout, editableAhead, firstHost, isHost, limitText, markFrames, markKinds, matchesSource, newHost, newMention, ownHTML, plainText, prepareBlock, splitBlock, textEnd, tidyEmptied, unitElement, withLeads } from "./core.mjs";
5
5
  import { createBlocks } from "./blocks.mjs";
6
+ import { hiddenByFold, reveal } from "./folded.mjs";
6
7
  import { createFlags } from "./flags.mjs";
7
8
  import { createFrame } from "./frame.mjs";
8
9
  import { createHistory } from "./history.mjs";
@@ -14,6 +15,7 @@ import { createCommits, kindOf } from "./commit.mjs";
14
15
  import { createConflicts, packageChanged, unfit } from "./conflicts.mjs";
15
16
  import { checkLeft, createAutosave, keepWith, lostKey, pendingKey, readLost, recover, reloadView, setReloader, takeView, viewNow } from "./autosave.mjs";
16
17
  import { SOLO, connectWorkbench } from "./session.mjs";
18
+ import { lookOfElement } from "../../shared/document-look.mjs";
17
19
  import { belowTargets, convertBlock, convertGroup, groupParents, groupTargets, noTextToCarry, targetsOf, touchedBy, turnInto, turnParents, turnUnit, typeOf } from "./convert.mjs";
18
20
  import { joinCode, rangeAt, rangeOffsets } from "./marks.mjs";
19
21
  import { addStyle, button, copyAddress, element, layer } from "./ui.mjs";
@@ -26,7 +28,8 @@ import { bookmarkItems, connectViews, linkTitle, offerViews } from "./links/view
26
28
  import { connectChipFills } from "./chip-fills.mjs";
27
29
  import { connectFormula, dismissFormula, formulaBoxNode, loadKatex, openFormula } from "./formula/index.mjs";
28
30
  import { settlePads } from "./pads.mjs";
29
- import { beforeDelete, codeArrow, codeClick, codePress, settleCodeEdge } from "./typing/code-edges.mjs";
31
+ import { followLongRuns } from "./long-runs.mjs";
32
+ import { beforeDelete, codeArrow, codeClick, codePress, deletedBack, deletingBack, settleCodeEdge, takeEntry } from "./typing/code-edges.mjs";
30
33
  import { createMarkdown } from "./markdown/index.mjs";
31
34
  import { createResize } from "./resize/index.mjs";
32
35
  import { connectPages, fieldOpen, keptByComponent, menuRow, modalOps, pageAt, pageIcon, pageObjects, pageOf, pageOfHost, pageOfType, pageOfUnit, panelOpen, typedAsIs } from "./page-objects.mjs";
@@ -42,6 +45,7 @@ import { createSelectAll, topStep } from "./select-all/index.mjs";
42
45
  import { revealText } from "./find/index.mjs";
43
46
  import { createOutline } from "./outline/index.mjs";
44
47
  import { createAnnotate } from "./annotate/index.mjs";
48
+ import { createChanges as createAgentChanges } from "./changes/index.mjs";
45
49
  import { BLOCK_TYPES, HOSTS, PLAIN_TEXT, TYPE_INFO, accepts, blankSaysOf, boxOwner, texOnly, textFilters } from "../components/registry.mjs";
46
50
  import { createPages } from "../components/pages.mjs";
47
51
  import { openMediaPicker } from "./emoji-picker/index.mjs";
@@ -77,6 +81,7 @@ import { language, t } from "../../shared/i18n/index.mjs";
77
81
  // select-all/ Ctrl+A: the block's text, then its container's blocks, one level a press, then the page
78
82
  // outline/ 目录: the page's headings at its right (the room there is margin.mjs's)
79
83
  // annotate/ 批注: started from the format bar, a block's button or Ctrl+Alt+M; cards in the margin, with no room markers in the gutter and the card by the words
84
+ // changes/ the Agent's changes (设置 › 显示变更): what an Agent wrote since the reader last kept or reverted it, marked; the bar at the bottom
80
85
  // blocks.mjs the block tools: handle/, block-menu/, selection/, drag/
81
86
  //
82
87
  // and the components' own editing (src/editor/components/<name>/page.mjs). Undo and redo are
@@ -103,6 +108,7 @@ let markdown = null; // Markdown shortcuts as the reader types (markdown/)
103
108
  let selectAll = null; // Ctrl+A widening step by step (select-all/)
104
109
  let outline = null; // the page's headings at its right (outline/), not on a page opened on its own (?solo)
105
110
  let annotate = null; // 批注 (annotate/), on every page of a session
111
+ let agentChanges = null; // what an Agent changed, under review (changes/), on every page of a session
106
112
  let editing = false;
107
113
  let composing = false; // an input method is composing
108
114
  // A composition ends with compositionend, which Chrome does not always send (a paste over the text
@@ -112,6 +118,7 @@ const compositionOver = (event) => {
112
118
  if (composing && !event.isComposing && event.keyCode !== 229) composing = false;
113
119
  };
114
120
  let pressing = false; // the main button is down on the page (a click or a drag under way)
121
+ let pressedText = null; // the text block a press of the main button came down in (onPointerDown), for the click that ends it
115
122
  let commits = null; // the page's commits (commit.mjs)
116
123
  let autosave = null; // when they go (autosave.mjs)
117
124
  let conflicts = null; // what the file refused, for the reader to decide (conflicts.mjs)
@@ -150,7 +157,8 @@ const STYLE = `
150
157
  as the element's own outline. Selected blocks get a fill instead (selection/). */
151
158
  /* Editable text has a text block's corners and, framed, a light fill under the pointer, unless its
152
159
  component declares others: --ge-text-radius, and --ge-text-tint (none: plain, as Notion's and
153
- Feishu's; a cell, a callout, a code block and its title, a disclosure's title). */
160
+ Feishu's; a cell, a code block and its title, a disclosure's title, a media block's
161
+ caption, a card's title and caption). */
154
162
  [data-ge-editing] [contenteditable="true"] { border-radius: var(--ge-text-radius, 3px); }
155
163
  [data-ge-editing] [contenteditable="true"]:focus { outline: none; }
156
164
  [data-ge-editing] [data-ge-round="box"] { border-radius: 8px; }
@@ -307,9 +315,10 @@ connectPages(createPages({
307
315
  })); // what the engines ask of a component (page-objects.mjs)
308
316
 
309
317
  const typing = createTyping({
310
- struct, say, keepText: (node) => blocks.keepText(node), paragraphBefore: insert.paragraphBefore, paragraphAfter: insert.paragraphAfter, removeBlocks: (units) => blocks.remove(units), selectBlock: (unit) => blocks.select(unit),
318
+ struct, say, keepText: (node) => blocks.keepText(node), paragraphBefore: insert.paragraphBefore, removeBlocks: (units) => blocks.remove(units), selectBlock: (unit) => blocks.select(unit),
311
319
  adding: () => slash?.adding ?? null,
312
320
  leave: createLeaving({ struct, touchedBy, convertHost, typeOf, targetsOf }),
321
+ showHidden: (node) => blocks?.folds.showAround(node), // a folded heading's section opened before a join takes in its words (fold/)
313
322
  });
314
323
  // Selecting text across blocks: its listeners go before the editor's own keys and clipboard.
315
324
  const crossText = createTextSelection({
@@ -411,6 +420,8 @@ function enter() {
411
420
  });
412
421
  layout = captureLayout(document, MAP);
413
422
  markFrames(document, layout, hosts);
423
+ // A text block holding a long run (a path, a URL) breaks anywhere, judged now and as its text changes (long-runs.mjs).
424
+ followLongRuns(document.querySelector("main") ?? document.body);
414
425
  commits = createCommits({ root: document, map: MAP, baselines, units, boxes, getLayout: () => layout, setLayout: (next) => (layout = next) });
415
426
  // Commits wait while an input method composes, a formula is being edited (formula/), the "/" menu
416
427
  // or the "@" menu stands on what is typed for it (half of it is no text of the file's) or a gap
@@ -433,6 +444,7 @@ function enter() {
433
444
  conflicts = createConflicts({ act: decide, nodeOf: conflictNode, restorable, copyBlock: (node) => clipboard.copyBlocks([node]), say });
434
445
  editing = true;
435
446
  document.documentElement.setAttribute("data-ge-editing", "");
447
+ blocks?.folds.start(); // what was folded, folded again in this task: no frame shows it open (fold/)
436
448
  refresh();
437
449
  for (const page of pageObjects()) page.entered?.();
438
450
  }
@@ -454,6 +466,7 @@ const keepsOverwrite = (asked) => !asked && [...overwrites.values()].some(({ lan
454
466
  // A commit was taken in (commit.mjs): a block merged with someone else's words shows the merge;
455
467
  // what the file refused is flagged and held back, the rest goes on.
456
468
  function committed({ conflicts: held, merged, outdated, forced = [] }, receipt) {
469
+ agentChanges?.committed(receipt);
457
470
  for (const host of merged) history.merged(host);
458
471
  for (const unit of forced) {
459
472
  const written = overwrites.get(unit);
@@ -757,7 +770,7 @@ function convertUnit(unit, type, { loaded = false } = {}) {
757
770
  if (!made || !onCreated(type, result.focus)) {
758
771
  if (emptied && isHost(result.focus)) caretAt(result.focus, "start");
759
772
  else {
760
- // What it is now: the new highlight block a block went into, as a disclosure (notion-lite's
773
+ // What it is now: the new highlight block or callout a block went into, as a disclosure (notion-lite's
761
774
  // transformBlocks selects the callout), the flowchart a block or a list went into (a container
762
775
  // of a type that takes a block as it is, no `wraps`), else the block holding the text.
763
776
  const { container, wraps } = BLOCK_TYPES[type];
@@ -946,6 +959,18 @@ function onBeforeInput(event) {
946
959
  } else if (type === "insertLineBreak" && can(host, "plain")) {
947
960
  event.preventDefault();
948
961
  say(limitText(host, "split"));
962
+ } else if (/^delete[A-Za-z]+(Backward|Forward)$/.test(type) && !event.isComposing && textFilters(host)) {
963
+ // Backspace or Delete beside a component's entry whose keydown onKeydown passed by (an input
964
+ // method's, keyCode 229): the whole entry, as there (typing/code-edges.mjs takeEntry). A
965
+ // deletion backward the browser does: the caret kept outside inline code it comes to (deletedBack).
966
+ const back = type.endsWith("Backward");
967
+ const before = type === "deleteContentBackward" || type === "deleteContentForward" ? ownHTML(host) : null;
968
+ const removed = before !== null ? takeEntry(host, back) : undefined;
969
+ if (removed) {
970
+ event.preventDefault();
971
+ recordStep(host, before);
972
+ say(removed);
973
+ } else if (back) deletingBack(host);
949
974
  } else if (type.startsWith("format")) {
950
975
  event.preventDefault(); // formatting goes through format-bar/: the kit's markup only
951
976
  } else if (type === "insertFromDrop" || type === "deleteByDrag") {
@@ -974,6 +999,7 @@ function onInput(event) {
974
999
  }
975
1000
  // What stood between two pieces of inline code deleted: they are one (marks.mjs joinCode).
976
1001
  if (event.inputType.startsWith("delete") && textFilters(host)) joinCode(host);
1002
+ if (event.inputType.startsWith("delete") && event.inputType.endsWith("Backward") && textFilters(host)) deletedBack(host);
977
1003
  pageOf(host)?.typed?.(host);
978
1004
  recordText(host);
979
1005
  markdown.typed(host, event.data);
@@ -1012,6 +1038,7 @@ function onPointerDown(event) {
1012
1038
  hideHint();
1013
1039
  endArrowRun();
1014
1040
  if (event.button === 0) pressing = true;
1041
+ pressedText = event.button === 0 && editing ? editable(event.target) : null;
1015
1042
  // What the press came down on may take it, by its component's `press`: code opens for editing, a
1016
1043
  // display formula goes into the formula editor, and no caret goes in.
1017
1044
  if (editing && event.button === 0 && pageAt(event.target)?.press?.(event)) return;
@@ -1033,6 +1060,9 @@ function onSelectionChange() {
1033
1060
  formatting.schedule();
1034
1061
  if (!composing) formatting.moved();
1035
1062
  const selection = getSelection();
1063
+ // The caret, or an end of a text selection, in what a folded heading hides (undo put it back there):
1064
+ // the fold opens (fold/).
1065
+ for (const end of [selection.anchorNode, selection.focusNode]) if (end && hiddenByFold(end)) blocks?.folds.showAround(end);
1036
1066
  const host = selection.rangeCount ? editable(selection.anchorNode) : null;
1037
1067
  // A text block no step named, one a block made on the page came with: what the caret finds there is
1038
1068
  // what the first change of it undoes to, as a change is only seen once made.
@@ -1173,7 +1203,10 @@ const COMMANDS = {
1173
1203
  },
1174
1204
  // 搜索文档 opened the report at a hit: its words scrolled into view and marked for a moment (find/).
1175
1205
  async reveal(message) {
1176
- return revealText(message, (node) => modalOps().show(node));
1206
+ return revealText(message, (node) => {
1207
+ blocks?.folds.showAround(node); // a folded heading's section opened (fold/)
1208
+ return modalOps().show(node);
1209
+ });
1177
1210
  },
1178
1211
  // A link or an address opened the report at a block.
1179
1212
  async "reveal-block"({ block }) {
@@ -1222,11 +1255,12 @@ const COMMANDS = {
1222
1255
  // ---------- links to blocks and documents ----------
1223
1256
 
1224
1257
  // The block of this id, once the page is drawn (what its charts move is moved by then), or an element
1225
- // of this id (an address may name a heading's): what hides it opened (the kit's DocKit.reveal: a
1226
- // detail card that does not hold it closed, a disclosure, a tab, the card it is in), in the middle of
1227
- // the window and selected, as notion-lite's revealBlock, and the keys are the page's. What was just
1228
- // shown lays itself out in the frames after (a tab's tables) and a card closed gives the focus back
1229
- // then, so it is brought there again before it is selected. A block that is gone is said, unless
1258
+ // of this id (an address may name a heading's): what hides it opened (a folded heading's section,
1259
+ // folded.mjs reveal, then the kit's DocKit.reveal: a detail card that does not hold it closed, a
1260
+ // disclosure, a tab, the card it is in), in the middle of the window and selected, as notion-lite's
1261
+ // revealBlock, and the keys are the page's. What was just shown lays itself out in the frames after (a
1262
+ // tab's tables) and a card closed gives the focus back then, so it is brought there again before it is
1263
+ // selected. A block that is gone is said, unless
1230
1264
  // `quiet`, the page staying as it is; #top with no such element is the top of the page, as for a browser.
1231
1265
  // A later step (a link, the way back or forth, an address) started past its waits leaves it there.
1232
1266
  async function revealBlock(id, { quiet = false } = {}) {
@@ -1239,7 +1273,7 @@ async function revealBlock(id, { quiet = false } = {}) {
1239
1273
  else if (!quiet) say(t("这个块已经不在了。"));
1240
1274
  return { ok: false, reason: "missing" };
1241
1275
  }
1242
- globalThis.DocKit.reveal(block);
1276
+ reveal(block); // a fold over it too (fold/)
1243
1277
  block.scrollIntoView({ block: "center" });
1244
1278
  await painted();
1245
1279
  await painted();
@@ -1418,7 +1452,10 @@ function handOff() {
1418
1452
  // The workbench around the page, if any: it hears the page's state and notices, and asks it to save,
1419
1453
  // close and show the report again.
1420
1454
  function connect() {
1421
- workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: currentTitle(), icon: pageIcon(), state: sessionState(), annotations: annotate?.count() }), commands: COMMANDS, modals: modalOps() });
1455
+ // The look the page shows (字体与布局: its <html>'s, src/shared/document-look.mjs), which the workbench's
1456
+ // 更多 marks as chosen; told again when it changes (the kit's ui.js, doc-look-change).
1457
+ workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: currentTitle(), icon: pageIcon(), look: lookOfElement(document.documentElement), state: sessionState(), annotations: annotate?.count() }), commands: COMMANDS, modals: modalOps() });
1458
+ addEventListener("doc-look-change", () => workbench?.update());
1422
1459
  relayTo(workbench ? workbench.notice : null, workbench ? workbench.noticeRoom : null); // the workbench shows the page's notices, above the page's bottom panels
1423
1460
  handOff();
1424
1461
  // A page on its own in a frame (?solo: the 写作偏好 dialog's, which no session asks to save) is saved
@@ -1471,6 +1508,7 @@ async function start() {
1471
1508
  else if (typeof state.docBlock === "string") revealBlock(state.docBlock, { quiet: true });
1472
1509
  });
1473
1510
  annotate?.start();
1511
+ agentChanges?.start();
1474
1512
  const open = await openTabs();
1475
1513
  if (open.has(TAB)) { // a duplicated tab: its own id from now on
1476
1514
  TAB = newTab();
@@ -1569,7 +1607,7 @@ function boot() {
1569
1607
  atMenu = createAtMenu({ composing: () => composing, page: thisPage, recordText, recordStep });
1570
1608
  markdown = createMarkdown({ slash, at: atMenu, convert: convertHost, recordStep });
1571
1609
  blocks = createBlocks({
1572
- units, boxes, active, struct, look: lookStep, say, edit: editBlock, turnInto, convert: convertUnit, turnIntoAll: groupTargets, convertAll, add: insert.addAfter, after: insert.paragraphAfter,
1610
+ slug: MAP.slug, units, boxes, active, struct, look: lookStep, say, edit: editBlock, turnInto, convert: convertUnit, turnIntoAll: groupTargets, convertAll, add: insert.addAfter, after: insert.paragraphAfter,
1573
1611
  below: insertBelow, belowTypes: belowTargets,
1574
1612
  blank: insert.clickBlank, nest: (units, delta) => pageObjects().some((page) => page.nestBlocks?.(units, delta)),
1575
1613
  keepsHandle: (node) => tables?.holds(node) ?? false,
@@ -1605,14 +1643,30 @@ function boot() {
1605
1643
  changed: () => workbench?.update(),
1606
1644
  removed: (items) => workbench?.annotationsRemoved(items),
1607
1645
  });
1646
+ // Read at once, drawn once the page is ready; commits and autosave are made as the page edits (enter).
1647
+ if (!SOLO) agentChanges = createAgentChanges({
1648
+ slug: MAP.slug, token: MAP.token, commits: () => commits, autosave: () => autosave, say, session,
1649
+ across: crossText, // a selection across blocks, kept on its words as the marks go and come
1650
+ flush: async () => {
1651
+ checkAll();
1652
+ return (await autosave?.flush()) ?? "nothing";
1653
+ },
1654
+ });
1608
1655
  document.addEventListener("pointerup", (event) => {
1609
1656
  pressing = false;
1610
1657
  formatting.released();
1611
- // A click at an edge of inline code: the caret on the side it was made on.
1658
+ // A click at an edge of inline code or an entry: the caret on the side it was made on, when the
1659
+ // press came down on that text; a press elsewhere that put the caret there (a card's backdrop,
1660
+ // which closed it and put the caret at its entry; the page's empty space) leaves it at the stop
1661
+ // it is at.
1612
1662
  const selection = getSelection();
1613
1663
  const host = editing && event.button === 0 && selection.rangeCount && selection.isCollapsed ? editable(selection.anchorNode) : null;
1664
+ const pressed = pressedText;
1665
+ pressedText = null;
1614
1666
  if (host && outOfAtom(event.clientX)) return; // a click beside a formula that put the caret in it
1615
- if (host && textFilters(host)) codeClick(host, event.clientX);
1667
+ if (!host || !textFilters(host)) return;
1668
+ if (pressed && (pressed === host || pressed.contains(host))) codeClick(host, event.clientX);
1669
+ else settleCodeEdge(host);
1616
1670
  }, true);
1617
1671
  document.addEventListener("focusin", passTab, true);
1618
1672
  document.addEventListener("compositionstart", (event) => {
@@ -1711,6 +1765,7 @@ function boot() {
1711
1765
  checkAll();
1712
1766
  autosave?.leave();
1713
1767
  annotate?.leave();
1768
+ agentChanges?.leave();
1714
1769
  });
1715
1770
  document.addEventListener("visibilitychange", () => {
1716
1771
  if (!document.hidden || !autosave) return;
@@ -1,19 +1,24 @@
1
- // Taken over from notion-lite (its src/ui/media-picker/emoji-tab.js), as it is: only what it imports is
2
- // Gallery's own (support.mjs, src/shared/emoji/). Its notes, below, are notion-lite's.
1
+ // Taken over from notion-lite (its src/ui/media-picker/emoji-tab.js): its structure and behaviour as
2
+ // there, what it imports Gallery's own (support.mjs, src/shared/emoji/), and since 2026-11 (the user's
3
+ // word) its look Gallery's own (styles.mjs: the editor's palette, no blue), no 旗帜 group (the data holds
4
+ // no flag: src/shared/emoji/data/README.md), every emoji a Twemoji picture on every system, the skin
5
+ // button's hand too (the system's font only for one the pack lacks), and a group's title sticking to
6
+ // the list's top. Its notes, below, are notion-lite's, those changes written in.
3
7
  /* “表情符号”页签(943244 EmojiPicker、885963 选肤色、625478 格子和肤色变体;尺寸是线上实测)。
4
- * 分组:最近(筛选词为空时)、标注(从标注打开时,固定 26 个)、人物、动物与自然、食物与饮料、活动、旅行与地点、物品、符号、旗帜、
8
+ * 分组:最近(筛选词为空时)、标注(从标注打开时,固定 26 个)、人物、动物与自然、食物与饮料、活动、旅行与地点、物品、符号(没有旗帜:Gallery 整组去掉)、
5
9
  * 自定义(本机的自定义表情,按名字排;第一个格子是“添加表情符号”的圆形加号)
6
- * 每行 (408 − 24) / 32 = 12 个;标题 12px / 500、上 6+6 下 8;格子 32×32、圆角 4、表情 24(../emoji/emoji.js)
10
+ * 每行 (408 − 24) / 32 = 12 个;标题 12px / 500、高 28,滚动时钉在列表顶上;格子 32×32、圆角 6、表情 24(Twemoji 图,../emoji/emoji.js)
7
11
  * 筛选(943244 W):按关键词打分——筛选词里有这个表情 > “:筛选词” > 关键词等于 > 关键词开头 > 多个词都对上 > 包含,常用的加分;
8
12
  * 自定义表情的关键词是它的名字(871871 g)。结果(连同加号)超过 12 × 12 / 2 = 72 个时仍按分组显示,否则拍平成一组按分数排
9
13
  * (加号排最后);只剩加号时显示“无结果”和“添加表情符号”按钮
10
- * 底部分类栏(943244 y):上边一条 --c-borSec,内边距 8 12 10;按钮 32×32、图标 22;当前滚到的分组按钮有底色;
11
- * 没有表情的分组按钮半透明不可点(“自定义”“添加”总能点);悬停提示分组名(左对齐、在下方);点了把那一组滚到顶上,
14
+ * 底部分类栏(943244 y):上边一条 --ge-line,内边距 6 12 8;按钮 28×28、图标 18;当前滚到的分组按钮有底色;
15
+ * 没有表情的分组按钮半透明不可点(“自定义”“添加”总能点);悬停提示分组名(左对齐、在下方);点了把那一组(连同组名)滚到顶上,
12
16
  * “添加”打开“添加表情符号”浮层。筛选结果拍平时不显示
13
- * 最近(943244 F):常用表情按次数、时间排,取 2 行;选中一个表情记一次(../emoji/data.js recordEmojiUse)
17
+ * 最近(943244 F):常用表情按次数、时间排,取 2 行,只取数据还认得的和自定义表情(以前用过的旗不再出现);打开时读一次;
18
+ * 选中一个表情记一次(../emoji/data.js recordEmojiUse)
14
19
  * 格子(625478):没设过肤色时点有肤色变体的表情,先弹出一排变体(选了正好 5 个变体的就记下肤色);
15
20
  * 自定义表情(758388)24×24、contain,提示是它的名字
16
- * 右边按钮(885963):选肤色,28×28、字号 20 的“✋”(设过就是那个肤色的手),点开一排六只手
21
+ * 右边按钮(885963):选肤色,28×28、18 的“✋”(Twemoji 图,设过就是那个肤色的手;数据到了再画),点开一排六只手
17
22
  * 随机(859941):活动 / 物品 / 旅行与地点里随便一个,选上但不关菜单
18
23
  * 添加表情符号(943244 Y → 757573 → 749527 tM):在加号 / 底栏按钮 / 无结果按钮下面打开 ./upload.js 的浮层;
19
24
  * 存好以后交给 env.uploaded(选上这个表情、关菜单)
@@ -52,7 +57,6 @@ const CATEGORIES = [
52
57
  { id: 'travel', category: 'Travel & Places', title: t("旅行与地点"), icon: 'airplane' },
53
58
  { id: 'objects', category: 'Objects', title: t("物品"), icon: 'lightBulbBright' },
54
59
  { id: 'symbols', category: 'Symbols', title: t("符号"), icon: 'checkmarkCircle' },
55
- { id: 'flags', category: 'Flags', title: t("旗帜"), icon: 'flag' },
56
60
  { id: 'custom', title: t("自定义"), icon: 'squareGrid2X2' },
57
61
  { id: 'add', title: ADD_LABEL, icon: 'plusCircle' },
58
62
  ];
@@ -89,7 +93,7 @@ function rank(emoji, filter, splitFilter) {
89
93
 
90
94
  /* 943244 W:分好组、切成一行行。返回 { categorized, rows: [{ id, title?, emojis }] };只剩加号时 rows 是 [{ emojis: [ADD] }] */
91
95
  function buildRows(rawFilter, isCallout, recent) {
92
- const cats = [!rawFilter.trim() ? CATEGORY.recent : null, isCallout ? CATEGORY.callout : null, ...CATEGORIES.slice(2, 10), CATEGORY.custom].filter(Boolean);
96
+ const cats = [!rawFilter.trim() ? CATEGORY.recent : null, isCallout ? CATEGORY.callout : null, ...CATEGORIES.filter(c => c.category), CATEGORY.custom].filter(Boolean);
93
97
  const filter = normalizeFilter(rawFilter) ?? '';
94
98
  const split = rawFilter.split(' ').filter(Boolean);
95
99
  const rows = [];
@@ -152,7 +156,7 @@ export function createEmojiTab(env) {
152
156
  el.className = 'nmm-body';
153
157
  el.innerHTML = `<div class="nmm-scroller notion-scroller vertical"><div role="grid" class="nmm-grid"></div></div><div class="nmm-categories"></div>`;
154
158
  const grid = el.querySelector('.nmm-grid'), scroller = el.querySelector('.nmm-scroller'), bar = el.querySelector('.nmm-categories');
155
- const recent = recentEmojis(PER_ROW);
159
+ let recent = null; // read once, when the data is in (render; Notion's: not reordered while open)
156
160
  let view = { categorized: true, rows: [] }, lastFilter = '', addPopup = null;
157
161
  const focus = createGridFocus({ scroller, grid, rows: () => view.rows.map(r => r.emojis.length) });
158
162
 
@@ -174,6 +178,10 @@ export function createEmojiTab(env) {
174
178
  function render(filter) {
175
179
  lastFilter = filter;
176
180
  if (!emojiDataLoaded()) { grid.innerHTML = ''; bar.hidden = true; view = { categorized: true, rows: [] }; return; } // 数据加载完再画
181
+ if (recent === null) { // 数据到了:最近(只要还认得的,去掉的旗不再出现)读一次,选肤色的手画成 Twemoji 图
182
+ recent = recentEmojis(PER_ROW, e => isCustomEmojiString(e) || Boolean(emojiInfo(e)));
183
+ paintSkin();
184
+ }
177
185
  view = buildRows(filter, env.isCallout, recent);
178
186
  const { rows } = view;
179
187
  let html = '';
@@ -205,7 +213,7 @@ export function createEmojiTab(env) {
205
213
  const present = new Set(view.rows.map(r => r.id));
206
214
  const enabled = c => c.id === 'custom' || c.id === 'add' || present.has(c.id);
207
215
  bar.innerHTML = CATEGORIES.filter(c => c.id !== 'callout').map(c =>
208
- `<div role="button" tabindex="-1" class="nmm-category${enabled(c) ? '' : ' disabled'}" data-category="${c.id}" aria-label="${t("跳转到类别:{0}", esc(c.category ?? c.id))}" aria-pressed="false"${enabled(c) ? '' : ' aria-disabled="true"'}>${icon(c.icon, 22)}</div>`).join('');
216
+ `<div role="button" tabindex="-1" class="nmm-category${enabled(c) ? '' : ' disabled'}" data-category="${c.id}" aria-label="${t("跳转到类别:{0}", esc(c.category ?? c.id))}" aria-pressed="false"${enabled(c) ? '' : ' aria-disabled="true"'}>${icon(c.icon, 18)}</div>`).join('');
209
217
  for (const b of bar.children) hoverTooltip(b, CATEGORY[b.dataset.category].title, { placement: 'bottom', align: 'start' });
210
218
  updateBarFocus();
211
219
  }
@@ -258,7 +266,8 @@ export function createEmojiTab(env) {
258
266
  right.className = 'nmm-skin-wrap';
259
267
  right.innerHTML = `<div role="button" tabindex="0" class="nmm-skin" data-act="skin"></div>`;
260
268
  const skinBtn = right.firstElementChild;
261
- const paintSkin = () => { const tone = getSkinTone(); skinBtn.textContent = tone !== undefined ? SKIN_TONE_HANDS[tone] : '✋'; };
269
+ // The hand as a Twemoji picture (a placeholder of its size until the data is in: render paints it again then).
270
+ function paintSkin() { skinBtn.innerHTML = emojiHTML(SKIN_TONE_HANDS[getSkinTone() ?? 0], { size: 18 }); }
262
271
  paintSkin();
263
272
  hoverTooltip(skinBtn, t("选择肤色"), { placement: 'bottom' });
264
273
  skinBtn.addEventListener('click', () => {
@@ -1,7 +1,8 @@
1
- // Taken over from notion-lite (its src/ui/media-picker/grid.js), as it is: only what it imports is
2
- // Gallery's own (support.mjs, src/shared/emoji/). Its notes, below, are notion-lite's.
1
+ // Taken over from notion-lite (its src/ui/media-picker/grid.js): what it imports is Gallery's own
2
+ // (support.mjs, src/shared/emoji/), its look too (styles.mjs: a deeper gray, no blue ring), and a cell the
3
+ // keys reach stays below a group's title stuck at the list's top. Its notes, below, are notion-lite's.
3
4
  /* 表情 / 图标格子共用的焦点(558045 Grid + 625478 / 485357 H 的提示):
4
- * 鼠标悬停和方向键共用一个焦点(有底色);方向键不绕回,上下按列对齐(短的一行取最后一个);键盘移动时焦点格有蓝色焦点环、
5
+ * 鼠标悬停和方向键共用一个焦点(有底色);方向键不绕回,上下按列对齐(短的一行取最后一个);键盘移动时焦点格是更深的灰底、
5
6
  * 滚进视野,并且一直显示名字(forceVisibleState);鼠标悬停 150ms 后在下方显示名字(closeDelay 0)。
6
7
  * 懒加载:格子里 img[data-src] / img[data-svg](表情包里的码点)滚到附近才换成 src(../emoji/emoji.js lazySrc;矢量表情一千多张,一次全做太慢)。
7
8
  * 格子是 .nmm-cell[data-r][data-c],提示文字在 data-tip(没有就用 aria-label)。
@@ -25,8 +26,9 @@ export function createGridFocus({ scroller, grid, rows }) {
25
26
  cell.classList.add('focused');
26
27
  if (!byKeyboard) return;
27
28
  cell.classList.add('focus-visible');
28
- const cr = cell.getBoundingClientRect(), sr = scroller.getBoundingClientRect();
29
- if (cr.top < sr.top) scroller.scrollTop -= sr.top - cr.top;
29
+ const cover = grid.querySelector('.nmm-section-title')?.offsetHeight ?? 0; // a group's title stays at the top (styles.mjs: sticky); a flat search has none
30
+ const cr = cell.getBoundingClientRect(), sr = scroller.getBoundingClientRect(), top = sr.top + cover;
31
+ if (cr.top < top) scroller.scrollTop -= top - cr.top;
30
32
  else if (cr.bottom > sr.bottom) scroller.scrollTop += cr.bottom - sr.bottom;
31
33
  if (tipOf(cell)) showTooltip(cell.getBoundingClientRect(), tipOf(cell), { placement: 'bottom' });
32
34
  }
@@ -1,4 +1,4 @@
1
- // The 图标 tab (notion-lite's src/ui/media-picker/icon-tab.js, its grid, colors and sizes as there),
1
+ // The 图标 tab (notion-lite's src/ui/media-picker/icon-tab.js, its grid and behaviour as there, its look Gallery's own: styles.mjs),
2
2
  // its icons Gallery's own: two open-source sets of the same 443 things, MingCute and Tabler, and
3
3
  // Tabler's 40 numbers, in 12 categories (src/shared/emoji/glyphs.mjs). Its notes, below, are
4
4
  // notion-lite's, the icons' own changed.
@@ -13,8 +13,8 @@
13
13
  * 数字只有 Tabler 有:切换时不动,选中的也总是 Tabler 的值(提示里写明 Tabler)
14
14
  * 颜色(icon_picker_color):设了就直接用这个颜色插入、格子也显示这个颜色;没设(“每次询问”)时格子显示默认灰色,
15
15
  * 点一下先弹出 10 种颜色(两行各 5 个、行内边距 2、间距 1,贴在格子下面)再选
16
- * 右边按钮(q):选图标颜色——28×28 带边框,圆圈图标描边 / 填充是当前颜色(没设时描边浅灰、填充 --c-icoTer);
17
- * 菜单 134 宽:两行色块(22×22、圆角 6,没设颜色时这两行半透明)+ 分隔线下“每次询问”开关
16
+ * 右边按钮(q):选图标颜色——28×28 无框,圆圈图标描边 / 填充是当前颜色(没设时描边浅灰、填充 --ge-ico3);
17
+ * 菜单 160 宽:两行色块(26×26、圆角 6,没设颜色时这两行半透明)+ 分隔线下“每次询问”开关(开是中性灰,不用蓝)
18
18
  * 随机:随便一组里这一套的图标,颜色用设好的,没设就随便一种;选上但不关菜单
19
19
  * 选中一个图标:记进最近(按组记,两套共用),值是 `/icons/<名字>_<颜色>.svg`(MingCute)或 `/icons/tabler/<名字>_<颜色>.svg`(数字总是这个)。
20
20
  * createIconTab(env) → { el, left, right, load(), render(filter), move(key), activateFocused(), random() }
@@ -178,7 +178,7 @@ export function createIconTab(env) {
178
178
  function paintColor() {
179
179
  const color = getIconColor(), values = ICON_COLOR_BY_THEME[theme()];
180
180
  colorBtn.setAttribute('aria-label', t("选择图标颜色,已选定 {0}", color ? colorWord(color) : t("无颜色")));
181
- colorBtn.innerHTML = `<svg viewBox="0 0 16 16" aria-hidden="true" style="width:14px;height:14px;display:block;stroke:${color ? values[color] : values.lightgray};fill:${color ? values[color] : 'var(--c-icoTer)'}">${CIRCLE_16}</svg>`;
181
+ colorBtn.innerHTML = `<svg viewBox="0 0 16 16" aria-hidden="true" style="width:14px;height:14px;display:block;stroke:${color ? values[color] : values.lightgray};fill:${color ? values[color] : 'var(--ge-ico3)'}">${CIRCLE_16}</svg>`;
182
182
  }
183
183
  paintColor();
184
184
  hoverTooltip(colorBtn, t("选择图标颜色"), { placement: 'bottom' });
@@ -13,7 +13,6 @@ const ICONS = {
13
13
  airplane: svg('<path d="M17.25 10c0-.9-.8-1.5-1.7-1.5H11L7.5 3.5H5.75l1.75 5H4.25L3 7H2l.75 3L2 13h1l1.25-1.5H7.5l-1.75 5H7.5L11 11.5h4.55c.9 0 1.7-.6 1.7-1.5z"/>'),
14
14
  lightBulbBright: svg('<path d="M10 2.75a5 5 0 0 0-3 9c.6.5 1 1.2 1 2v.5h4v-.5c0-.8.4-1.5 1-2a5 5 0 0 0-3-9z"/><path d="M8.25 16.5h3.5"/>'),
15
15
  checkmarkCircle: svg('<circle cx="10" cy="10" r="7.25"/><path d="m6.75 10.25 2.4 2.4 4.1-4.9"/>'),
16
- flag: svg('<path d="M5.25 17V3.75"/><path d="M5.25 4.75c2.5-1.5 4.5 1.5 7 0 1-.6 2-.8 2.75-.7v6.5c-.75-.1-1.75.1-2.75.7-2.5 1.5-4.5-1.5-7 0"/>'),
17
16
  squareGrid2X2: svg('<rect x="3.5" y="3.5" width="5" height="5" rx="1.25"/><rect x="11.5" y="3.5" width="5" height="5" rx="1.25"/><rect x="3.5" y="11.5" width="5" height="5" rx="1.25"/><rect x="11.5" y="11.5" width="5" height="5" rx="1.25"/>'),
18
17
  plusCircle: svg('<circle cx="10" cy="10" r="7.25"/><path d="M10 6.75v6.5M6.75 10h6.5"/>'),
19
18
  xMarkCircle: svg('<circle cx="10" cy="10" r="7.25"/><path d="m7.75 7.75 4.5 4.5m0-4.5-4.5 4.5"/>'),
@@ -1,11 +1,13 @@
1
- // Taken over from notion-lite (its src/ui/media-picker/index.js), as it is: only what it imports is
2
- // Gallery's own (support.mjs, src/shared/emoji/). Its notes, below, are notion-lite's.
1
+ // Taken over from notion-lite (its src/ui/media-picker/index.js): its structure and behaviour as there,
2
+ // what it imports Gallery's own (support.mjs, src/shared/emoji/), and since 2026-11 (the user's word) its
3
+ // look Gallery's own (styles.mjs: the editor's palette, text tabs as the formula editor's, no blue). Its
4
+ // notes, below, are notion-lite's, the look's changed.
3
5
  /* 图标选择器(Notion 485357 MediaPicker,页签由 569553 m 给:表情符号 / 图标 / 上传;尺寸、颜色是线上实测):标注图标、选项卡图标、页面图标共用。
4
6
  * 宽 408(569553 popupWidth);“表情符号”“图标”页签高 390(tabHeight),“上传”页签没给高度,随内容。
5
- * 页签栏(eU):高 40、左右 8、底下一条 --ca-borSecTra;“表情符号 / 图标 / 上传”,选中的下面 2px 线;右边“移除”(灰色文字按钮)。
7
+ * 页签栏(eU):上下 6、左右 8、底下一条 --ge-line;“表情符号 / 图标 / 上传”是文字页签,当前的一块灰底(没有下划线);右边“移除”(灰色文字按钮)。
6
8
  * 打开时当前图标是 Notion 图标就在“图标”页签,否则在“表情符号”(569553 d;Notion 的当前页签只记在内存里)
7
- * 筛选行(ec):只有“表情符号”“图标”页签有。上 4 下 10;里面左右 8、间距 8:输入框(放大镜、“筛选…”、有字时的清除按钮)
8
- * + “随机”(28×28 带边框)+ 页签自己的按钮(表情:选肤色;图标:选图标颜色),两个页签共用同一个筛选词(onFilterChange 同时改两边)
9
+ * 筛选行(ec):只有“表情符号”“图标”页签有。8 12 6:输入框(放大镜、“筛选…”、有字时的清除按钮;聚焦时灰色内框)
10
+ * + “随机”(28×28,无框)+ 页签自己的按钮(表情:选肤色;图标:选图标颜色),两个页签共用同一个筛选词(onFilterChange 同时改两边)
9
11
  * “上传”页签(569553 custom → 963369,./upload.js):上传 / 拖进来 / 粘贴一张图片或图片链接,“保存”就用它当图标;
10
12
  * 勾上“添加到表情符号库”再起个名字,就同时存成自定义表情(以后在“表情符号”页签的“自定义”里)
11
13
  * 随机:选上但不关菜单(keepVisible)
@@ -53,9 +55,9 @@ export function openMediaPicker({ origin, value, isCallout = false, onPick, only
53
55
  `<div class="nmm-header">` +
54
56
  `<div class="nmm-tabbar">` +
55
57
  `<div role="tablist" class="nmm-tabs">` +
56
- `<div class="nmm-tab-wrap"><div role="tab" class="nmm-tab" data-tab="emoji">${t("表情符号")}</div><div class="nmm-tab-line"></div></div>` +
57
- `<div class="nmm-tab-wrap"><div role="tab" class="nmm-tab" data-tab="icon">${t("图标")}</div><div class="nmm-tab-line"></div></div>` +
58
- `<div class="nmm-tab-wrap"><div role="tab" class="nmm-tab" data-tab="upload">${t("上传")}</div><div class="nmm-tab-line"></div></div>` +
58
+ `<div class="nmm-tab-wrap"><div role="tab" class="nmm-tab" data-tab="emoji">${t("表情符号")}</div></div>` +
59
+ `<div class="nmm-tab-wrap"><div role="tab" class="nmm-tab" data-tab="icon">${t("图标")}</div></div>` +
60
+ `<div class="nmm-tab-wrap"><div role="tab" class="nmm-tab" data-tab="upload">${t("上传")}</div></div>` +
59
61
  `</div>` +
60
62
  `<div class="nmm-tabbar-right"><div role="button" tabindex="0" class="nmm-text-button" data-act="remove">${t("移除")}</div></div>` +
61
63
  `</div>` +
@@ -63,7 +65,7 @@ export function openMediaPicker({ origin, value, isCallout = false, onPick, only
63
65
  `<div class="nmm-input"><div class="nmm-input-icon">${icon('magnifyingGlassSmall', 16)}</div>` +
64
66
  `<input type="text" role="combobox" placeholder="${t("筛选…")}" autocomplete="off" spellcheck="false" aria-autocomplete="list" aria-haspopup="grid" aria-expanded="true">` +
65
67
  `<div role="button" tabindex="0" aria-label="${t("清除输入")}" class="nmm-clear" data-act="clear" hidden>${icon('xMarkCircle', 20)}</div></div>` +
66
- `<div class="nmm-filter-right"><div class="nmm-tab-left"></div><div role="button" tabindex="0" aria-label="${t("随机")}" class="nmm-random" data-act="random">${icon('arrowIntersectRightSmall', 14)}</div><div class="nmm-tab-button"></div></div>` +
68
+ `<div class="nmm-filter-right"><div class="nmm-tab-left"></div><div role="button" tabindex="0" aria-label="${t("随机")}" class="nmm-random" data-act="random">${icon('arrowIntersectRightSmall', 16)}</div><div class="nmm-tab-button"></div></div>` +
67
69
  `</div></div>` +
68
70
  `</div>` +
69
71
  `<div class="nmm-content"></div>`;