@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
@@ -0,0 +1,102 @@
1
+ import { t } from "../../../shared/i18n/index.mjs";
2
+ import { Z } from "../../../shared/layers.mjs";
3
+ import { PANEL_STYLE, onPanels, panelGone, panelShown } from "../panel.mjs";
4
+ import { addStyle, button, element, fadeIn, fadeOut, mount } from "../ui.mjs";
5
+
6
+ // The bar of the Agent's changes (docs/editor.md "Agent's changes"), Cursor's review bar: the third of
7
+ // the editor's bottom panels (panel.mjs), in the middle of the window's bottom, so that a notice
8
+ // stands above it as above the others. It says how many blocks the Agent added and changed (+) and
9
+ // changed and deleted (−), the numbers in the conflict card's green and red, and offers 撤回 and 确认
10
+ // (the primary one), buttons with no border, as the format bar's. Reverted, it says so and offers
11
+ // 撤销 for UNDO_SHOWN. A conflict card at the left or the lost texts at the right reaching under it
12
+ // lift it 8px above them.
13
+
14
+ const STYLE = `
15
+ .ge-changes { position: fixed; left: 50%; bottom: var(--ge-changes-bottom, 16px); transform: translateX(-50%); z-index: ${Z.panel};
16
+ display: flex; align-items: center; gap: 8px; height: 40px; padding: 0 6px 0 14px; box-sizing: border-box; max-width: calc(100vw - 32px);
17
+ border-radius: 10px; background: var(--ge-bg); color: var(--ge-tx); box-shadow: var(--ge-shadow);
18
+ font: 13px/1 var(--doc-sans-font, var(--doc-font, system-ui, sans-serif)); white-space: nowrap; }
19
+ .ge-changes[hidden] { display: none; }
20
+ .ge-changes-what { color: var(--ge-tx2); }
21
+ .ge-changes-count { display: inline-flex; gap: 6px; margin-right: 4px; font-variant-numeric: tabular-nums; font-weight: 500; }
22
+ .ge-changes-plus { color: var(--ge-diff-add-text); }
23
+ .ge-changes-minus { color: var(--ge-diff-del-text); }
24
+ /* Its buttons as the editor's floating bars' (the format bar's 转换为): no border, 6px corners, a gray under the
25
+ pointer, darker pressed; 确认 the primary blue (the workbench dialog's, the media menu's 嵌入链接). */
26
+ .ge-changes button { height: 28px; padding: 0 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-weight: 500; white-space: nowrap; cursor: pointer; transition: background 20ms ease-in; }
27
+ .ge-changes button:hover:not(:disabled) { background: var(--ge-hover); }
28
+ .ge-changes button:active:not(:disabled) { background: var(--ge-press); }
29
+ .ge-changes button[data-primary] { background: var(--ge-accent); color: #fff; }
30
+ .ge-changes button[data-primary]:hover:not(:disabled) { background: var(--ge-accent-hover); }
31
+ .ge-changes button[data-primary]:active:not(:disabled) { background: var(--ge-accent-press); }
32
+ .ge-changes button:disabled { opacity: .45; cursor: default; }
33
+ @media print { .ge-changes { display: none; } }
34
+ `;
35
+
36
+ export const UNDO_SHOWN = 6000; // how long a revert can be undone from the bar
37
+ const ABOVE = 8; // px between a bottom panel and the bar lifted over it
38
+ const BOTTOM = 16;
39
+
40
+ /** The bar: { counts(counts), reverted(left, undo), hide(), node }. revert(), accept(): what its buttons do. */
41
+ export function createBar({ revert, accept }) {
42
+ addStyle(PANEL_STYLE);
43
+ addStyle(STYLE);
44
+ let bar = null;
45
+ let timer = 0;
46
+ // Lifted over a bottom panel it would stand on.
47
+ const place = () => {
48
+ if (!bar || bar.hidden) return;
49
+ const own = bar.getBoundingClientRect();
50
+ let lift = BOTTOM;
51
+ for (const panel of document.querySelectorAll(".ge-conflicts:not([hidden]), .ge-panel")) {
52
+ const rect = panel.getBoundingClientRect();
53
+ if (rect.height > 0 && rect.left < own.right && rect.right > own.left) lift = Math.max(lift, Math.round(innerHeight - rect.top) + ABOVE);
54
+ }
55
+ bar.style.setProperty("--ge-changes-bottom", `${lift}px`);
56
+ };
57
+ onPanels(place);
58
+ addEventListener("resize", place, { passive: true });
59
+ const made = () => {
60
+ if (!bar) bar = mount(element("div", { class: "ge-changes", role: "region", "aria-label": t("Agent 的修改"), hidden: "" }));
61
+ return bar;
62
+ };
63
+ const show = (...children) => {
64
+ clearTimeout(timer);
65
+ const node = made();
66
+ node.replaceChildren(...children);
67
+ if (node.hidden) {
68
+ fadeIn(node);
69
+ panelShown(node);
70
+ }
71
+ place();
72
+ };
73
+ return {
74
+ get node() { return bar; },
75
+ counts({ added = 0, changed = 0, removed = 0 }) {
76
+ const plus = added + changed;
77
+ const minus = removed + changed;
78
+ show(
79
+ element("span", { class: "ge-changes-what", text: t("Agent 已修改") }),
80
+ element("span", { class: "ge-changes-count", title: t("Agent 新增 {0} 块、改动 {1} 块、删除 {2} 块", added, changed, removed) },
81
+ ...(plus ? [element("span", { class: "ge-changes-plus", text: `+${plus}` })] : []),
82
+ ...(minus ? [element("span", { class: "ge-changes-minus", text: `−${minus}` })] : [])),
83
+ button(t("撤回"), revert, { title: t("撤回 Agent 的这些修改,你自己的修改会保留"), "data-action": "revert" }),
84
+ button(t("确认"), accept, { primary: true, title: t("保留这些修改,不再标出"), "data-action": "accept" }),
85
+ );
86
+ },
87
+ // Reverted `left` ms ago at most: 撤销 offered until UNDO_SHOWN is over.
88
+ reverted(left, undo) {
89
+ show(
90
+ element("span", { class: "ge-changes-what", text: t("已撤回 Agent 的修改") }),
91
+ button(t("撤销"), undo, { "data-action": "undo" }),
92
+ );
93
+ timer = setTimeout(() => this.hide(), Math.max(0, left));
94
+ },
95
+ hide() {
96
+ clearTimeout(timer);
97
+ if (!bar || bar.hidden) return;
98
+ const node = bar;
99
+ fadeOut(node, 100, () => panelGone(node));
100
+ },
101
+ };
102
+ }
@@ -0,0 +1,206 @@
1
+ import { IN_LAYER } from "../../../shared/layers.mjs";
2
+ import { KIND_NAMES } from "../../components/registry.mjs";
3
+ import { identityOf, withLeads } from "../core.mjs";
4
+ import { own } from "../overlays.mjs";
5
+ import { drawAt, layerOf } from "../overlay-layer.mjs";
6
+ import { element } from "../ui.mjs";
7
+
8
+ // The blocks an Agent deleted, shown where they were (docs/editor.md "Agent's changes"): a ghost of
9
+ // them, their words in red struck through on a faint red fill, a line each (a heading larger and
10
+ // bold, an item with its marker, nested items further in, a table a line each row, a block with no
11
+ // words its kind's name), never a block of the page: nothing of it is in the document's flows, so no
12
+ // commit, undo step, copy, selection, drop or search ever meets it. Room is made for it where the
13
+ // blocks were by a margin of the block after the place (`before`; at the document's end the last
14
+ // block's below it, `after`; with no block left at all, the body's top, `into`), written in a
15
+ // stylesheet of the editor's own keyed by that block's identity (data-ge-id: never a style
16
+ // attribute, which the file would get), and the ghost is drawn in that room in the block's overlay
17
+ // layer (overlay-layer.mjs), measured first: the room is its height and the gap the page had
18
+ // there, the gap kept above it and below it (a collapsed margin made good on a second measure). A
19
+ // block that stands beside others (a Sheet's tab in its bar, a flowchart's node in its row, a column
20
+ // of 分栏) has the room made before the row it stands in, which a margin of its own would push out of
21
+ // line; what stands at one place is one ghost in one room (two groups before one row; one before a
22
+ // block and one before the first of its blocks that stays, a paragraph before a list and the list's
23
+ // first item: a margin of the inner one would fold into the outer one's, the two drawn over each
24
+ // other). Placed again whenever the page may have moved (the overlays' frame loop), the room made
25
+ // again when its height changes (the window narrowed, its lines wrapped anew).
26
+
27
+ const REACH = 2; // px past the block at its sides, as a text block's fill
28
+ const GAP_LEAST = 6;
29
+ const GAP_MOST = 20;
30
+
31
+ export const GHOST_STYLE = `
32
+ .ge-diff-ghost { left: 0; top: 0; z-index: ${IN_LAYER.halo}; box-sizing: border-box; padding: 2px ${REACH}px; border-radius: 4px; background: var(--ge-diff-del-fill); color: var(--ge-diff-del-text); pointer-events: none; user-select: none; overflow-wrap: anywhere; }
33
+ .ge-diff-ghost-line { position: relative; white-space: pre-line; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; overflow: hidden; }
34
+ .ge-diff-ghost-line + .ge-diff-ghost-line { margin-top: 2px; }
35
+ .ge-diff-ghost-words { text-decoration: line-through; text-decoration-color: var(--ge-diff-del-text); }
36
+ .ge-diff-ghost-line[data-tag="h1"], .ge-diff-ghost-line[data-tag="h2"] { font-size: 1.25em; font-weight: 600; }
37
+ .ge-diff-ghost-line:is([data-tag="h3"], [data-tag="h4"]) { font-size: 1.1em; font-weight: 600; }
38
+ .ge-diff-ghost-line[data-tag="pre"] { font-family: var(--doc-mono, ui-monospace, SFMono-Regular, Consolas, monospace); font-size: .875em; }
39
+ .ge-diff-ghost-marker { position: absolute; transform: translateX(-100%); padding-right: .45em; font-variant-numeric: tabular-nums; }
40
+ .ge-diff-ghost-line[data-empty] .ge-diff-ghost-words { text-decoration: none; opacity: .8; }
41
+ @media print { .ge-diff-ghost { display: none; } }
42
+ `;
43
+
44
+ const shows = (node) => node.isConnected && node.getClientRects().length > 0 && node.getBoundingClientRect().width > 0;
45
+
46
+ // Whether `node` stands beside the blocks next to it rather than under them: in a row of a flex box,
47
+ // a grid of more than one column, or a line of text.
48
+ function beside(node) {
49
+ const parent = node.parentElement;
50
+ if (!parent || parent.localName === "main") return false;
51
+ const style = getComputedStyle(parent);
52
+ if (style.display.includes("flex")) return style.flexDirection.startsWith("row");
53
+ if (style.display.includes("grid")) return style.gridTemplateColumns.trim().split(/\s+/).length > 1;
54
+ return getComputedStyle(node).display.startsWith("inline");
55
+ }
56
+ // The block a room is made before or after for `node`: itself, or the row it stands in.
57
+ function rowOf(node) {
58
+ while (beside(node)) node = node.parentElement;
59
+ return node;
60
+ }
61
+ const level = (a, b) => Math.abs(a.getBoundingClientRect().top - b.getBoundingClientRect().top) < 1;
62
+
63
+ /** A line of the ghost: one deleted block (source/changes.mjs ghostRow). */
64
+ function lineOf(row) {
65
+ const words = (row.text ?? "").trim();
66
+ const line = element("div", { class: "ge-diff-ghost-line", "data-tag": row.tag ?? "" });
67
+ line.style.paddingLeft = `${(row.depth ?? 0) * 1.5 + (row.marker ? 1.5 : 0)}em`;
68
+ if (row.marker) line.append(element("span", { class: "ge-diff-ghost-marker", text: row.marker === "ol" ? `${row.number ?? 1}.` : "•" }));
69
+ if (!words) line.setAttribute("data-empty", "");
70
+ line.append(element("span", { class: "ge-diff-ghost-words", text: words || KIND_NAMES[row.kind] || "…" }));
71
+ return line;
72
+ }
73
+
74
+ /**
75
+ * The ghosts of a review's deleted blocks. Returns { add(group, anchor, edge), place(), clear(),
76
+ * drop(gone?), count, entries }: add makes one (drawn at the next place), place puts each where its
77
+ * room is (made or made again as needed), clear takes them and their rooms away.
78
+ */
79
+ export function createGhosts() {
80
+ const sheet = element("style", { "data-doc-dev": "", "data-ge-changes-room": "" });
81
+ let entries = []; // { drawn, group, anchor, edge, target, height, width, gap, room }
82
+
83
+ // The rooms, one rule each, for the screen alone (printed, the page is as the file has it). A
84
+ // room's margin is !important: the kit's spacing (a flow's first block's none, the gap between two
85
+ // kinds of block) is said by rules more specific than one attribute, and the room is added to it.
86
+ function writeRules() {
87
+ const text = entries.map((entry) => entry.room).filter(Boolean).join("\n");
88
+ const css = text ? `@media screen {\n${text}\n}` : "";
89
+ if (sheet.textContent !== css) sheet.textContent = css;
90
+ if (css && !sheet.isConnected) document.head.append(sheet);
91
+ if (!css && sheet.isConnected) sheet.remove();
92
+ }
93
+ const selectorOf = (target) => (target.localName === "main" ? "main" : `[data-ge-id="${CSS.escape(identityOf(target))}"]`);
94
+ const setRule = (entry, declaration) => {
95
+ entry.room = declaration ? `${selectorOf(entry.target)} { ${declaration} }` : null;
96
+ writeRules();
97
+ };
98
+
99
+ // Room for `height` px where `entry`'s blocks were, the gap the page had there kept on either side.
100
+ function makeRoom(entry, height) {
101
+ const { target, edge } = entry;
102
+ setRule(entry, null);
103
+ const style = getComputedStyle(target);
104
+ if (edge === "after") {
105
+ entry.gap = Math.min(GAP_MOST, Math.max(GAP_LEAST, parseFloat(style.marginBottom) || 0));
106
+ setRule(entry, `margin-bottom: ${Math.round((parseFloat(style.marginBottom) || 0) + entry.gap + height + entry.gap)}px !important;`);
107
+ return;
108
+ }
109
+ if (edge === "into") {
110
+ entry.gap = GAP_LEAST;
111
+ setRule(entry, `padding-top: ${Math.round((parseFloat(style.paddingTop) || 0) + height + 2 * entry.gap)}px !important;`);
112
+ return;
113
+ }
114
+ const top = target.getBoundingClientRect().top;
115
+ let before = target.previousElementSibling;
116
+ while (before && (before.hasAttribute("data-doc-dev") || !shows(before))) before = before.previousElementSibling;
117
+ const above = before ? top - before.getBoundingClientRect().bottom : null;
118
+ entry.gap = Math.min(GAP_MOST, Math.max(GAP_LEAST, above ?? GAP_LEAST));
119
+ const want = height + entry.gap; // how far the block goes down
120
+ let margin = (parseFloat(style.marginTop) || 0) + want;
121
+ setRule(entry, `margin-top: ${Math.round(margin)}px !important;`);
122
+ const moved = target.getBoundingClientRect().top - top;
123
+ if (Math.abs(moved - want) > 0.5) { // its margin collapsed with the one above it
124
+ margin += want - moved;
125
+ setRule(entry, `margin-top: ${Math.round(margin)}px !important;`);
126
+ }
127
+ }
128
+
129
+ return {
130
+ get count() { return entries.length; },
131
+ get entries() { return entries; },
132
+ /** A ghost of `group` (a review's `removed` entry) at `anchor`'s `edge` ("before", "after", "into"). */
133
+ add(group, anchor, edge) {
134
+ const lines = group.rows.map(lineOf);
135
+ if (group.more || group.rows.length === 0) lines.push(lineOf({ text: "…", tag: "", depth: 0 }));
136
+ const target = edge === "into" ? anchor : rowOf(edge === "before" ? withLeads(anchor)[0] : anchor);
137
+ // At the place of one made already (the groups come in the page's order): its lines go on in that one.
138
+ const one = entries.find((entry) => entry.edge === edge && (entry.target === target || (edge === "before" && entry.target.contains(target) && level(entry.target, target))));
139
+ if (one) {
140
+ one.drawn.append(...lines);
141
+ one.height = -1;
142
+ return one;
143
+ }
144
+ const drawn = element("div", { class: "ge-diff-ghost", "data-doc-dev": "", "aria-hidden": "true" }, ...lines);
145
+ const entry = { drawn, group, anchor, edge, target, height: -1, width: -1, gap: GAP_LEAST, room: null };
146
+ own(drawn, "changes", { owner: anchor, alive: () => anchor.isConnected && entries.includes(entry) });
147
+ entries.push(entry);
148
+ return entry;
149
+ },
150
+ /** Each ghost where its room is; a block not shown (a closed disclosure, another tab) neither ghost nor room. */
151
+ place() {
152
+ let changed = false;
153
+ for (const entry of entries) {
154
+ const { drawn: node, target } = entry;
155
+ if (!entry.anchor.isConnected || !shows(target)) {
156
+ node.remove();
157
+ if (entry.room) {
158
+ entry.room = null;
159
+ entry.height = -1;
160
+ changed = true;
161
+ }
162
+ continue;
163
+ }
164
+ const layer = layerOf(target);
165
+ if (node.parentNode !== layer) {
166
+ const font = getComputedStyle(target.closest("main") ?? document.body);
167
+ Object.assign(node.style, { fontFamily: font.fontFamily, fontSize: font.fontSize, lineHeight: font.lineHeight });
168
+ layer.append(node);
169
+ }
170
+ let rect = target.getBoundingClientRect();
171
+ const width = Math.round(rect.width + 2 * REACH);
172
+ if (width !== entry.width) {
173
+ node.style.width = `${width}px`;
174
+ entry.width = width;
175
+ }
176
+ const height = node.offsetHeight;
177
+ if (height !== entry.height) {
178
+ makeRoom(entry, height);
179
+ entry.height = height;
180
+ rect = target.getBoundingClientRect();
181
+ }
182
+ const top = entry.edge === "before" ? rect.top - entry.gap - height
183
+ : entry.edge === "after" ? rect.bottom + entry.gap
184
+ : rect.top + (parseFloat(getComputedStyle(target).paddingTop) || 0) - height - entry.gap;
185
+ const origin = layer.getBoundingClientRect();
186
+ drawAt(node, layer, rect.left - REACH - origin.left, top - origin.top);
187
+ }
188
+ if (changed) writeRules();
189
+ return entries.length > 0;
190
+ },
191
+ /** Ghosts whose block left the page go, and their rooms. */
192
+ settle() {
193
+ const gone = entries.filter((entry) => !entry.anchor.isConnected);
194
+ if (gone.length === 0) return;
195
+ for (const entry of gone) entry.drawn.remove();
196
+ entries = entries.filter((entry) => !gone.includes(entry));
197
+ writeRules();
198
+ },
199
+ /** Every ghost and room taken away. */
200
+ clear() {
201
+ for (const entry of entries) entry.drawn.remove();
202
+ entries = [];
203
+ writeRules();
204
+ },
205
+ };
206
+ }
@@ -0,0 +1,94 @@
1
+ import { isPad } from "../pads.mjs";
2
+
3
+ // The words an Agent took out of a text block, shown where they were (docs/editor.md "Agent's
4
+ // changes"): an empty inline span of the editor's own in the block's text (`span.ge-diff-del`,
5
+ // `data-ge-gone-words` holding the words, drawn by its ::before, contenteditable="false",
6
+ // aria-hidden, data-doc-dev). It holds no text node, so whatever reads the block's words (its
7
+ // textContent, a selection's text, readText, the search, the outline, a copy's plain text) reads none
8
+ // of them; what reads its markup leaves it out (core.mjs serializeNodes, writing the text on either side
9
+ // of it as one; ownHTML, which undo records; a copy's clipboard.own, which drops data-doc-dev). Put in, it
10
+ // splits the text node it stands in (the live ranges, the selection and the added words' highlight
11
+ // among them, follow); taken out, the two halves are one text node again, the caret where it was (and
12
+ // the live ranges it is given: a selection across blocks', which keeps its ends as points of its own).
13
+
14
+ const ATTRIBUTE = "data-ge-gone-words";
15
+
16
+ /**
17
+ * A mark of `words` taken out (not on the page yet). `before`, `after`: the characters of the text on
18
+ * either side of where it goes. A space at either end of the words is drawn apart from them, neither
19
+ * struck nor filled (data-ge-gone-lead, -tail), and only where the text beside has none: "words
20
+ * [that were removed] stay", never a struck space.
21
+ */
22
+ export function goneMark(words, doc = document, { before, after } = {}) {
23
+ const span = doc.createElement("span");
24
+ span.className = "ge-diff-del";
25
+ const bare = words.trim();
26
+ span.setAttribute(ATTRIBUTE, bare || words);
27
+ if (bare && /^\s/.test(words) && before !== undefined && !/\s/.test(before)) span.setAttribute("data-ge-gone-lead", "");
28
+ if (bare && /\s$/.test(words) && after !== undefined && !/\s/.test(after)) span.setAttribute("data-ge-gone-tail", "");
29
+ span.setAttribute("contenteditable", "false");
30
+ span.setAttribute("aria-hidden", "true");
31
+ span.setAttribute("data-doc-dev", "");
32
+ return span;
33
+ }
34
+
35
+ /**
36
+ * Where words taken out at character `at` of `read` (annotate/anchors.mjs readText of the block)
37
+ * stand: before that character, or after the one before it when it is a line's end or there is
38
+ * none (the words taken out at the block's end). { place, side }, or null for a block with no text.
39
+ */
40
+ export function placeOf(read, at) {
41
+ if (read.at[at]) return { place: read.at[at], side: "before" };
42
+ if (at > 0 && read.at[at - 1]) return { place: read.at[at - 1], side: "after" };
43
+ for (let index = at + 1; index < read.at.length; index++) if (read.at[index]) return { place: read.at[index], side: "before" };
44
+ for (let index = Math.min(at, read.at.length) - 1; index >= 0; index--) if (read.at[index]) return { place: read.at[index], side: "after" };
45
+ return null;
46
+ }
47
+
48
+ /** Put `mark` at `where` (placeOf): in a text node split there, or beside a formula or a chip, never inside one. */
49
+ export function putMark(mark, { place, side }) {
50
+ if (place.node) {
51
+ const { node } = place;
52
+ const at = side === "before" ? place.offset : place.offset + 1;
53
+ if (at <= 0) node.before(mark);
54
+ else if (at >= node.length) node.after(mark);
55
+ else node.splitText(at).before(mark);
56
+ return;
57
+ }
58
+ const beside = place.element.closest("[data-ge-atom]") ?? place.element;
59
+ if (side === "before") beside.before(mark);
60
+ else beside.after(mark);
61
+ }
62
+
63
+ /**
64
+ * Take `mark` off the page, the text on either side of it one text node again, the selection where it
65
+ * was, and so each of `ranges` (live ranges a tool keeps: a selection across blocks' pieces,
66
+ * text-select/), whose ends in the second half would otherwise fall out of the text with it.
67
+ */
68
+ export function takeMark(mark, ranges = []) {
69
+ const first = mark.previousSibling;
70
+ const second = mark.nextSibling;
71
+ mark.remove();
72
+ if (first?.nodeType !== 3 || second?.nodeType !== 3 || isPad(first) || isPad(second)) return;
73
+ const selection = first.ownerDocument.getSelection();
74
+ const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
75
+ const length = first.length;
76
+ const held = ranges.map((range) => [range, range.startContainer === second ? range.startOffset : -1, range.endContainer === second ? range.endOffset : -1]);
77
+ first.appendData(second.data);
78
+ second.remove();
79
+ for (const [range, start, end] of held) {
80
+ if (start >= 0) range.setStart(first, length + start);
81
+ if (end >= 0) range.setEnd(first, length + end);
82
+ }
83
+ if (anchorNode !== second && focusNode !== second) return; // where it is still says the same (a point in `first`, or in their parent)
84
+ const point = (node, offset) => (node === second ? [first, length + offset] : [node, offset]);
85
+ selection.setBaseAndExtent(...point(anchorNode, anchorOffset), ...point(focusNode, focusOffset));
86
+ }
87
+
88
+ // The reader edits a block holding marks: they go first, before the edit touches it (changes/index.mjs).
89
+ // A key edits unless it only moves the caret or the view, or copies, selects all, finds, saves or prints.
90
+ const MOVES = /^(Arrow(Up|Down|Left|Right)|Home|End|PageUp|PageDown|Escape|Shift|Control|Alt|Meta|AltGraph|CapsLock|NumLock|ScrollLock|ContextMenu|F\d{1,2})$/;
91
+ const KEEPS = /^[acfsp]$/;
92
+
93
+ /** Whether a keydown may edit the block the caret is in (an input method's Process key does). */
94
+ export const editsBy = (event) => !MOVES.test(event.key) && !((event.ctrlKey || event.metaKey) && !event.altKey && !event.shiftKey && KEEPS.test(event.key));