@deepseek-ai/dsh-client-ui-primitives 0.1.1-rc.2 → 0.1.2-alpha.3
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.
- package/README.i18n.yaml +2 -2
- package/README.md +91 -24
- package/README.zh.md +92 -25
- package/lib/ConnectionIndicator.module.css +107 -0
- package/lib/DiffBlock.module.css +0 -6
- package/lib/DisclosureRow.module.css +21 -6
- package/lib/ReadBlock.module.css +0 -6
- package/lib/SearchBlock.module.css +0 -8
- package/lib/TerminalBlock.module.css +0 -6
- package/lib/Toast.module.css +7 -10
- package/lib/Tooltip.module.css +0 -7
- package/lib/WebBlock.module.css +0 -6
- package/lib/index.js +1243 -352
- package/lib/markdown/CodeBlock.module.css +4 -2
- package/lib/markdown/MarkdownText.module.css +2 -2
- package/lib/types/ConnectionIndicator.d.ts +25 -0
- package/lib/types/DiffBlock.d.ts +28 -9
- package/lib/types/FishLogo.d.ts +7 -0
- package/lib/types/FoldToggle.d.ts +20 -0
- package/lib/types/HoverCard.d.ts +4 -4
- package/lib/types/JsonTree.d.ts +4 -6
- package/lib/types/Modal.d.ts +22 -18
- package/lib/types/OnboardingSurface.d.ts +2 -2
- package/lib/types/ReadBlock.d.ts +13 -1
- package/lib/types/ReferenceIcon.d.ts +16 -0
- package/lib/types/RiskConfirmation.d.ts +2 -1
- package/lib/types/SearchBlock.d.ts +14 -0
- package/lib/types/TerminalBlock.d.ts +4 -10
- package/lib/types/Toast.d.ts +9 -1
- package/lib/types/WebBlock.d.ts +13 -0
- package/lib/types/icons/index.d.ts +8 -0
- package/lib/types/index.d.ts +14 -8
- package/lib/types/markdown/CodeBlock.d.ts +12 -3
- package/lib/types/markdown/JsonBlock.d.ts +1 -1
- package/lib/types/markdown/MarkdownText.d.ts +8 -7
- package/lib/types/markdown/highlight.d.ts +69 -2
- package/lib/types/markdown/incremental.d.ts +23 -12
- package/lib/types/markdown/render.d.ts +9 -4
- package/lib/types/markdown/useViewportHighlighting.d.ts +11 -0
- package/lib/types/relative-time.d.ts +23 -0
- package/lib/types/useAnchoredPosition.d.ts +8 -6
- package/lib/types/useDismissOnOutsidePointer.d.ts +3 -1
- package/lib/types/user-text.d.ts +19 -0
- package/lib/user-text.module.css +35 -0
- package/package.json +4 -5
- package/lib/ConnectionBanner.module.css +0 -13
- package/lib/types/ConnectionBanner.d.ts +0 -12
package/lib/index.js
CHANGED
|
@@ -12,20 +12,21 @@ import css$6 from "./HoverCard.module.css";
|
|
|
12
12
|
import css$7 from "./Modal.module.css";
|
|
13
13
|
import css$8 from "./OnboardingSurface.module.css";
|
|
14
14
|
import css$9 from "./RiskConfirmation.module.css";
|
|
15
|
-
import css$10 from "./
|
|
16
|
-
import css$11 from "./
|
|
17
|
-
import css$12 from "./
|
|
18
|
-
import css$13 from "./
|
|
15
|
+
import css$10 from "./ConnectionIndicator.module.css";
|
|
16
|
+
import css$11 from "./user-text.module.css";
|
|
17
|
+
import css$12 from "./Tooltip.module.css";
|
|
18
|
+
import css$13 from "./Toast.module.css";
|
|
19
|
+
import css$14 from "./JsonTree.module.css";
|
|
19
20
|
import Anser from "anser";
|
|
20
|
-
import css$
|
|
21
|
+
import css$15 from "./TerminalBlock.module.css";
|
|
21
22
|
import { createCssVariablesTheme, createHighlighterCoreSync } from "shiki/core";
|
|
22
23
|
import { createJavaScriptRegexEngine, defaultJavaScriptRegexConstructor } from "shiki/engine/javascript";
|
|
23
24
|
import langTs from "@shikijs/langs/typescript";
|
|
24
25
|
import langBash from "@shikijs/langs/shellscript";
|
|
25
26
|
import langJson from "@shikijs/langs/json";
|
|
26
|
-
import css$
|
|
27
|
-
import css$
|
|
28
|
-
import css$
|
|
27
|
+
import css$16 from "./ReadBlock.module.css";
|
|
28
|
+
import css$17 from "./DiffBlock.module.css";
|
|
29
|
+
import css$18 from "./SearchBlock.module.css";
|
|
29
30
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
30
31
|
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
31
32
|
import { mathFromMarkdown } from "mdast-util-math";
|
|
@@ -37,13 +38,13 @@ import { classifyCharacter } from "micromark-util-classify-character";
|
|
|
37
38
|
import { codes, constants, types } from "micromark-util-symbol";
|
|
38
39
|
import { factorySpace } from "micromark-factory-space";
|
|
39
40
|
import { normalizeUri } from "micromark-util-sanitize-uri";
|
|
40
|
-
import css$
|
|
41
|
+
import css$19 from "./markdown/CodeBlock.module.css";
|
|
41
42
|
import katex from "katex";
|
|
42
|
-
import css$
|
|
43
|
+
import css$20 from "./markdown/MarkdownText.module.css";
|
|
43
44
|
import "katex/dist/katex.min.css";
|
|
44
|
-
import css$
|
|
45
|
-
import css$
|
|
46
|
-
import css$
|
|
45
|
+
import css$21 from "./WebBlock.module.css";
|
|
46
|
+
import css$22 from "./markdown/JsonBlock.module.css";
|
|
47
|
+
import css$23 from "./markdown/MessageText.module.css";
|
|
47
48
|
//#region lib/types/StateDot.js
|
|
48
49
|
/** Outer 3x3 matrix cells (2px pixels on a 10px grid), clockwise from top-left. */
|
|
49
50
|
const MATRIX_CELLS = [
|
|
@@ -618,6 +619,33 @@ const IconBrowseOutline16 = ({ size = 16, className }) => jsxs("svg", {
|
|
|
618
619
|
})
|
|
619
620
|
]
|
|
620
621
|
});
|
|
622
|
+
/** ic_ds_context_injection_outline_16 (figma extract): browse document frame with an open top and an arrow dropping in. */
|
|
623
|
+
const IconContextInjectionOutline16 = ({ size = 16, className }) => jsxs("svg", {
|
|
624
|
+
width: size,
|
|
625
|
+
height: size,
|
|
626
|
+
className,
|
|
627
|
+
viewBox: "0 0 16 16",
|
|
628
|
+
fill: "none",
|
|
629
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
630
|
+
children: [
|
|
631
|
+
jsx("path", {
|
|
632
|
+
d: "M11.9512 1.13281C12.401 1.20666 12.8093 1.34164 13.1738 1.60645C13.4282 1.79137 13.6521 2.01609 13.8369 2.27051C14.1574 2.71187 14.2892 3.21614 14.3506 3.78223C14.4105 4.33532 14.4102 5.02658 14.4102 5.87305V10.0273C14.4102 10.8738 14.4105 11.5651 14.3506 12.1182C14.2892 12.6843 14.1574 13.1885 13.8369 13.6299C13.652 13.8843 13.4282 14.109 13.1738 14.2939C12.7324 14.6146 12.2273 14.7462 11.6611 14.8076C11.1081 14.8675 10.4166 14.8672 9.57031 14.8672H6.43164C5.58533 14.8672 4.89387 14.8675 4.34082 14.8076C3.77474 14.7463 3.27046 14.6144 2.8291 14.2939C2.57453 14.109 2.35003 13.8844 2.16504 13.6299C1.84444 13.1885 1.71272 12.6844 1.65137 12.1182C1.59147 11.5651 1.5918 10.8738 1.5918 10.0273V5.87305C1.5918 5.02655 1.59146 4.33533 1.65137 3.78223C1.71272 3.21606 1.84443 2.71191 2.16504 2.27051C2.35003 2.01596 2.57453 1.79141 2.8291 1.60645C3.19332 1.34202 3.60062 1.20669 4.0498 1.13281V2.56445C3.87191 2.61154 3.74906 2.66836 3.65137 2.73926C3.51583 2.83777 3.3964 2.95726 3.29785 3.09277C3.1794 3.25581 3.09143 3.4856 3.04297 3.93262C2.9931 4.39287 2.99219 4.99529 2.99219 5.87305V10.0273C2.99219 10.905 2.99312 11.5075 3.04297 11.9678C3.09142 12.4147 3.17943 12.6446 3.29785 12.8076C3.3964 12.9431 3.51583 13.0626 3.65137 13.1611C3.81441 13.2795 4.04437 13.3676 4.49121 13.416C4.95142 13.4658 5.55411 13.4668 6.43164 13.4668H9.57031C10.4479 13.4668 11.0505 13.4659 11.5107 13.416C11.9576 13.3675 12.1876 13.2796 12.3506 13.1611C12.4861 13.0626 12.6056 12.9431 12.7041 12.8076C12.8224 12.6446 12.9106 12.4146 12.959 11.9678C13.0088 11.5075 13.0098 10.905 13.0098 10.0273V5.87305C13.0098 4.99532 13.0088 4.39286 12.959 3.93262C12.9105 3.48579 12.8225 3.2558 12.7041 3.09277C12.6056 2.95727 12.4861 2.83778 12.3506 2.73926C12.2527 2.66816 12.1296 2.61064 11.9512 2.56348V1.13281Z",
|
|
633
|
+
fill: "currentColor"
|
|
634
|
+
}),
|
|
635
|
+
jsx("path", {
|
|
636
|
+
d: "M9.32227 11.4141H4.95508V10.2148H9.32227V11.4141Z",
|
|
637
|
+
fill: "currentColor"
|
|
638
|
+
}),
|
|
639
|
+
jsx("path", {
|
|
640
|
+
d: "M11.0439 8.90039H4.95508V7.70117H11.0439V8.90039Z",
|
|
641
|
+
fill: "currentColor"
|
|
642
|
+
}),
|
|
643
|
+
jsx("path", {
|
|
644
|
+
d: "M8.59961 3.75781L9.70996 2.64746L10.5586 3.49609L8.49512 5.55957C8.22173 5.83266 7.77816 5.83285 7.50488 5.55957L5.44141 3.49512L6.28906 2.64746L7.40039 3.75781V1.09668H8.59961V3.75781Z",
|
|
645
|
+
fill: "currentColor"
|
|
646
|
+
})
|
|
647
|
+
]
|
|
648
|
+
});
|
|
621
649
|
/** ic_ds_link_outline_14 */
|
|
622
650
|
const IconLinkOutline14 = ({ size = 14, className }) => jsxs("svg", {
|
|
623
651
|
width: size,
|
|
@@ -1127,6 +1155,55 @@ const IconDataOutline16 = ({ size = 16, className }) => jsxs("svg", {
|
|
|
1127
1155
|
fill: "currentColor"
|
|
1128
1156
|
})]
|
|
1129
1157
|
});
|
|
1158
|
+
/** IconDataOutline16 without its gear: a three-tier database cylinder. */
|
|
1159
|
+
const IconDatabaseOutline16 = ({ size = 16, className }) => jsxs("svg", {
|
|
1160
|
+
width: size,
|
|
1161
|
+
height: size,
|
|
1162
|
+
className,
|
|
1163
|
+
viewBox: "0 0 16 16",
|
|
1164
|
+
fill: "none",
|
|
1165
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1166
|
+
children: [
|
|
1167
|
+
jsx("ellipse", {
|
|
1168
|
+
cx: "8",
|
|
1169
|
+
cy: "3.6",
|
|
1170
|
+
rx: "5.75",
|
|
1171
|
+
ry: "2.4",
|
|
1172
|
+
stroke: "currentColor",
|
|
1173
|
+
strokeWidth: "1.25"
|
|
1174
|
+
}),
|
|
1175
|
+
jsx("path", {
|
|
1176
|
+
d: "M2.25 3.6V12.3A5.75 2.4 0 0 0 13.75 12.3V3.6",
|
|
1177
|
+
stroke: "currentColor",
|
|
1178
|
+
strokeWidth: "1.25"
|
|
1179
|
+
}),
|
|
1180
|
+
jsx("path", {
|
|
1181
|
+
d: "M2.25 7.95A5.75 2.4 0 0 0 13.75 7.95",
|
|
1182
|
+
stroke: "currentColor",
|
|
1183
|
+
strokeWidth: "1.25"
|
|
1184
|
+
})
|
|
1185
|
+
]
|
|
1186
|
+
});
|
|
1187
|
+
/** Thin-stroke clock: outlined dial with square-cut hour and minute hands. */
|
|
1188
|
+
const IconClockOutline16 = ({ size = 16, className }) => jsxs("svg", {
|
|
1189
|
+
width: size,
|
|
1190
|
+
height: size,
|
|
1191
|
+
className,
|
|
1192
|
+
viewBox: "0 0 16 16",
|
|
1193
|
+
fill: "none",
|
|
1194
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1195
|
+
children: [jsx("circle", {
|
|
1196
|
+
cx: "8",
|
|
1197
|
+
cy: "8",
|
|
1198
|
+
r: "6.375",
|
|
1199
|
+
stroke: "currentColor",
|
|
1200
|
+
strokeWidth: "1.25"
|
|
1201
|
+
}), jsx("path", {
|
|
1202
|
+
d: "M8 4.4V8.3L10.7 9.85",
|
|
1203
|
+
stroke: "currentColor",
|
|
1204
|
+
strokeWidth: "1.25"
|
|
1205
|
+
})]
|
|
1206
|
+
});
|
|
1130
1207
|
/** ic_send_outline_14 (figma extract): thin-stroke upward send arrow. */
|
|
1131
1208
|
const IconSendOutline14 = ({ size = 14, className }) => jsx("svg", {
|
|
1132
1209
|
width: size,
|
|
@@ -1318,6 +1395,56 @@ const IconQuestionOutline14 = ({ size = 14, className }) => jsxs("svg", {
|
|
|
1318
1395
|
})
|
|
1319
1396
|
]
|
|
1320
1397
|
});
|
|
1398
|
+
/** Alarm clock outline for active scheduled-task indicators. */
|
|
1399
|
+
const IconAlarmClockOutline16 = ({ size = 16, className }) => jsxs("svg", {
|
|
1400
|
+
"aria-hidden": "true",
|
|
1401
|
+
width: size,
|
|
1402
|
+
height: size,
|
|
1403
|
+
className,
|
|
1404
|
+
viewBox: "0 0 16 16",
|
|
1405
|
+
fill: "none",
|
|
1406
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1407
|
+
children: [
|
|
1408
|
+
jsx("path", {
|
|
1409
|
+
d: "M3.5 2.5 1.75 4",
|
|
1410
|
+
stroke: "currentColor",
|
|
1411
|
+
strokeWidth: "1.25",
|
|
1412
|
+
strokeLinecap: "round"
|
|
1413
|
+
}),
|
|
1414
|
+
jsx("path", {
|
|
1415
|
+
d: "M12.5 2.5 14.25 4",
|
|
1416
|
+
stroke: "currentColor",
|
|
1417
|
+
strokeWidth: "1.25",
|
|
1418
|
+
strokeLinecap: "round"
|
|
1419
|
+
}),
|
|
1420
|
+
jsx("circle", {
|
|
1421
|
+
cx: "8",
|
|
1422
|
+
cy: "8.5",
|
|
1423
|
+
r: "4.75",
|
|
1424
|
+
stroke: "currentColor",
|
|
1425
|
+
strokeWidth: "1.25"
|
|
1426
|
+
}),
|
|
1427
|
+
jsx("path", {
|
|
1428
|
+
d: "M8 5.75V8.5L10 9.75",
|
|
1429
|
+
stroke: "currentColor",
|
|
1430
|
+
strokeWidth: "1.25",
|
|
1431
|
+
strokeLinecap: "round",
|
|
1432
|
+
strokeLinejoin: "round"
|
|
1433
|
+
}),
|
|
1434
|
+
jsx("path", {
|
|
1435
|
+
d: "m4.75 12.25-1 1.5",
|
|
1436
|
+
stroke: "currentColor",
|
|
1437
|
+
strokeWidth: "1.25",
|
|
1438
|
+
strokeLinecap: "round"
|
|
1439
|
+
}),
|
|
1440
|
+
jsx("path", {
|
|
1441
|
+
d: "m11.25 12.25 1 1.5",
|
|
1442
|
+
stroke: "currentColor",
|
|
1443
|
+
strokeWidth: "1.25",
|
|
1444
|
+
strokeLinecap: "round"
|
|
1445
|
+
})
|
|
1446
|
+
]
|
|
1447
|
+
});
|
|
1321
1448
|
/** ic_ds_archive_outline_20 (figma extract): lidded box + label slot. The export's
|
|
1322
1449
|
* 0.11px stroke ring around the box contour is dropped — it restates the same
|
|
1323
1450
|
* contour in the same ink, which currentColor already carries. */
|
|
@@ -1755,19 +1882,19 @@ function useAnchoredMaxHeight(ref, cap, signal) {
|
|
|
1755
1882
|
*
|
|
1756
1883
|
* A portaled panel is positioned from its anchor's viewport rect, which stops
|
|
1757
1884
|
* being true the moment anything scrolls or the window resizes. This owns that
|
|
1758
|
-
* one concern: measure the anchor, offset the panel below it, clamp
|
|
1759
|
-
* inside the viewport, and re-run on scroll (capture phase, so
|
|
1760
|
-
* inside the page are caught too), on resize, and on the
|
|
1761
|
-
* changes while the element is open.
|
|
1885
|
+
* one concern: measure the anchor, offset the panel below or above it, clamp
|
|
1886
|
+
* the result inside the viewport, and re-run on scroll (capture phase, so
|
|
1887
|
+
* scrollers nested inside the page are caught too), on resize, and on the
|
|
1888
|
+
* panel's own size changes while the element is open.
|
|
1762
1889
|
* @module @deepseek-ai/dsh-client-ui-primitives/useAnchoredPosition
|
|
1763
1890
|
*/
|
|
1764
1891
|
/**
|
|
1765
1892
|
* Track an anchor and return the panel's fixed coordinates.
|
|
1766
|
-
* @param options - the open state, the two refs, and the gap/margin distances.
|
|
1893
|
+
* @param options - the open state, the two refs, the placement side, and the gap/margin distances.
|
|
1767
1894
|
* @returns `left`/`top` for the panel, or `null` before the first measurement.
|
|
1768
1895
|
*/
|
|
1769
1896
|
function useAnchoredPosition(options) {
|
|
1770
|
-
const { open, anchorRef, panelRef, gap, margin } = options;
|
|
1897
|
+
const { open, anchorRef, panelRef, side = "bottom", gap, margin } = options;
|
|
1771
1898
|
const [position, setPosition] = useState(null);
|
|
1772
1899
|
useLayoutEffect(() => {
|
|
1773
1900
|
if (!open) {
|
|
@@ -1784,7 +1911,7 @@ function useAnchoredPosition(options) {
|
|
|
1784
1911
|
const width = panel?.offsetWidth ?? 0;
|
|
1785
1912
|
const height = panel?.offsetHeight ?? 0;
|
|
1786
1913
|
let left = rect.left;
|
|
1787
|
-
let top = rect.bottom + gap;
|
|
1914
|
+
let top = side === "top" ? rect.top - gap - height : rect.bottom + gap;
|
|
1788
1915
|
if (width > 0) left = Math.min(Math.max(left, margin), window.innerWidth - width - margin);
|
|
1789
1916
|
if (height > 0) top = Math.min(Math.max(top, margin), window.innerHeight - height - margin);
|
|
1790
1917
|
/* v8 ignore stop */
|
|
@@ -1811,6 +1938,7 @@ function useAnchoredPosition(options) {
|
|
|
1811
1938
|
open,
|
|
1812
1939
|
anchorRef,
|
|
1813
1940
|
panelRef,
|
|
1941
|
+
side,
|
|
1814
1942
|
gap,
|
|
1815
1943
|
margin
|
|
1816
1944
|
]);
|
|
@@ -1827,12 +1955,14 @@ function useAnchoredPosition(options) {
|
|
|
1827
1955
|
* @param root - element containing both the trigger and the open surface.
|
|
1828
1956
|
* @param open - whether the surface is showing; false detaches the listener.
|
|
1829
1957
|
* @param setOpen - state setter invoked with false on an outside pointerdown.
|
|
1958
|
+
* @param portal - surface portaled outside the root (a `document.body` dialog)
|
|
1959
|
+
* that also counts as inside; omit when the root contains the whole popover.
|
|
1830
1960
|
*/
|
|
1831
|
-
function useDismissOnOutsidePointer(root, open, setOpen) {
|
|
1961
|
+
function useDismissOnOutsidePointer(root, open, setOpen, portal) {
|
|
1832
1962
|
useEffect(() => {
|
|
1833
1963
|
if (!open) return;
|
|
1834
1964
|
const closeOutside = (event) => {
|
|
1835
|
-
if (event.target instanceof Node &&
|
|
1965
|
+
if (event.target instanceof Node && root.current?.contains(event.target) !== true && portal?.current?.contains(event.target) !== true) setOpen(false);
|
|
1836
1966
|
};
|
|
1837
1967
|
document.addEventListener("pointerdown", closeOutside);
|
|
1838
1968
|
return () => {
|
|
@@ -1841,7 +1971,8 @@ function useDismissOnOutsidePointer(root, open, setOpen) {
|
|
|
1841
1971
|
}, [
|
|
1842
1972
|
root,
|
|
1843
1973
|
open,
|
|
1844
|
-
setOpen
|
|
1974
|
+
setOpen,
|
|
1975
|
+
portal
|
|
1845
1976
|
]);
|
|
1846
1977
|
}
|
|
1847
1978
|
//#endregion
|
|
@@ -1888,11 +2019,11 @@ async function writeClipboard(text) {
|
|
|
1888
2019
|
* @param props.disabled - suppress opening; turning true closes an open card.
|
|
1889
2020
|
* @param props.copyText - optional primary value copied by activation and
|
|
1890
2021
|
* included in the card's accessible name.
|
|
1891
|
-
* @param props.copyLabel - accessible activation-label prefix
|
|
1892
|
-
* @param props.copiedLabel - visible success label
|
|
2022
|
+
* @param props.copyLabel - localized accessible activation-label prefix.
|
|
2023
|
+
* @param props.copiedLabel - localized visible success label.
|
|
1893
2024
|
* @returns anchor wrapper with the conditional portaled card.
|
|
1894
2025
|
*/
|
|
1895
|
-
function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyText, copyLabel
|
|
2026
|
+
function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyText, copyLabel, copiedLabel }) {
|
|
1896
2027
|
const rootRef = useRef(null);
|
|
1897
2028
|
const cardRef = useRef(null);
|
|
1898
2029
|
const timerRef = useRef(null);
|
|
@@ -2058,23 +2189,20 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
|
|
|
2058
2189
|
//#endregion
|
|
2059
2190
|
//#region lib/types/Modal.js
|
|
2060
2191
|
/**
|
|
2061
|
-
* Render a centered modal over a blurred page mask.
|
|
2192
|
+
* Render a centered, body-portaled modal over a blurred page mask.
|
|
2062
2193
|
* @param props.open - whether the dialog is showing.
|
|
2063
2194
|
* @param props.onClose - Escape or mask click.
|
|
2064
2195
|
* @param props.title - dialog heading (aria-label in every mode).
|
|
2065
|
-
* @param props.closeLabel - accessible close-button label.
|
|
2196
|
+
* @param props.closeLabel - localized accessible close-button label.
|
|
2066
2197
|
* @param props.description - optional supporting sentence under the title.
|
|
2067
2198
|
* @param props.children - body (inputs, etc.).
|
|
2068
2199
|
* @param props.footer - action row (Cancel / Create).
|
|
2069
2200
|
* @param props.contentClassName - optional class for a scrollable content region.
|
|
2070
2201
|
* @param props.headless - render children directly in the card (no default
|
|
2071
|
-
* header/close/body chrome)
|
|
2072
|
-
* header structure; mask, card, Escape, and aria-label remain.
|
|
2073
|
-
* @param props.closeLabel - close-button aria label; the owner passes
|
|
2074
|
-
* localized copy (this package is cordis-free, so copy arrives via props).
|
|
2202
|
+
* header/close/body chrome); mask, card, Escape, and aria-label remain.
|
|
2075
2203
|
* @returns null when closed; otherwise the overlay tree.
|
|
2076
2204
|
*/
|
|
2077
|
-
function Modal({ open, onClose, title, closeLabel
|
|
2205
|
+
function Modal({ open, onClose, title, closeLabel, description, children, footer, className, contentClassName, headless = false }) {
|
|
2078
2206
|
useEffect(() => {
|
|
2079
2207
|
if (!open) return;
|
|
2080
2208
|
const onKeyDown = (e) => {
|
|
@@ -2133,8 +2261,8 @@ function Modal({ open, onClose, title, closeLabel = "Close", description, childr
|
|
|
2133
2261
|
//#endregion
|
|
2134
2262
|
//#region lib/types/OnboardingSurface.js
|
|
2135
2263
|
/**
|
|
2136
|
-
* Render
|
|
2137
|
-
*
|
|
2264
|
+
* Render a body-portaled onboarding stage and keep the application root inert
|
|
2265
|
+
* while mounted.
|
|
2138
2266
|
* @param props.children - the step's page content, centered on the stage.
|
|
2139
2267
|
* @returns the body-portaled overlay tree.
|
|
2140
2268
|
*/
|
|
@@ -2169,11 +2297,12 @@ function OnboardingSurface({ children }) {
|
|
|
2169
2297
|
* Render one in-page confirmation whose primary action is unavailable until
|
|
2170
2298
|
* the caller-controlled acknowledgement is checked.
|
|
2171
2299
|
*/
|
|
2172
|
-
function RiskConfirmation({ open, title, description, acknowledgeLabel, cancelLabel, confirmLabel, acknowledged, disabled = false, onAcknowledgedChange, onCancel, onConfirm }) {
|
|
2300
|
+
function RiskConfirmation({ open, title, description, acknowledgeLabel, cancelLabel, closeLabel, confirmLabel, acknowledged, disabled = false, onAcknowledgedChange, onCancel, onConfirm }) {
|
|
2173
2301
|
return jsxs(Modal, {
|
|
2174
2302
|
open,
|
|
2175
2303
|
onClose: onCancel,
|
|
2176
2304
|
title,
|
|
2305
|
+
closeLabel,
|
|
2177
2306
|
className: css$9.confirmation ?? "",
|
|
2178
2307
|
contentClassName: css$9.confirmationContent ?? "",
|
|
2179
2308
|
footer: jsxs(Fragment, { children: [jsx(Button, {
|
|
@@ -2209,23 +2338,112 @@ function RiskConfirmation({ open, title, description, acknowledgeLabel, cancelLa
|
|
|
2209
2338
|
});
|
|
2210
2339
|
}
|
|
2211
2340
|
//#endregion
|
|
2212
|
-
//#region lib/types/
|
|
2341
|
+
//#region lib/types/ConnectionIndicator.js
|
|
2213
2342
|
/**
|
|
2214
|
-
* Render
|
|
2215
|
-
* @param props.
|
|
2216
|
-
* @param props.
|
|
2217
|
-
*
|
|
2218
|
-
* @
|
|
2343
|
+
* Render an inline connection-recovery control.
|
|
2344
|
+
* @param props.state - visible outage, retry-attempt, or recovered state.
|
|
2345
|
+
* @param props.disconnectedLabel - localized outage text.
|
|
2346
|
+
* @param props.reconnectLabel - localized action text shown on hover or focus.
|
|
2347
|
+
* @param props.connectingLabel - localized retry text followed by the attempt dots.
|
|
2348
|
+
* @param props.recoveredLabel - localized recovery confirmation.
|
|
2349
|
+
* @param props.reconnectActionLabel - accessible label for the outage action.
|
|
2350
|
+
* @param props.restartActionLabel - accessible label for replacing an active attempt.
|
|
2351
|
+
* @param props.onReconnect - request an immediate reconnect attempt.
|
|
2352
|
+
* @returns the indicator, or null when no connection feedback is active.
|
|
2219
2353
|
*/
|
|
2220
|
-
function
|
|
2221
|
-
if (
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2354
|
+
function ConnectionIndicator({ state, disconnectedLabel, reconnectLabel, connectingLabel, recoveredLabel, reconnectActionLabel, restartActionLabel, onReconnect }) {
|
|
2355
|
+
if (state === void 0) return null;
|
|
2356
|
+
const sizeLabels = jsxs(Fragment, { children: [
|
|
2357
|
+
jsx("span", {
|
|
2358
|
+
className: css$10.sizeLabel,
|
|
2359
|
+
"aria-hidden": "true",
|
|
2360
|
+
children: disconnectedLabel
|
|
2361
|
+
}),
|
|
2362
|
+
jsx("span", {
|
|
2363
|
+
className: css$10.sizeLabel,
|
|
2364
|
+
"aria-hidden": "true",
|
|
2365
|
+
children: reconnectLabel
|
|
2366
|
+
}),
|
|
2367
|
+
jsxs("span", {
|
|
2368
|
+
className: css$10.sizeLabel,
|
|
2369
|
+
"aria-hidden": "true",
|
|
2370
|
+
children: [connectingLabel, jsx("span", {
|
|
2371
|
+
className: css$10.dots,
|
|
2372
|
+
children: "..."
|
|
2373
|
+
})]
|
|
2374
|
+
}),
|
|
2375
|
+
jsx("span", {
|
|
2376
|
+
className: css$10.sizeLabel,
|
|
2377
|
+
"aria-hidden": "true",
|
|
2378
|
+
children: recoveredLabel
|
|
2379
|
+
})
|
|
2380
|
+
] });
|
|
2381
|
+
if (state === "recovered") return jsxs("div", {
|
|
2382
|
+
className: `${css$10.indicator} ${css$10.success}`,
|
|
2383
|
+
role: "status",
|
|
2384
|
+
"aria-label": recoveredLabel,
|
|
2385
|
+
children: [jsx("span", {
|
|
2386
|
+
className: css$10.icon,
|
|
2387
|
+
"aria-hidden": "true",
|
|
2388
|
+
children: jsx(IconCheckOutline16, { size: 14 })
|
|
2389
|
+
}), jsxs("span", {
|
|
2390
|
+
className: css$10.label,
|
|
2391
|
+
children: [sizeLabels, jsx("span", {
|
|
2392
|
+
className: css$10.stateLabel,
|
|
2393
|
+
children: recoveredLabel
|
|
2394
|
+
})]
|
|
2395
|
+
})]
|
|
2396
|
+
});
|
|
2397
|
+
const connecting = state === "connecting";
|
|
2398
|
+
return jsxs("button", {
|
|
2399
|
+
type: "button",
|
|
2400
|
+
className: `${css$10.indicator} ${css$10.warning}`,
|
|
2401
|
+
"data-phase": state,
|
|
2402
|
+
"aria-label": connecting ? restartActionLabel : reconnectActionLabel,
|
|
2403
|
+
onClick: onReconnect,
|
|
2404
|
+
children: [jsx("span", {
|
|
2405
|
+
className: css$10.icon,
|
|
2406
|
+
"aria-hidden": "true",
|
|
2407
|
+
children: jsx(IconWarningOutline16, { size: 14 })
|
|
2408
|
+
}), jsxs("span", {
|
|
2409
|
+
className: css$10.label,
|
|
2410
|
+
children: [
|
|
2411
|
+
sizeLabels,
|
|
2412
|
+
jsx("span", {
|
|
2413
|
+
className: css$10.stateLabel,
|
|
2414
|
+
children: connecting ? jsxs(Fragment, { children: [connectingLabel, jsxs("span", {
|
|
2415
|
+
className: css$10.dots,
|
|
2416
|
+
"aria-hidden": "true",
|
|
2417
|
+
children: [
|
|
2418
|
+
jsx("span", { children: "." }),
|
|
2419
|
+
jsx("span", {
|
|
2420
|
+
className: css$10.secondDot,
|
|
2421
|
+
children: "."
|
|
2422
|
+
}),
|
|
2423
|
+
jsx("span", {
|
|
2424
|
+
className: css$10.thirdDot,
|
|
2425
|
+
children: "."
|
|
2426
|
+
})
|
|
2427
|
+
]
|
|
2428
|
+
})] }) : disconnectedLabel
|
|
2429
|
+
}),
|
|
2430
|
+
jsx("span", {
|
|
2431
|
+
className: css$10.hoverLabel,
|
|
2432
|
+
children: reconnectLabel
|
|
2433
|
+
})
|
|
2434
|
+
]
|
|
2435
|
+
})]
|
|
2225
2436
|
});
|
|
2226
2437
|
}
|
|
2227
2438
|
//#endregion
|
|
2228
2439
|
//#region lib/types/FishLogo.js
|
|
2440
|
+
/** Native viewBox of {@link FISH_LOGO_PATH} (width and height in user units). */
|
|
2441
|
+
const FISH_LOGO_VIEWBOX = {
|
|
2442
|
+
width: 23.16,
|
|
2443
|
+
height: 17.04
|
|
2444
|
+
};
|
|
2445
|
+
/** The fish silhouette path data, exported for consumers that compose their own svg (entrance effects, masks) around the same geometry. */
|
|
2446
|
+
const FISH_LOGO_PATH = "M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z";
|
|
2229
2447
|
/**
|
|
2230
2448
|
* Render the fish logo.
|
|
2231
2449
|
* @param props.size - width in px (default 24; height keeps the 23.16:17.04 ratio).
|
|
@@ -2235,13 +2453,13 @@ function ConnectionBanner({ reconnecting, label = "连接已断开,正在重
|
|
|
2235
2453
|
function FishLogo({ size = 24, className }) {
|
|
2236
2454
|
return jsx("svg", {
|
|
2237
2455
|
width: size,
|
|
2238
|
-
height: size *
|
|
2456
|
+
height: size * FISH_LOGO_VIEWBOX.height / FISH_LOGO_VIEWBOX.width,
|
|
2239
2457
|
className,
|
|
2240
|
-
viewBox:
|
|
2458
|
+
viewBox: `0 0 ${FISH_LOGO_VIEWBOX.width} ${FISH_LOGO_VIEWBOX.height}`,
|
|
2241
2459
|
fill: "none",
|
|
2242
2460
|
"aria-hidden": "true",
|
|
2243
2461
|
children: jsx("path", {
|
|
2244
|
-
d:
|
|
2462
|
+
d: FISH_LOGO_PATH,
|
|
2245
2463
|
fill: "currentColor"
|
|
2246
2464
|
})
|
|
2247
2465
|
});
|
|
@@ -2369,6 +2587,120 @@ function BrandWordmark({ size = 24, className, includeMark = true }) {
|
|
|
2369
2587
|
});
|
|
2370
2588
|
}
|
|
2371
2589
|
//#endregion
|
|
2590
|
+
//#region lib/types/ReferenceIcon.js
|
|
2591
|
+
/**
|
|
2592
|
+
* Render the icon that identifies one inline reference domain.
|
|
2593
|
+
* @param props - Reference kind, optional size, and optional CSS class.
|
|
2594
|
+
* @returns The corresponding current-color SVG glyph.
|
|
2595
|
+
*/
|
|
2596
|
+
function ReferenceIcon({ kind, size = 16, className }) {
|
|
2597
|
+
switch (kind) {
|
|
2598
|
+
case "session": return jsx("svg", {
|
|
2599
|
+
width: size,
|
|
2600
|
+
height: size,
|
|
2601
|
+
className,
|
|
2602
|
+
viewBox: "0 0 16 16",
|
|
2603
|
+
fill: "none",
|
|
2604
|
+
"aria-hidden": true,
|
|
2605
|
+
children: jsx("path", {
|
|
2606
|
+
d: "M8 0.597656C3.91296 0.597656 0.599716 3.91103 0.599609 7.99805C0.599609 9.13171 0.854567 10.2079 1.31152 11.1699L1.59277 11.7607L2.77441 11.1992L2.49414 10.6084L2.36035 10.3076C2.06865 9.59612 1.90723 8.81645 1.90723 7.99805C1.90733 4.63362 4.63554 1.90625 8 1.90625C11.3644 1.90635 14.0917 4.63368 14.0918 7.99805C14.0918 11.3625 11.3644 14.0907 8 14.0908C7.311 14.0908 6.80642 14.0414 6.35938 13.918C5.919 13.7963 5.50105 13.5929 5.00098 13.2441C4.26805 12.7329 3.21756 12.5526 2.35156 13.0996L2.33789 13.1084L2.32422 13.1182L1.74805 13.5234L2.18164 14.8184L3.05957 14.2002C3.37505 14.0068 3.84248 14.0319 4.25195 14.3174C4.84447 14.7307 5.39718 15.009 6.01172 15.1787C6.61963 15.3465 7.25579 15.3984 8 15.3984C12.087 15.3983 15.4004 12.0851 15.4004 7.99805C15.4003 3.9111 12.087 0.59776 8 0.597656ZM4.56836 8.50977V9.80371H8.12402V8.50977H4.56836ZM4.56836 7.30078H11.4619V6.00684H4.56836V7.30078Z",
|
|
2607
|
+
fill: "currentColor"
|
|
2608
|
+
})
|
|
2609
|
+
});
|
|
2610
|
+
case "file": return jsx(IconBrowseOutline16, {
|
|
2611
|
+
size,
|
|
2612
|
+
className
|
|
2613
|
+
});
|
|
2614
|
+
case "folder": return jsx(IconFolderClose16, {
|
|
2615
|
+
size,
|
|
2616
|
+
className
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
//#endregion
|
|
2621
|
+
//#region lib/types/user-text.js
|
|
2622
|
+
/** The wire form a session chip serializes to; label is the display text. */
|
|
2623
|
+
const SESSION_WIRE_RE = /@\[([^\]\n]+)\]\(dsh-session:[^)\s]+\)/gu;
|
|
2624
|
+
/**
|
|
2625
|
+
* Split one sent text into inline plain runs and reference chips.
|
|
2626
|
+
* @param text - the logged model text of the message or queue row.
|
|
2627
|
+
* @param sessionLabels - exact session mention labels associated by an adjacent recall.
|
|
2628
|
+
* @returns inline nodes covering the whole text.
|
|
2629
|
+
*/
|
|
2630
|
+
function projectUserText(text, sessionLabels) {
|
|
2631
|
+
const ranges = [];
|
|
2632
|
+
SESSION_WIRE_RE.lastIndex = 0;
|
|
2633
|
+
let wire;
|
|
2634
|
+
while ((wire = SESSION_WIRE_RE.exec(text)) !== null) ranges.push({
|
|
2635
|
+
start: wire.index,
|
|
2636
|
+
end: wire.index + wire[0].length,
|
|
2637
|
+
label: wire[0],
|
|
2638
|
+
kind: "session",
|
|
2639
|
+
display: wire[1]
|
|
2640
|
+
});
|
|
2641
|
+
for (const rawLabel of [...new Set(sessionLabels)].sort((a, b) => b.length - a.length)) {
|
|
2642
|
+
const label = `@${rawLabel}`;
|
|
2643
|
+
let start = text.indexOf(label);
|
|
2644
|
+
while (start >= 0) {
|
|
2645
|
+
ranges.push({
|
|
2646
|
+
start,
|
|
2647
|
+
end: start + label.length,
|
|
2648
|
+
label,
|
|
2649
|
+
kind: "session"
|
|
2650
|
+
});
|
|
2651
|
+
start = text.indexOf(label, start + label.length);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
const re = /(^|\s)(\/[\w-]+|@"[^"\n]+"|@[^\s]+)/gu;
|
|
2655
|
+
let m;
|
|
2656
|
+
while ((m = re.exec(text)) !== null) {
|
|
2657
|
+
const tokenStart = m.index + m[1].length;
|
|
2658
|
+
const rawLabel = m[2];
|
|
2659
|
+
const label = rawLabel.startsWith("@\"") ? rawLabel : rawLabel.replace(/[.,;:!?,。;:!?]+$/gu, "");
|
|
2660
|
+
if (label.length <= 1) continue;
|
|
2661
|
+
ranges.push({
|
|
2662
|
+
start: tokenStart,
|
|
2663
|
+
end: tokenStart + label.length,
|
|
2664
|
+
label,
|
|
2665
|
+
kind: "plain"
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
const rankOf = (range) => range.kind === "session" ? 0 : 1;
|
|
2669
|
+
ranges.sort((a, b) => a.start - b.start || rankOf(a) - rankOf(b) || b.end - a.end);
|
|
2670
|
+
const parts = [];
|
|
2671
|
+
let cursor = 0;
|
|
2672
|
+
const pushPlain = (from, to) => {
|
|
2673
|
+
parts.push(jsx("span", {
|
|
2674
|
+
className: css$11.plainRun,
|
|
2675
|
+
children: text.slice(from, to)
|
|
2676
|
+
}, `t${from}`));
|
|
2677
|
+
};
|
|
2678
|
+
for (const range of ranges) {
|
|
2679
|
+
if (range.start < cursor) continue;
|
|
2680
|
+
const { start: tokenStart, end, label, kind } = range;
|
|
2681
|
+
if (tokenStart > cursor) pushPlain(cursor, tokenStart);
|
|
2682
|
+
const referenceKind = kind === "session" ? "session" : label.startsWith("@") ? label.endsWith("/") ? "folder" : "file" : void 0;
|
|
2683
|
+
const displayLabel = range.display ?? (referenceKind === void 0 ? label : referenceKind === "session" ? label.slice(1) : label.slice(1).replace(/^"|"$/gu, "").split(/[\\/]/u).filter(Boolean).at(-1) ?? label.slice(1));
|
|
2684
|
+
parts.push(jsxs("span", {
|
|
2685
|
+
className: css$11.refChip,
|
|
2686
|
+
"data-ref-chip": referenceKind ?? "skill",
|
|
2687
|
+
title: label,
|
|
2688
|
+
children: [referenceKind !== void 0 && jsx(ReferenceIcon, {
|
|
2689
|
+
kind: referenceKind,
|
|
2690
|
+
size: 16,
|
|
2691
|
+
className: css$11.refIcon
|
|
2692
|
+
}), displayLabel]
|
|
2693
|
+
}, tokenStart));
|
|
2694
|
+
cursor = end;
|
|
2695
|
+
}
|
|
2696
|
+
if (parts.length === 0) return jsx("span", {
|
|
2697
|
+
className: css$11.plainRun,
|
|
2698
|
+
children: text
|
|
2699
|
+
});
|
|
2700
|
+
if (cursor < text.length) pushPlain(cursor, text.length);
|
|
2701
|
+
return jsx(Fragment, { children: parts });
|
|
2702
|
+
}
|
|
2703
|
+
//#endregion
|
|
2372
2704
|
//#region lib/types/Tooltip.js
|
|
2373
2705
|
/**
|
|
2374
2706
|
* Attach a hover/focus tooltip to an anchor element.
|
|
@@ -2500,7 +2832,7 @@ function Tooltip({ label, side = "right", delayMs = 0, disabled = false, maxWidt
|
|
|
2500
2832
|
}
|
|
2501
2833
|
}), pos !== null && jsx("span", {
|
|
2502
2834
|
ref: bubble,
|
|
2503
|
-
className: css$
|
|
2835
|
+
className: css$12.bubble,
|
|
2504
2836
|
"data-side": placement,
|
|
2505
2837
|
style: {
|
|
2506
2838
|
left: pos.x,
|
|
@@ -2513,6 +2845,10 @@ function Tooltip({ label, side = "right", delayMs = 0, disabled = false, maxWidt
|
|
|
2513
2845
|
}
|
|
2514
2846
|
//#endregion
|
|
2515
2847
|
//#region lib/types/Toast.js
|
|
2848
|
+
/** Full-opacity hold before the fade starts, when the owner names none. */
|
|
2849
|
+
const HOLD_MS = 3e3;
|
|
2850
|
+
/** Fade duration. Must agree with the stylesheet's toast-fade duration. */
|
|
2851
|
+
const FADE_MS = 1e3;
|
|
2516
2852
|
/**
|
|
2517
2853
|
* Transient top-center banner: slides in, holds at full opacity, fades out,
|
|
2518
2854
|
* then reports done so the owner can unmount it. Re-showing the same text
|
|
@@ -2521,21 +2857,28 @@ function Tooltip({ label, side = "right", delayMs = 0, disabled = false, maxWidt
|
|
|
2521
2857
|
* transformed or filtered ancestor cannot trap the fixed banner in that
|
|
2522
2858
|
* ancestor's box.
|
|
2523
2859
|
*
|
|
2860
|
+
* The hold is the owner's to set, because how long a banner has to stay
|
|
2861
|
+
* depends on how much there is to read: a one-line limit lands in the default
|
|
2862
|
+
* window, while a failure that names what broke does not. One value drives
|
|
2863
|
+
* both the unmount timer and the stylesheet's fade delay — the stylesheet
|
|
2864
|
+
* reads it as a custom property — so the two can no longer disagree and leave
|
|
2865
|
+
* the banner unmounting mid-fade.
|
|
2524
2866
|
* @param props.text - resolved banner copy; the owner passes localized text.
|
|
2525
2867
|
* @param props.icon - optional leading glyph (e.g. a warning icon).
|
|
2868
|
+
* @param props.holdMs - full-opacity hold before the fade; defaults to 3000.
|
|
2526
2869
|
* @param props.anchor - optional element whose horizontal center the banner
|
|
2527
2870
|
* follows (e.g. the composer card, so the banner centers over the chat column
|
|
2528
2871
|
* rather than the whole window); omitted, it centers on the viewport.
|
|
2529
2872
|
* @param props.onDone - called once the fade completes; unmount the toast here.
|
|
2530
2873
|
* @returns the floating banner.
|
|
2531
2874
|
*/
|
|
2532
|
-
function Toast({ text, icon, anchor, onDone }) {
|
|
2875
|
+
function Toast({ text, icon, anchor, holdMs = HOLD_MS, onDone }) {
|
|
2533
2876
|
useEffect(() => {
|
|
2534
|
-
const timer = setTimeout(onDone,
|
|
2877
|
+
const timer = setTimeout(onDone, holdMs + FADE_MS);
|
|
2535
2878
|
return () => {
|
|
2536
2879
|
clearTimeout(timer);
|
|
2537
2880
|
};
|
|
2538
|
-
}, [onDone]);
|
|
2881
|
+
}, [holdMs, onDone]);
|
|
2539
2882
|
const [left, setLeft] = useState(null);
|
|
2540
2883
|
useLayoutEffect(() => {
|
|
2541
2884
|
if (anchor == null) return;
|
|
@@ -2550,36 +2893,73 @@ function Toast({ text, icon, anchor, onDone }) {
|
|
|
2550
2893
|
};
|
|
2551
2894
|
}, [anchor]);
|
|
2552
2895
|
return createPortal(jsxs("div", {
|
|
2553
|
-
className: css$
|
|
2896
|
+
className: css$13.toast,
|
|
2554
2897
|
role: "alert",
|
|
2555
|
-
style:
|
|
2898
|
+
style: {
|
|
2899
|
+
...left === null ? {} : { left },
|
|
2900
|
+
"--dsh-toast-hold": `${String(holdMs)}ms`
|
|
2901
|
+
},
|
|
2556
2902
|
children: [icon !== void 0 && jsx("span", {
|
|
2557
|
-
className: css$
|
|
2903
|
+
className: css$13.icon,
|
|
2558
2904
|
"aria-hidden": true,
|
|
2559
2905
|
children: icon
|
|
2560
2906
|
}), jsx("span", {
|
|
2561
|
-
className: css$
|
|
2907
|
+
className: css$13.text,
|
|
2562
2908
|
children: text
|
|
2563
2909
|
})]
|
|
2564
2910
|
}), document.body);
|
|
2565
2911
|
}
|
|
2566
2912
|
//#endregion
|
|
2913
|
+
//#region lib/types/relative-time.js
|
|
2914
|
+
/**
|
|
2915
|
+
* Compact relative-time bucketing shared by every surface that dates a
|
|
2916
|
+
* session. Bucketing is here so two surfaces naming the same session agree;
|
|
2917
|
+
* the words stay in each plugin's own dictionary, per locale-owned copy.
|
|
2918
|
+
*
|
|
2919
|
+
* @module @deepseek-ai/dsh-client-ui-primitives/relative-time
|
|
2920
|
+
*/
|
|
2921
|
+
/**
|
|
2922
|
+
* Compact relative time, as a structured bucket the renderer localizes
|
|
2923
|
+
* ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
|
|
2924
|
+
* @param at - epoch ms of the dated moment.
|
|
2925
|
+
* @param now - current epoch ms (injected for pure rendering).
|
|
2926
|
+
* @returns the row's trailing time bucket and magnitude.
|
|
2927
|
+
*/
|
|
2928
|
+
function relativeTime(at, now) {
|
|
2929
|
+
const MIN = 6e4;
|
|
2930
|
+
const HOUR = 36e5;
|
|
2931
|
+
const DAY = 864e5;
|
|
2932
|
+
const diff = Math.max(0, now - at);
|
|
2933
|
+
if (diff < MIN) return {
|
|
2934
|
+
unit: "now",
|
|
2935
|
+
n: 0
|
|
2936
|
+
};
|
|
2937
|
+
if (diff < HOUR) return {
|
|
2938
|
+
unit: "minutes",
|
|
2939
|
+
n: Math.floor(diff / MIN)
|
|
2940
|
+
};
|
|
2941
|
+
if (diff < DAY) return {
|
|
2942
|
+
unit: "hours",
|
|
2943
|
+
n: Math.floor(diff / HOUR)
|
|
2944
|
+
};
|
|
2945
|
+
if (diff < 30 * DAY) return {
|
|
2946
|
+
unit: "days",
|
|
2947
|
+
n: Math.floor(diff / DAY)
|
|
2948
|
+
};
|
|
2949
|
+
if (diff < 365 * DAY) return {
|
|
2950
|
+
unit: "months",
|
|
2951
|
+
n: Math.floor(diff / (30 * DAY))
|
|
2952
|
+
};
|
|
2953
|
+
return {
|
|
2954
|
+
unit: "years",
|
|
2955
|
+
n: Math.floor(diff / (365 * DAY))
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
//#endregion
|
|
2567
2959
|
//#region lib/types/JsonTree.js
|
|
2568
2960
|
const OBJECT_PREVIEW_LIMIT = 4;
|
|
2569
2961
|
const ARRAY_PREVIEW_LIMIT = 5;
|
|
2570
2962
|
const PREVIEW_DEPTH_LIMIT = 2;
|
|
2571
|
-
const DEFAULT_LABELS$1 = {
|
|
2572
|
-
copyValue: "Copy value",
|
|
2573
|
-
copyJson: "Copy JSON",
|
|
2574
|
-
copyPath: "Copy property path",
|
|
2575
|
-
copyPrettyJson: "Copy pretty JSON",
|
|
2576
|
-
copyCompactJson: "Copy compact JSON",
|
|
2577
|
-
copied: "Copied",
|
|
2578
|
-
copyFailed: "Copy failed",
|
|
2579
|
-
collapseNode: "Collapse JSON node",
|
|
2580
|
-
expandNode: "Expand JSON node",
|
|
2581
|
-
copyButtonTitle: (action) => `${action}; right-click for copy options`
|
|
2582
|
-
};
|
|
2583
2963
|
function valueCopyMenuItems(labels) {
|
|
2584
2964
|
return [
|
|
2585
2965
|
{
|
|
@@ -2624,35 +3004,35 @@ function bracketOf(value) {
|
|
|
2624
3004
|
}
|
|
2625
3005
|
function previewPrimitive(value) {
|
|
2626
3006
|
if (value === null) return jsx("span", {
|
|
2627
|
-
className: css$
|
|
3007
|
+
className: css$14.keywordValue,
|
|
2628
3008
|
children: "null"
|
|
2629
3009
|
});
|
|
2630
3010
|
if (typeof value === "string") return jsx("span", {
|
|
2631
|
-
className: css$
|
|
3011
|
+
className: css$14.stringValue,
|
|
2632
3012
|
children: JSON.stringify(value)
|
|
2633
3013
|
});
|
|
2634
3014
|
if (typeof value === "number") return jsx("span", {
|
|
2635
|
-
className: css$
|
|
3015
|
+
className: css$14.numberValue,
|
|
2636
3016
|
children: String(value)
|
|
2637
3017
|
});
|
|
2638
3018
|
if (typeof value === "boolean") return jsx("span", {
|
|
2639
|
-
className: css$
|
|
3019
|
+
className: css$14.keywordValue,
|
|
2640
3020
|
children: String(value)
|
|
2641
3021
|
});
|
|
2642
3022
|
if (typeof value === "bigint") return jsx("span", {
|
|
2643
|
-
className: css$
|
|
3023
|
+
className: css$14.otherValue,
|
|
2644
3024
|
children: value.toString()
|
|
2645
3025
|
});
|
|
2646
3026
|
if (typeof value === "undefined") return jsx("span", {
|
|
2647
|
-
className: css$
|
|
3027
|
+
className: css$14.otherValue,
|
|
2648
3028
|
children: "undefined"
|
|
2649
3029
|
});
|
|
2650
3030
|
if (typeof value === "symbol") return jsx("span", {
|
|
2651
|
-
className: css$
|
|
3031
|
+
className: css$14.otherValue,
|
|
2652
3032
|
children: value.description ?? "Symbol"
|
|
2653
3033
|
});
|
|
2654
3034
|
if (typeof value === "function") return jsx("span", {
|
|
2655
|
-
className: css$
|
|
3035
|
+
className: css$14.otherValue,
|
|
2656
3036
|
children: value.name || "Function"
|
|
2657
3037
|
});
|
|
2658
3038
|
return null;
|
|
@@ -2666,71 +3046,71 @@ function previewValue(value, depth) {
|
|
|
2666
3046
|
const [open, close] = bracketOf(value);
|
|
2667
3047
|
return jsxs(Fragment, { children: [
|
|
2668
3048
|
jsx("span", {
|
|
2669
|
-
className: css$
|
|
3049
|
+
className: css$14.punctuation,
|
|
2670
3050
|
children: open
|
|
2671
3051
|
}),
|
|
2672
3052
|
depth >= PREVIEW_DEPTH_LIMIT ? jsx("span", {
|
|
2673
|
-
className: css$
|
|
3053
|
+
className: css$14.previewEllipsis,
|
|
2674
3054
|
children: "…"
|
|
2675
3055
|
}) : visible.map(([key, item], index) => jsxs("span", { children: [
|
|
2676
3056
|
index > 0 && jsx("span", {
|
|
2677
|
-
className: css$
|
|
3057
|
+
className: css$14.punctuation,
|
|
2678
3058
|
children: ", "
|
|
2679
3059
|
}),
|
|
2680
3060
|
!array && jsxs(Fragment, { children: [jsx("span", {
|
|
2681
|
-
className: css$
|
|
3061
|
+
className: css$14.previewProperty,
|
|
2682
3062
|
children: key
|
|
2683
3063
|
}), jsx("span", {
|
|
2684
|
-
className: css$
|
|
3064
|
+
className: css$14.punctuation,
|
|
2685
3065
|
children: ": "
|
|
2686
3066
|
})] }),
|
|
2687
3067
|
previewValue(item, depth + 1)
|
|
2688
3068
|
] }, key)),
|
|
2689
3069
|
depth < PREVIEW_DEPTH_LIMIT && entries.length > limit && jsx("span", {
|
|
2690
|
-
className: css$
|
|
3070
|
+
className: css$14.previewEllipsis,
|
|
2691
3071
|
children: ", …"
|
|
2692
3072
|
}),
|
|
2693
3073
|
jsx("span", {
|
|
2694
|
-
className: css$
|
|
3074
|
+
className: css$14.punctuation,
|
|
2695
3075
|
children: close
|
|
2696
3076
|
})
|
|
2697
3077
|
] });
|
|
2698
3078
|
}
|
|
2699
3079
|
function primitiveValue(value) {
|
|
2700
3080
|
if (value === null) return jsx("span", {
|
|
2701
|
-
className: css$
|
|
3081
|
+
className: css$14.keywordValue,
|
|
2702
3082
|
children: "null"
|
|
2703
3083
|
});
|
|
2704
3084
|
if (typeof value === "string") return jsx("span", {
|
|
2705
|
-
className: css$
|
|
3085
|
+
className: css$14.stringValue,
|
|
2706
3086
|
children: JSON.stringify(value)
|
|
2707
3087
|
});
|
|
2708
3088
|
if (typeof value === "boolean") return jsx("span", {
|
|
2709
|
-
className: css$
|
|
3089
|
+
className: css$14.keywordValue,
|
|
2710
3090
|
children: String(value)
|
|
2711
3091
|
});
|
|
2712
3092
|
if (typeof value === "number") return jsx("span", {
|
|
2713
|
-
className: css$
|
|
3093
|
+
className: css$14.numberValue,
|
|
2714
3094
|
children: String(value)
|
|
2715
3095
|
});
|
|
2716
3096
|
if (typeof value === "bigint") return jsx("span", {
|
|
2717
|
-
className: css$
|
|
3097
|
+
className: css$14.numberValue,
|
|
2718
3098
|
children: `${value.toString()}n`
|
|
2719
3099
|
});
|
|
2720
3100
|
if (value instanceof Date) return jsx("span", {
|
|
2721
|
-
className: css$
|
|
3101
|
+
className: css$14.otherValue,
|
|
2722
3102
|
children: value.toISOString()
|
|
2723
3103
|
});
|
|
2724
3104
|
if (typeof value === "function") return jsxs("span", {
|
|
2725
|
-
className: css$
|
|
3105
|
+
className: css$14.otherValue,
|
|
2726
3106
|
children: ["function() ", "{ }"]
|
|
2727
3107
|
});
|
|
2728
3108
|
if (typeof value === "undefined") return jsx("span", {
|
|
2729
|
-
className: css$
|
|
3109
|
+
className: css$14.otherValue,
|
|
2730
3110
|
children: "undefined"
|
|
2731
3111
|
});
|
|
2732
3112
|
return jsx("span", {
|
|
2733
|
-
className: css$
|
|
3113
|
+
className: css$14.otherValue,
|
|
2734
3114
|
children: value.toString()
|
|
2735
3115
|
});
|
|
2736
3116
|
}
|
|
@@ -2758,7 +3138,7 @@ function moveFocus(button, direction) {
|
|
|
2758
3138
|
function NodeField({ field, expandable, onToggle }) {
|
|
2759
3139
|
if (field === void 0) return null;
|
|
2760
3140
|
return jsxs("span", {
|
|
2761
|
-
className: clsx(css$
|
|
3141
|
+
className: clsx(css$14.label, expandable && css$14.clickableLabel),
|
|
2762
3142
|
onClick: expandable ? onToggle : void 0,
|
|
2763
3143
|
children: [fieldText(field), ":"]
|
|
2764
3144
|
});
|
|
@@ -2787,7 +3167,7 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
|
|
|
2787
3167
|
}
|
|
2788
3168
|
};
|
|
2789
3169
|
const row = (children, ariaExpanded) => jsx("div", {
|
|
2790
|
-
className: css$
|
|
3170
|
+
className: css$14.row,
|
|
2791
3171
|
role: "treeitem",
|
|
2792
3172
|
"aria-expanded": ariaExpanded,
|
|
2793
3173
|
onMouseOver: (event) => {
|
|
@@ -2807,7 +3187,7 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
|
|
|
2807
3187
|
}),
|
|
2808
3188
|
primitiveValue(value),
|
|
2809
3189
|
!lastElement && jsx("span", {
|
|
2810
|
-
className: css$
|
|
3190
|
+
className: css$14.punctuation,
|
|
2811
3191
|
children: ","
|
|
2812
3192
|
})
|
|
2813
3193
|
] }));
|
|
@@ -2819,22 +3199,22 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
|
|
|
2819
3199
|
onToggle: toggle
|
|
2820
3200
|
}),
|
|
2821
3201
|
jsx("span", {
|
|
2822
|
-
className: css$
|
|
3202
|
+
className: css$14.punctuation,
|
|
2823
3203
|
children: open
|
|
2824
3204
|
}),
|
|
2825
3205
|
jsx("span", {
|
|
2826
|
-
className: css$
|
|
3206
|
+
className: css$14.punctuation,
|
|
2827
3207
|
children: close
|
|
2828
3208
|
}),
|
|
2829
3209
|
!lastElement && jsx("span", {
|
|
2830
|
-
className: css$
|
|
3210
|
+
className: css$14.punctuation,
|
|
2831
3211
|
children: ","
|
|
2832
3212
|
})
|
|
2833
3213
|
] }));
|
|
2834
3214
|
return row(jsxs(Fragment, { children: [
|
|
2835
3215
|
jsx("span", {
|
|
2836
3216
|
ref: expanderRef,
|
|
2837
|
-
className: clsx(css$
|
|
3217
|
+
className: clsx(css$14.expander, expanded ? css$14.collapseIcon : css$14.expandIcon),
|
|
2838
3218
|
"data-json-expander": true,
|
|
2839
3219
|
role: "button",
|
|
2840
3220
|
"aria-label": expanded ? labels.collapseNode : labels.expandNode,
|
|
@@ -2853,17 +3233,17 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
|
|
|
2853
3233
|
onToggle: toggle
|
|
2854
3234
|
}),
|
|
2855
3235
|
jsx("span", {
|
|
2856
|
-
className: css$
|
|
3236
|
+
className: css$14.preview,
|
|
2857
3237
|
children: previewValue(value, 0)
|
|
2858
3238
|
}),
|
|
2859
3239
|
!lastElement && jsx("span", {
|
|
2860
|
-
className: css$
|
|
3240
|
+
className: css$14.punctuation,
|
|
2861
3241
|
children: ","
|
|
2862
3242
|
}),
|
|
2863
3243
|
expanded && jsx("ul", {
|
|
2864
3244
|
id: contentsId,
|
|
2865
3245
|
role: "group",
|
|
2866
|
-
className: css$
|
|
3246
|
+
className: css$14.children,
|
|
2867
3247
|
children: entries.map(([key, item], index) => jsx(JsonTreeNode, {
|
|
2868
3248
|
field: key,
|
|
2869
3249
|
value: item,
|
|
@@ -2900,11 +3280,7 @@ function copyText$2(target, mode) {
|
|
|
2900
3280
|
* @param props - Parsed data, accessible label, and display options.
|
|
2901
3281
|
* @returns A read-only JSON tree with an optionally fixed-open top level.
|
|
2902
3282
|
*/
|
|
2903
|
-
function JsonTree({ data, label
|
|
2904
|
-
const copyLabels = useMemo(() => labels === void 0 ? DEFAULT_LABELS$1 : {
|
|
2905
|
-
...DEFAULT_LABELS$1,
|
|
2906
|
-
...labels
|
|
2907
|
-
}, [labels]);
|
|
3283
|
+
function JsonTree({ data, label, className, copyable = true, expandTopLevel = true, labels }) {
|
|
2908
3284
|
const rootEntries = entriesOf(data);
|
|
2909
3285
|
const firstExpandableIndex = rootEntries.findIndex(([, value]) => isExpandableValue(value) && entriesOf(value).length > 0);
|
|
2910
3286
|
const firstExpandableEntry = rootEntries[firstExpandableIndex];
|
|
@@ -3025,20 +3401,20 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
|
|
|
3025
3401
|
const [rootOpen, rootClose] = bracketOf(data);
|
|
3026
3402
|
const copyTargetIsObject = typeof copyTarget?.value === "object" && copyTarget.value !== null;
|
|
3027
3403
|
const defaultCopyMode = copyTargetIsObject ? "prettyJson" : "value";
|
|
3028
|
-
const copyTitle = copyState === "copied" ?
|
|
3404
|
+
const copyTitle = copyState === "copied" ? labels.copied : copyState === "failed" ? labels.copyFailed : copyTargetIsObject ? labels.copyPrettyJson : labels.copyValue;
|
|
3029
3405
|
return jsxs("div", {
|
|
3030
3406
|
ref: rootRef,
|
|
3031
|
-
className: clsx(css$
|
|
3407
|
+
className: clsx(css$14.root, className),
|
|
3032
3408
|
onMouseOver: handleRootMouseOver,
|
|
3033
3409
|
onMouseLeave: () => {
|
|
3034
3410
|
if (!copyMenuOpenRef.current) clearCopyTarget();
|
|
3035
3411
|
},
|
|
3036
3412
|
onScroll: handleScroll,
|
|
3037
3413
|
children: [expandTopLevel ? jsxs("div", {
|
|
3038
|
-
className: css$
|
|
3414
|
+
className: css$14.expandedTopLevel,
|
|
3039
3415
|
children: [
|
|
3040
3416
|
jsx("div", {
|
|
3041
|
-
className: clsx(css$
|
|
3417
|
+
className: clsx(css$14.row, css$14.topLevelBracket),
|
|
3042
3418
|
"data-json-root-row": true,
|
|
3043
3419
|
onMouseOver: (event) => {
|
|
3044
3420
|
event.stopPropagation();
|
|
@@ -3048,19 +3424,19 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
|
|
|
3048
3424
|
});
|
|
3049
3425
|
},
|
|
3050
3426
|
children: jsx("span", {
|
|
3051
|
-
className: css$
|
|
3427
|
+
className: css$14.punctuation,
|
|
3052
3428
|
children: rootOpen
|
|
3053
3429
|
})
|
|
3054
3430
|
}),
|
|
3055
3431
|
jsx("div", {
|
|
3056
3432
|
"aria-label": label,
|
|
3057
|
-
className: clsx(css$
|
|
3433
|
+
className: clsx(css$14.container, css$14.expandedTopLevelContainer),
|
|
3058
3434
|
role: "tree",
|
|
3059
3435
|
children: rootEntries.map(([key, value], index) => jsx(JsonTreeNode, {
|
|
3060
3436
|
field: key,
|
|
3061
3437
|
value,
|
|
3062
3438
|
path: [Array.isArray(data) ? index : key],
|
|
3063
|
-
labels
|
|
3439
|
+
labels,
|
|
3064
3440
|
lastElement: index === rootEntries.length - 1,
|
|
3065
3441
|
initialExpanded: false,
|
|
3066
3442
|
tabStopId,
|
|
@@ -3069,21 +3445,21 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
|
|
|
3069
3445
|
}, key))
|
|
3070
3446
|
}),
|
|
3071
3447
|
jsx("div", {
|
|
3072
|
-
className: clsx(css$
|
|
3448
|
+
className: clsx(css$14.row, css$14.topLevelBracket),
|
|
3073
3449
|
children: jsx("span", {
|
|
3074
|
-
className: css$
|
|
3450
|
+
className: css$14.punctuation,
|
|
3075
3451
|
children: rootClose
|
|
3076
3452
|
})
|
|
3077
3453
|
})
|
|
3078
3454
|
]
|
|
3079
3455
|
}) : jsx("div", {
|
|
3080
3456
|
"aria-label": label,
|
|
3081
|
-
className: css$
|
|
3457
|
+
className: css$14.container,
|
|
3082
3458
|
role: "tree",
|
|
3083
3459
|
children: jsx(JsonTreeNode, {
|
|
3084
3460
|
value: data,
|
|
3085
3461
|
path: [],
|
|
3086
|
-
labels
|
|
3462
|
+
labels,
|
|
3087
3463
|
lastElement: true,
|
|
3088
3464
|
initialExpanded: true,
|
|
3089
3465
|
tabStopId,
|
|
@@ -3091,7 +3467,7 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
|
|
|
3091
3467
|
onRowHover: handleRowHover
|
|
3092
3468
|
})
|
|
3093
3469
|
}), copyTarget !== void 0 && jsx("span", {
|
|
3094
|
-
className: css$
|
|
3470
|
+
className: css$14.copyAnchor,
|
|
3095
3471
|
style: {
|
|
3096
3472
|
left: copyTarget.left,
|
|
3097
3473
|
top: copyTarget.top
|
|
@@ -3105,11 +3481,11 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
|
|
|
3105
3481
|
anchor: jsx("button", {
|
|
3106
3482
|
ref: copyButtonRef,
|
|
3107
3483
|
type: "button",
|
|
3108
|
-
className: css$
|
|
3484
|
+
className: css$14.copyButton,
|
|
3109
3485
|
"data-json-copy-button": true,
|
|
3110
3486
|
"data-state": copyState,
|
|
3111
3487
|
"aria-label": copyTitle,
|
|
3112
|
-
title:
|
|
3488
|
+
title: labels.copyButtonTitle(copyTitle),
|
|
3113
3489
|
onClick: () => void copy(defaultCopyMode),
|
|
3114
3490
|
onContextMenu: (event) => {
|
|
3115
3491
|
event.preventDefault();
|
|
@@ -3119,7 +3495,7 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
|
|
|
3119
3495
|
},
|
|
3120
3496
|
children: copyState === "copied" ? jsx(IconCheckOutline16, { size: 12 }) : jsx(IconCopyOutline16, { size: 12 })
|
|
3121
3497
|
}),
|
|
3122
|
-
items: copyTargetIsObject ? objectCopyMenuItems(
|
|
3498
|
+
items: copyTargetIsObject ? objectCopyMenuItems(labels) : valueCopyMenuItems(labels),
|
|
3123
3499
|
onSelect: (id) => {
|
|
3124
3500
|
copy(id);
|
|
3125
3501
|
copyMenuOpenRef.current = false;
|
|
@@ -3572,26 +3948,8 @@ function useCopyFeedback(text) {
|
|
|
3572
3948
|
}
|
|
3573
3949
|
//#endregion
|
|
3574
3950
|
//#region lib/types/TerminalBlock.js
|
|
3575
|
-
/**
|
|
3576
|
-
* Output lines shown before the height cap collapses the middle. Matches the
|
|
3577
|
-
* TUI transcript's default tool-output budget so both front ends cut a long
|
|
3578
|
-
* command's output at the same place.
|
|
3579
|
-
*/
|
|
3951
|
+
/** Output lines shown before the height cap collapses the middle. */
|
|
3580
3952
|
const DEFAULT_TERMINAL_MAX_LINES = 16;
|
|
3581
|
-
const DEFAULT_LABELS = {
|
|
3582
|
-
signal: (signal) => `信号 ${signal}`,
|
|
3583
|
-
exitCode: (exitCode) => `退出码 ${exitCode}`,
|
|
3584
|
-
running: "运行中",
|
|
3585
|
-
failed: "失败",
|
|
3586
|
-
done: "已完成",
|
|
3587
|
-
copy: "复制",
|
|
3588
|
-
copied: "复制成功",
|
|
3589
|
-
noOutput: "无输出",
|
|
3590
|
-
collapseAria: "收起输出",
|
|
3591
|
-
collapse: "收起",
|
|
3592
|
-
expandAria: (hidden) => `展开其余 ${hidden} 行输出`,
|
|
3593
|
-
expand: (hidden) => `… 其余 ${hidden} 行`
|
|
3594
|
-
};
|
|
3595
3953
|
/**
|
|
3596
3954
|
* Prompt label for a working directory: `~` for the home directory itself,
|
|
3597
3955
|
* otherwise the path's last segment (both separators accepted, trailing
|
|
@@ -3656,7 +4014,7 @@ function runState(running, exitCode, signal, labels) {
|
|
|
3656
4014
|
* @param line - the line's styled runs.
|
|
3657
4015
|
* @returns the line's children.
|
|
3658
4016
|
*/
|
|
3659
|
-
function renderLine(line) {
|
|
4017
|
+
function renderLine$1(line) {
|
|
3660
4018
|
return line.map((span, index) => span.style === void 0 ? span.text : jsx("span", {
|
|
3661
4019
|
style: span.style,
|
|
3662
4020
|
children: span.text
|
|
@@ -3668,10 +4026,7 @@ function renderLine(line) {
|
|
|
3668
4026
|
* @returns the terminal block element.
|
|
3669
4027
|
*/
|
|
3670
4028
|
function TerminalBlock({ command, cwd, home, output, exitCode, signal, running = false, maxLines = 16, className, labels }) {
|
|
3671
|
-
const copy =
|
|
3672
|
-
...DEFAULT_LABELS,
|
|
3673
|
-
...labels
|
|
3674
|
-
}, [labels]);
|
|
4029
|
+
const copy = labels;
|
|
3675
4030
|
const text = output ?? "";
|
|
3676
4031
|
const lines = useMemo(() => {
|
|
3677
4032
|
const parsed = parseAnsiLines(text);
|
|
@@ -3691,73 +4046,90 @@ function TerminalBlock({ command, cwd, home, output, exitCode, signal, running =
|
|
|
3691
4046
|
const empty = lines.every((line) => line.every((span) => span.text.trim() === ""));
|
|
3692
4047
|
const { hidden, capped, headLines, tailLines } = headTailCap(lines.length, maxLines, expanded);
|
|
3693
4048
|
return jsxs("div", {
|
|
3694
|
-
className: clsx(css$
|
|
4049
|
+
className: clsx(css$15.block, className),
|
|
3695
4050
|
"data-terminal": "",
|
|
3696
4051
|
"data-running": running ? "" : void 0,
|
|
3697
4052
|
children: [jsxs("div", {
|
|
3698
|
-
className: css$
|
|
4053
|
+
className: css$15.header,
|
|
3699
4054
|
children: [
|
|
3700
4055
|
jsxs("div", {
|
|
3701
|
-
className: css$
|
|
4056
|
+
className: css$15.prompt,
|
|
3702
4057
|
children: [jsx("span", {
|
|
3703
|
-
className: css$
|
|
4058
|
+
className: css$15.runStateLabel,
|
|
3704
4059
|
children: state.label
|
|
3705
4060
|
}), commandLines.map((line, index) => jsxs("div", {
|
|
3706
|
-
className: css$
|
|
4061
|
+
className: css$15.promptLine,
|
|
3707
4062
|
children: [
|
|
3708
4063
|
index === 0 && jsx(StateDot, {
|
|
3709
4064
|
state: state.state,
|
|
3710
|
-
className: css$
|
|
4065
|
+
className: css$15.runState
|
|
3711
4066
|
}),
|
|
3712
4067
|
jsx("span", {
|
|
3713
|
-
className: css$
|
|
4068
|
+
className: css$15.cwd,
|
|
3714
4069
|
children: index > 0 || cwd === void 0 ? "$" : promptLabel(cwd, home)
|
|
3715
4070
|
}),
|
|
3716
4071
|
jsx("span", {
|
|
3717
|
-
className: css$
|
|
4072
|
+
className: css$15.command,
|
|
3718
4073
|
children: line
|
|
3719
4074
|
})
|
|
3720
4075
|
]
|
|
3721
4076
|
}, index))]
|
|
3722
4077
|
}),
|
|
3723
4078
|
status !== void 0 && jsx(Pill, {
|
|
3724
|
-
className: css$
|
|
4079
|
+
className: css$15.status,
|
|
3725
4080
|
children: status
|
|
3726
4081
|
}),
|
|
3727
4082
|
!running && !empty && jsx("button", {
|
|
3728
4083
|
type: "button",
|
|
3729
|
-
className: css$
|
|
4084
|
+
className: css$15.copyButton,
|
|
3730
4085
|
onClick: onCopy,
|
|
3731
4086
|
children: copied ? copy.copied : copy.copy
|
|
3732
4087
|
})
|
|
3733
4088
|
]
|
|
3734
4089
|
}), !running && (empty ? jsx("div", {
|
|
3735
|
-
className: css$
|
|
4090
|
+
className: css$15.empty,
|
|
3736
4091
|
children: copy.noOutput
|
|
3737
4092
|
}) : jsxs("div", {
|
|
3738
|
-
className: css$
|
|
4093
|
+
className: css$15.output,
|
|
3739
4094
|
children: [
|
|
3740
4095
|
(capped ? lines.slice(0, headLines) : lines).map((line, index) => jsx("div", {
|
|
3741
|
-
className: css$
|
|
3742
|
-
children: renderLine(line)
|
|
4096
|
+
className: css$15.line,
|
|
4097
|
+
children: renderLine$1(line)
|
|
3743
4098
|
}, index)),
|
|
3744
4099
|
hidden > 0 && jsx("button", {
|
|
3745
4100
|
type: "button",
|
|
3746
|
-
className: css$
|
|
4101
|
+
className: css$15.expand,
|
|
3747
4102
|
"aria-expanded": expanded,
|
|
3748
4103
|
"aria-label": expanded ? copy.collapseAria : copy.expandAria(hidden),
|
|
3749
4104
|
onClick: onToggle,
|
|
3750
4105
|
children: expanded ? copy.collapse : copy.expand(hidden)
|
|
3751
4106
|
}),
|
|
3752
4107
|
capped && lines.slice(lines.length - tailLines).map((line, index) => jsx("div", {
|
|
3753
|
-
className: css$
|
|
3754
|
-
children: renderLine(line)
|
|
4108
|
+
className: css$15.line,
|
|
4109
|
+
children: renderLine$1(line)
|
|
3755
4110
|
}, index))
|
|
3756
4111
|
]
|
|
3757
4112
|
}))]
|
|
3758
4113
|
});
|
|
3759
4114
|
}
|
|
3760
4115
|
//#endregion
|
|
4116
|
+
//#region lib/types/FoldToggle.js
|
|
4117
|
+
/**
|
|
4118
|
+
* Render the shared head-tail fold control with caller-owned localized copy.
|
|
4119
|
+
* @param props - Fold state, localized labels, and toggle callback.
|
|
4120
|
+
* @returns The accessible expand or collapse button.
|
|
4121
|
+
*/
|
|
4122
|
+
function FoldToggle({ className, expanded, hidden, labels, onToggle }) {
|
|
4123
|
+
return jsx("button", {
|
|
4124
|
+
type: "button",
|
|
4125
|
+
className,
|
|
4126
|
+
"aria-expanded": expanded,
|
|
4127
|
+
"aria-label": expanded ? labels.collapseAria : labels.expandAria(hidden),
|
|
4128
|
+
onClick: onToggle,
|
|
4129
|
+
children: expanded ? labels.collapse : labels.expand(hidden)
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
//#endregion
|
|
3761
4133
|
//#region lib/types/markdown/highlight.js
|
|
3762
4134
|
/**
|
|
3763
4135
|
* The client's ONE syntax highlighter: a synchronous fine-grained shiki core
|
|
@@ -3881,6 +4253,14 @@ const LANG_ALIASES = new Map([
|
|
|
3881
4253
|
["xml", "xml"],
|
|
3882
4254
|
["lua", "lua"]
|
|
3883
4255
|
]);
|
|
4256
|
+
/**
|
|
4257
|
+
* Whether a language hint can use the shared syntax highlighter.
|
|
4258
|
+
* @param lang - Language hint from a code surface.
|
|
4259
|
+
* @returns Whether the hint resolves to a supported grammar.
|
|
4260
|
+
*/
|
|
4261
|
+
function supportsHighlighting(lang) {
|
|
4262
|
+
return lang !== void 0 && LANG_ALIASES.has(lang.toLowerCase());
|
|
4263
|
+
}
|
|
3884
4264
|
/** All token colors resolve through `--shiki-*` custom properties (theme package sheets). */
|
|
3885
4265
|
const cssVariablesTheme = createCssVariablesTheme({
|
|
3886
4266
|
name: "css-variables",
|
|
@@ -4008,6 +4388,153 @@ function highlightToHtml(code, lang) {
|
|
|
4008
4388
|
theme: "css-variables"
|
|
4009
4389
|
});
|
|
4010
4390
|
}
|
|
4391
|
+
/** vscode-textmate FontStyle bits shiki folds into `text-decoration` values. */
|
|
4392
|
+
const DECORATION_BITS = [[4, "underline"], [8, "line-through"]];
|
|
4393
|
+
/**
|
|
4394
|
+
* The inline style shiki's HTML arm assigns one token (`getTokenStyleObject`
|
|
4395
|
+
* mirrored onto React style keys): the css-variables color plus the
|
|
4396
|
+
* vscode-textmate font-style bits the theme lets through — italic (1), bold
|
|
4397
|
+
* (2), and the {@link DECORATION_BITS} decorations (the theme injects bold,
|
|
4398
|
+
* italic, and underline rules for markup scopes, so markdown fences carry
|
|
4399
|
+
* them). The theme has no per-scope backgrounds, so `background-color` never
|
|
4400
|
+
* occurs; the arm-parity tests fail loud if a shiki upgrade changes that.
|
|
4401
|
+
*/
|
|
4402
|
+
function spanStyle(token) {
|
|
4403
|
+
const style = { color: token.color };
|
|
4404
|
+
/* v8 ignore next -- fontStyle is optional in ThemedToken's type; tokenizeWithTheme always stamps it. */
|
|
4405
|
+
const bits = token.fontStyle ?? 0;
|
|
4406
|
+
if ((bits & 1) !== 0) style.fontStyle = "italic";
|
|
4407
|
+
if ((bits & 2) !== 0) style.fontWeight = "bold";
|
|
4408
|
+
const decorations = DECORATION_BITS.filter(([bit]) => (bits & bit) !== 0);
|
|
4409
|
+
if (decorations.length > 0) style.textDecoration = decorations.map(([, value]) => value).join(" ");
|
|
4410
|
+
return style;
|
|
4411
|
+
}
|
|
4412
|
+
/**
|
|
4413
|
+
* Narrow one tokenized line to the runs a `<span style>` renders, folding a
|
|
4414
|
+
* whitespace-only run into the token that follows it — shiki's default
|
|
4415
|
+
* `mergeWhitespaces` HTML behavior — with each run styled through
|
|
4416
|
+
* {@link spanStyle}, so the streaming spans and the settled `codeToHtml`
|
|
4417
|
+
* swap render one identical span tree. shiki exempts underlined/struck
|
|
4418
|
+
* whitespace from the fold; under the css-variables theme that case cannot
|
|
4419
|
+
* occur — its only underline rule styles inline-link scopes, whose spaced
|
|
4420
|
+
* text tokenizes as one run, and it injects no strikethrough rule — so the
|
|
4421
|
+
* unconditional fold here stays equivalent (the markdown arm-parity test
|
|
4422
|
+
* pins it). A line-trailing whitespace-only run has no follower and keeps
|
|
4423
|
+
* its own span, as in shiki.
|
|
4424
|
+
*/
|
|
4425
|
+
function lineSpans(line) {
|
|
4426
|
+
const spans = [];
|
|
4427
|
+
let pendingWhitespace = "";
|
|
4428
|
+
for (const [index, token] of line.entries()) {
|
|
4429
|
+
if (/^\s+$/.test(token.content) && index + 1 < line.length) {
|
|
4430
|
+
pendingWhitespace += token.content;
|
|
4431
|
+
continue;
|
|
4432
|
+
}
|
|
4433
|
+
spans.push({
|
|
4434
|
+
text: pendingWhitespace + token.content,
|
|
4435
|
+
style: spanStyle(token)
|
|
4436
|
+
});
|
|
4437
|
+
pendingWhitespace = "";
|
|
4438
|
+
}
|
|
4439
|
+
return spans;
|
|
4440
|
+
}
|
|
4441
|
+
/**
|
|
4442
|
+
* Incremental highlighter for one growing streaming fence. TextMate
|
|
4443
|
+
* tokenization is line-based and forward-only — a line's tokens depend only on
|
|
4444
|
+
* its own text and the grammar state entering it — so appended text never
|
|
4445
|
+
* changes a completed line's tokens. The session caches the spans of every
|
|
4446
|
+
* completed line together with the grammar state after them;
|
|
4447
|
+
* {@link updateFrame} reports only newly completed lines plus the still-growing
|
|
4448
|
+
* last line, while {@link update} materializes the complete compatibility
|
|
4449
|
+
* result. Per-call tokenization cost therefore excludes the completed prefix,
|
|
4450
|
+
* and the result equals a from-scratch tokenization of the same code.
|
|
4451
|
+
* Non-append input and a change of resolved grammar reset the cache and
|
|
4452
|
+
* re-tokenize fully, so any input stays correct.
|
|
4453
|
+
*/
|
|
4454
|
+
var StreamingHighlightSession = class {
|
|
4455
|
+
/** Grammar id the cache was built with; a different resolution resets it. */
|
|
4456
|
+
resolved;
|
|
4457
|
+
/** Newline-terminated source prefix covered by {@link spans}. */
|
|
4458
|
+
prefix = "";
|
|
4459
|
+
/** Cached spans, one entry per completed line of {@link prefix}. */
|
|
4460
|
+
spans = [];
|
|
4461
|
+
/** Grammar state after {@link prefix}; undefined = the grammar's initial state. */
|
|
4462
|
+
state;
|
|
4463
|
+
lastCode;
|
|
4464
|
+
lastLang;
|
|
4465
|
+
lastResult;
|
|
4466
|
+
generation = 0;
|
|
4467
|
+
lastFrame;
|
|
4468
|
+
reset(resolved) {
|
|
4469
|
+
this.resolved = resolved;
|
|
4470
|
+
this.prefix = "";
|
|
4471
|
+
this.spans = [];
|
|
4472
|
+
this.state = void 0;
|
|
4473
|
+
this.generation += 1;
|
|
4474
|
+
this.lastFrame = void 0;
|
|
4475
|
+
}
|
|
4476
|
+
/** Tokenize `text` with `resolved`, resuming from the cached grammar state when one exists. */
|
|
4477
|
+
tokenize(resolved, text) {
|
|
4478
|
+
return highlighter().codeToTokensBase(text, {
|
|
4479
|
+
lang: resolved,
|
|
4480
|
+
theme: "css-variables",
|
|
4481
|
+
...this.state === void 0 ? {} : { grammarState: this.state }
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
/**
|
|
4485
|
+
* Tokenize one update as a delta for a retained renderer.
|
|
4486
|
+
* @param code - the fence text accumulated so far.
|
|
4487
|
+
* @param lang - the language hint.
|
|
4488
|
+
* @returns Newly completed lines plus the current tail, or `undefined` for the plain arm.
|
|
4489
|
+
*/
|
|
4490
|
+
updateFrame(code, lang) {
|
|
4491
|
+
if (code === this.lastCode && lang === this.lastLang && this.lastFrame !== void 0) return this.lastFrame;
|
|
4492
|
+
this.lastCode = code;
|
|
4493
|
+
this.lastLang = lang;
|
|
4494
|
+
this.lastResult = void 0;
|
|
4495
|
+
const resolved = lang === void 0 ? void 0 : LANG_ALIASES.get(lang.toLowerCase());
|
|
4496
|
+
if (resolved === void 0 || !ensureGrammar(resolved)) {
|
|
4497
|
+
this.reset(void 0);
|
|
4498
|
+
return;
|
|
4499
|
+
}
|
|
4500
|
+
if (resolved !== this.resolved || !code.startsWith(this.prefix)) this.reset(resolved);
|
|
4501
|
+
const firstNewLine = this.spans.length;
|
|
4502
|
+
const rest = code.slice(this.prefix.length);
|
|
4503
|
+
const lastNewline = rest.lastIndexOf("\n");
|
|
4504
|
+
if (lastNewline >= 0) {
|
|
4505
|
+
const grownEnd = rest[lastNewline - 1] === "\r" ? lastNewline - 1 : lastNewline;
|
|
4506
|
+
const tokens = this.tokenize(resolved, rest.slice(0, grownEnd));
|
|
4507
|
+
for (const line of tokens) this.spans.push(lineSpans(line));
|
|
4508
|
+
this.state = highlighter().getLastGrammarState(tokens);
|
|
4509
|
+
this.prefix = code.slice(0, this.prefix.length + lastNewline + 1);
|
|
4510
|
+
}
|
|
4511
|
+
this.lastFrame = {
|
|
4512
|
+
generation: this.generation,
|
|
4513
|
+
appended: this.spans.slice(firstNewLine),
|
|
4514
|
+
tail: this.tokenize(resolved, rest.slice(lastNewline + 1)).map(lineSpans)
|
|
4515
|
+
};
|
|
4516
|
+
return this.lastFrame;
|
|
4517
|
+
}
|
|
4518
|
+
/**
|
|
4519
|
+
* Tokenize the fence's current text into per-line highlighted runs;
|
|
4520
|
+
* `undefined` means the caller renders its plain fallback. Idempotent per
|
|
4521
|
+
* (`code`, `lang`) input — repeated calls return the identical result array —
|
|
4522
|
+
* and a retained line keeps its span-array identity across growing calls, so
|
|
4523
|
+
* a React caller can reuse cached line elements. A lazy grammar not yet
|
|
4524
|
+
* loaded returns `undefined` and loads in the background exactly as
|
|
4525
|
+
* {@link highlightToHtml} does; the next call after it registers highlights.
|
|
4526
|
+
* @param code - the fence text accumulated so far (display-trimmed, no synthetic trailing newline).
|
|
4527
|
+
* @param lang - the language hint (a markdown fence info string).
|
|
4528
|
+
* @returns one entry per line of `code` (each an array of runs), or `undefined` for unknown or not-yet-loaded languages.
|
|
4529
|
+
*/
|
|
4530
|
+
update(code, lang) {
|
|
4531
|
+
if (code === this.lastCode && lang === this.lastLang && this.lastResult !== void 0) return this.lastResult;
|
|
4532
|
+
const frame = this.updateFrame(code, lang);
|
|
4533
|
+
if (frame === void 0) return void 0;
|
|
4534
|
+
this.lastResult = [...this.spans, ...frame.tail];
|
|
4535
|
+
return this.lastResult;
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4011
4538
|
/**
|
|
4012
4539
|
* Tokenize `code` into per-line highlighted runs when `lang` maps to a
|
|
4013
4540
|
* registered grammar; `undefined` means the caller renders its plain fallback.
|
|
@@ -4016,8 +4543,9 @@ function highlightToHtml(code, lang) {
|
|
|
4016
4543
|
* so this returns shiki's own 2D line/token structure narrowed to what a run
|
|
4017
4544
|
* renders. Each run's color is a `--shiki-*` custom property, keeping token
|
|
4018
4545
|
* colors on the theme package's sheets exactly as the HTML path does; the
|
|
4019
|
-
*
|
|
4020
|
-
*
|
|
4546
|
+
* markup font-style bits the theme lets through (bold/italic/underline in
|
|
4547
|
+
* markdown scopes) are dropped — the line-numbered file view renders
|
|
4548
|
+
* color-only runs. The trailing newline shiki appends as a final empty line
|
|
4021
4549
|
* is dropped so the run count matches the caller's own line array.
|
|
4022
4550
|
* @param code - the source text.
|
|
4023
4551
|
* @param lang - the language hint (a file-extension-derived language id).
|
|
@@ -4038,6 +4566,73 @@ function highlightLines(code, lang) {
|
|
|
4038
4566
|
})));
|
|
4039
4567
|
}
|
|
4040
4568
|
//#endregion
|
|
4569
|
+
//#region lib/types/markdown/useViewportHighlighting.js
|
|
4570
|
+
const noop = () => {};
|
|
4571
|
+
/** One document-wide observer; activated elements leave it permanently. */
|
|
4572
|
+
var HighlightViewport = class {
|
|
4573
|
+
observer;
|
|
4574
|
+
activators = /* @__PURE__ */ new Map();
|
|
4575
|
+
observe(element, activate) {
|
|
4576
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
4577
|
+
activate();
|
|
4578
|
+
return noop;
|
|
4579
|
+
}
|
|
4580
|
+
this.observer ??= new IntersectionObserver((entries) => {
|
|
4581
|
+
for (const entry of entries) {
|
|
4582
|
+
if (!entry.isIntersecting) continue;
|
|
4583
|
+
const current = this.activators.get(entry.target);
|
|
4584
|
+
/* v8 ignore next -- the observer reports only elements still registered with it. */
|
|
4585
|
+
if (current === void 0) continue;
|
|
4586
|
+
this.activators.delete(entry.target);
|
|
4587
|
+
this.observer?.unobserve(entry.target);
|
|
4588
|
+
current();
|
|
4589
|
+
}
|
|
4590
|
+
this.releaseEmptyObserver();
|
|
4591
|
+
});
|
|
4592
|
+
this.activators.set(element, activate);
|
|
4593
|
+
this.observer.observe(element);
|
|
4594
|
+
return () => {
|
|
4595
|
+
this.activators.delete(element);
|
|
4596
|
+
this.observer?.unobserve(element);
|
|
4597
|
+
this.releaseEmptyObserver();
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
releaseEmptyObserver() {
|
|
4601
|
+
if (this.activators.size > 0) return;
|
|
4602
|
+
this.observer?.disconnect();
|
|
4603
|
+
this.observer = void 0;
|
|
4604
|
+
}
|
|
4605
|
+
};
|
|
4606
|
+
const highlightViewport = new HighlightViewport();
|
|
4607
|
+
/**
|
|
4608
|
+
* Activate one supported code surface when it first intersects the viewport.
|
|
4609
|
+
* Activation lasts for the component lifetime; browsers without
|
|
4610
|
+
* IntersectionObserver activate immediately.
|
|
4611
|
+
* @param target - Code surface whose plain rendering reserves its geometry.
|
|
4612
|
+
* @param lang - Optional language hint.
|
|
4613
|
+
* @returns Whether this component may build highlighted output.
|
|
4614
|
+
*/
|
|
4615
|
+
function useViewportHighlighting(target, lang) {
|
|
4616
|
+
const supported = supportsHighlighting(lang);
|
|
4617
|
+
const [activated, setActivated] = useState(false);
|
|
4618
|
+
const activate = useCallback(() => {
|
|
4619
|
+
setActivated(true);
|
|
4620
|
+
}, []);
|
|
4621
|
+
useEffect(() => {
|
|
4622
|
+
if (activated || !supported) return;
|
|
4623
|
+
const element = target.current;
|
|
4624
|
+
/* v8 ignore next -- React attaches the host ref before running effects. */
|
|
4625
|
+
if (element === null) return;
|
|
4626
|
+
return highlightViewport.observe(element, activate);
|
|
4627
|
+
}, [
|
|
4628
|
+
activate,
|
|
4629
|
+
activated,
|
|
4630
|
+
supported,
|
|
4631
|
+
target
|
|
4632
|
+
]);
|
|
4633
|
+
return activated && supported;
|
|
4634
|
+
}
|
|
4635
|
+
//#endregion
|
|
4041
4636
|
//#region lib/types/ReadBlock.js
|
|
4042
4637
|
/**
|
|
4043
4638
|
* Content lines shown before the height cap collapses the middle. Matches
|
|
@@ -4045,13 +4640,6 @@ function highlightLines(code, lang) {
|
|
|
4045
4640
|
* same place in the same flow.
|
|
4046
4641
|
*/
|
|
4047
4642
|
const DEFAULT_READ_MAX_LINES = 16;
|
|
4048
|
-
/**
|
|
4049
|
-
* Render one line's highlighted runs. The css-variables theme colors every run,
|
|
4050
|
-
* so each run is a styled span; a line with no highlighting at all takes the
|
|
4051
|
-
* bare-text path in the caller instead (an unknown or absent language).
|
|
4052
|
-
* @param spans - the line's styled runs.
|
|
4053
|
-
* @returns the line's children.
|
|
4054
|
-
*/
|
|
4055
4643
|
function renderSpans(spans) {
|
|
4056
4644
|
return spans.map((span, index) => jsx("span", {
|
|
4057
4645
|
style: span.style,
|
|
@@ -4064,9 +4652,12 @@ function renderSpans(spans) {
|
|
|
4064
4652
|
* @param props - see {@link ReadBlockProps}.
|
|
4065
4653
|
* @returns the read block element.
|
|
4066
4654
|
*/
|
|
4067
|
-
function ReadBlock({ label, lines, totalLines, lang, maxLines = 16, className }) {
|
|
4655
|
+
function ReadBlock({ label, labels, lines, totalLines, lang, maxLines = 16, className }) {
|
|
4656
|
+
const rootRef = useRef(null);
|
|
4657
|
+
const highlighting = useViewportHighlighting(rootRef, lang);
|
|
4068
4658
|
const raw = useMemo(() => lines.map((line) => line.text).join("\n"), [lines]);
|
|
4069
|
-
const highlighted = useMemo(() => highlightLines(raw, lang), [
|
|
4659
|
+
const highlighted = useMemo(() => highlighting ? highlightLines(raw, lang) : void 0, [
|
|
4660
|
+
highlighting,
|
|
4070
4661
|
raw,
|
|
4071
4662
|
lang,
|
|
4072
4663
|
useSyncExternalStore(subscribeGrammarLoaded, grammarLoadCount, grammarLoadCount)
|
|
@@ -4091,61 +4682,56 @@ function ReadBlock({ label, lines, totalLines, lang, maxLines = 16, className })
|
|
|
4091
4682
|
const headLines = Math.ceil(maxLines / 2);
|
|
4092
4683
|
const tailLines = maxLines - headLines;
|
|
4093
4684
|
const windowed = lines.length < totalLines;
|
|
4094
|
-
/**
|
|
4095
|
-
* Render a slice of the line array as gutter-numbered rows.
|
|
4096
|
-
* @param slice - the lines to draw, each with its aligned run array.
|
|
4097
|
-
* @returns the row elements.
|
|
4098
|
-
*/
|
|
4099
4685
|
const rows = (slice) => slice.map(([line, spans]) => jsxs("div", {
|
|
4100
|
-
className: css$
|
|
4686
|
+
className: css$16.line,
|
|
4101
4687
|
children: [jsx("span", {
|
|
4102
|
-
className: css$
|
|
4688
|
+
className: css$16.gutter,
|
|
4103
4689
|
"aria-hidden": true,
|
|
4104
4690
|
children: line.number
|
|
4105
4691
|
}), jsx("span", {
|
|
4106
|
-
className: css$
|
|
4692
|
+
className: css$16.content,
|
|
4107
4693
|
children: spans === void 0 ? line.text : renderSpans(spans)
|
|
4108
4694
|
})]
|
|
4109
4695
|
}, line.number));
|
|
4110
4696
|
const paired = lines.map((line, index) => [line, highlighted?.[index]]);
|
|
4111
4697
|
return jsxs("div", {
|
|
4112
|
-
|
|
4698
|
+
ref: rootRef,
|
|
4699
|
+
className: clsx(css$16.block, className),
|
|
4113
4700
|
"data-read": "",
|
|
4114
4701
|
children: [jsxs("div", {
|
|
4115
|
-
className: css$
|
|
4702
|
+
className: css$16.banner,
|
|
4116
4703
|
children: [jsx("div", {
|
|
4117
|
-
className: css$
|
|
4704
|
+
className: css$16.label,
|
|
4118
4705
|
children: label ?? ""
|
|
4119
4706
|
}), jsxs("div", {
|
|
4120
|
-
className: css$
|
|
4707
|
+
className: css$16.action,
|
|
4121
4708
|
children: [
|
|
4122
4709
|
windowed && jsx("span", {
|
|
4123
|
-
className: css$
|
|
4124
|
-
children:
|
|
4710
|
+
className: css$16.count,
|
|
4711
|
+
children: labels.window(lines.length, totalLines)
|
|
4125
4712
|
}),
|
|
4126
4713
|
jsx("span", {
|
|
4127
|
-
className: css$
|
|
4714
|
+
className: css$16.lang,
|
|
4128
4715
|
children: lang ?? ""
|
|
4129
4716
|
}),
|
|
4130
4717
|
lines.length > 0 && jsx("button", {
|
|
4131
4718
|
type: "button",
|
|
4132
|
-
className: css$
|
|
4719
|
+
className: css$16.copyButton,
|
|
4133
4720
|
onClick: onCopy,
|
|
4134
|
-
children: copied ?
|
|
4721
|
+
children: copied ? labels.copied : labels.copy
|
|
4135
4722
|
})
|
|
4136
4723
|
]
|
|
4137
4724
|
})]
|
|
4138
4725
|
}), jsxs("div", {
|
|
4139
|
-
className: css$
|
|
4726
|
+
className: css$16.body,
|
|
4140
4727
|
children: [
|
|
4141
4728
|
rows(capped ? paired.slice(0, headLines) : paired),
|
|
4142
|
-
hidden > 0 && jsx(
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
children: expanded ? "收起" : `… 其余 ${hidden} 行`
|
|
4729
|
+
hidden > 0 && jsx(FoldToggle, {
|
|
4730
|
+
className: css$16.expand,
|
|
4731
|
+
expanded,
|
|
4732
|
+
hidden,
|
|
4733
|
+
labels,
|
|
4734
|
+
onToggle
|
|
4149
4735
|
}),
|
|
4150
4736
|
capped && rows(paired.slice(paired.length - tailLines))
|
|
4151
4737
|
]
|
|
@@ -4154,11 +4740,7 @@ function ReadBlock({ label, lines, totalLines, lang, maxLines = 16, className })
|
|
|
4154
4740
|
}
|
|
4155
4741
|
//#endregion
|
|
4156
4742
|
//#region lib/types/DiffBlock.js
|
|
4157
|
-
/**
|
|
4158
|
-
* Output lines shown before the height cap collapses the middle. Matches
|
|
4159
|
-
* {@link DEFAULT_TERMINAL_MAX_LINES} so a diff card and a terminal card cut a
|
|
4160
|
-
* long body at the same place.
|
|
4161
|
-
*/
|
|
4743
|
+
/** Output lines shown before the height cap collapses the middle. */
|
|
4162
4744
|
const DEFAULT_DIFF_MAX_LINES = 16;
|
|
4163
4745
|
/** Local exhaustiveness helper — this package does not depend on `dsh-llm`. */
|
|
4164
4746
|
/* v8 ignore next 3 -- closed-union backstop; only reached if a row kind is forged */
|
|
@@ -4167,26 +4749,44 @@ function assertNever(value) {
|
|
|
4167
4749
|
}
|
|
4168
4750
|
/** The dim class per row kind (path/gap chrome vs the diff's own +/- colors). */
|
|
4169
4751
|
const ROW_CLASS = {
|
|
4170
|
-
path: css$
|
|
4171
|
-
del: css$
|
|
4172
|
-
add: css$
|
|
4173
|
-
gap: css$
|
|
4752
|
+
path: css$17.path,
|
|
4753
|
+
del: css$17.del,
|
|
4754
|
+
add: css$17.add,
|
|
4755
|
+
gap: css$17.gap
|
|
4174
4756
|
};
|
|
4175
4757
|
/**
|
|
4758
|
+
* Total added/removed line counts across hunks — the same numbers the footer
|
|
4759
|
+
* prints, exported so a summary row can show them without rebuilding the body.
|
|
4760
|
+
* Every old-side line counts toward `removed` and every new-side line toward
|
|
4761
|
+
* `added`, under {@link contentLines}'s terminator rule.
|
|
4762
|
+
* @param diffs - the hunks to count.
|
|
4763
|
+
* @returns the +/- totals.
|
|
4764
|
+
*/
|
|
4765
|
+
function diffTotals(diffs) {
|
|
4766
|
+
let added = 0;
|
|
4767
|
+
let removed = 0;
|
|
4768
|
+
for (const diff of diffs) {
|
|
4769
|
+
if (diff.oldText !== null) removed += contentLines(diff.oldText).length;
|
|
4770
|
+
added += contentLines(diff.newText).length;
|
|
4771
|
+
}
|
|
4772
|
+
return {
|
|
4773
|
+
added,
|
|
4774
|
+
removed
|
|
4775
|
+
};
|
|
4776
|
+
}
|
|
4777
|
+
/**
|
|
4176
4778
|
* Flatten the hunks into the body's rows plus the footer counts. A path header
|
|
4177
4779
|
* opens each new file; a same-file second hunk (a scattered edit) opens with a
|
|
4178
|
-
* `⋯` gap instead of repeating the path.
|
|
4179
|
-
*
|
|
4180
|
-
*
|
|
4181
|
-
*
|
|
4780
|
+
* `⋯` gap instead of repeating the path. The +/- totals are
|
|
4781
|
+
* {@link diffTotals}'s. The file count is of DISTINCT paths, matching the TUI
|
|
4782
|
+
* diff card's footer, so two hunks in one file read as `1 file` on both front
|
|
4783
|
+
* ends.
|
|
4182
4784
|
* @param diffs - the hunks to render.
|
|
4183
4785
|
* @returns the body rows, the +/- totals, and the distinct-file count.
|
|
4184
4786
|
*/
|
|
4185
4787
|
function buildRows(diffs) {
|
|
4186
4788
|
const rows = [];
|
|
4187
4789
|
const paths = /* @__PURE__ */ new Set();
|
|
4188
|
-
let added = 0;
|
|
4189
|
-
let removed = 0;
|
|
4190
4790
|
let prevPath;
|
|
4191
4791
|
for (const diff of diffs) {
|
|
4192
4792
|
paths.add(diff.path);
|
|
@@ -4199,25 +4799,18 @@ function buildRows(diffs) {
|
|
|
4199
4799
|
text: "⋯"
|
|
4200
4800
|
});
|
|
4201
4801
|
prevPath = diff.path;
|
|
4202
|
-
if (diff.oldText !== null) for (const line of contentLines(diff.oldText)) {
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
rows.push({
|
|
4211
|
-
kind: "add",
|
|
4212
|
-
text: line
|
|
4213
|
-
});
|
|
4214
|
-
added++;
|
|
4215
|
-
}
|
|
4802
|
+
if (diff.oldText !== null) for (const line of contentLines(diff.oldText)) rows.push({
|
|
4803
|
+
kind: "del",
|
|
4804
|
+
text: line
|
|
4805
|
+
});
|
|
4806
|
+
for (const line of contentLines(diff.newText)) rows.push({
|
|
4807
|
+
kind: "add",
|
|
4808
|
+
text: line
|
|
4809
|
+
});
|
|
4216
4810
|
}
|
|
4217
4811
|
return {
|
|
4218
4812
|
rows,
|
|
4219
|
-
|
|
4220
|
-
removed,
|
|
4813
|
+
...diffTotals(diffs),
|
|
4221
4814
|
files: paths.size
|
|
4222
4815
|
};
|
|
4223
4816
|
}
|
|
@@ -4258,7 +4851,7 @@ function copyText$1(rows) {
|
|
|
4258
4851
|
* @param props - see {@link DiffBlockProps}.
|
|
4259
4852
|
* @returns the diff block element.
|
|
4260
4853
|
*/
|
|
4261
|
-
function DiffBlock({ diffs, maxLines = 16, className }) {
|
|
4854
|
+
function DiffBlock({ diffs, labels, maxLines = 16, className }) {
|
|
4262
4855
|
const { rows, added, removed, files } = useMemo(() => buildRows(diffs), [diffs]);
|
|
4263
4856
|
const [expanded, setExpanded] = useState(false);
|
|
4264
4857
|
const [copied, setCopied] = useState(false);
|
|
@@ -4283,47 +4876,44 @@ function DiffBlock({ diffs, maxLines = 16, className }) {
|
|
|
4283
4876
|
const head = capped ? rows.slice(0, headLines) : rows;
|
|
4284
4877
|
const tail = capped ? rows.slice(rows.length - tailLines) : [];
|
|
4285
4878
|
return jsxs("div", {
|
|
4286
|
-
className: clsx(css$
|
|
4879
|
+
className: clsx(css$17.block, className),
|
|
4287
4880
|
"data-diff": "",
|
|
4288
4881
|
children: [
|
|
4289
4882
|
jsx("button", {
|
|
4290
4883
|
type: "button",
|
|
4291
|
-
className: css$
|
|
4884
|
+
className: css$17.copyButton,
|
|
4292
4885
|
onClick: onCopy,
|
|
4293
|
-
children: copied ?
|
|
4886
|
+
children: copied ? labels.copied : labels.copy
|
|
4294
4887
|
}),
|
|
4295
4888
|
jsxs("div", {
|
|
4296
|
-
className: css$
|
|
4889
|
+
className: css$17.body,
|
|
4297
4890
|
children: [
|
|
4298
4891
|
head.map((row, index) => jsx("div", {
|
|
4299
|
-
className: clsx(css$
|
|
4892
|
+
className: clsx(css$17.line, ROW_CLASS[row.kind]),
|
|
4300
4893
|
children: row.text
|
|
4301
4894
|
}, index)),
|
|
4302
|
-
hidden > 0 && jsx(
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
children: expanded ? "收起" : `… 其余 ${hidden} 行`
|
|
4895
|
+
hidden > 0 && jsx(FoldToggle, {
|
|
4896
|
+
className: css$17.expand,
|
|
4897
|
+
expanded,
|
|
4898
|
+
hidden,
|
|
4899
|
+
labels,
|
|
4900
|
+
onToggle
|
|
4309
4901
|
}),
|
|
4310
4902
|
tail.map((row, index) => jsx("div", {
|
|
4311
|
-
className: clsx(css$
|
|
4903
|
+
className: clsx(css$17.line, ROW_CLASS[row.kind]),
|
|
4312
4904
|
children: row.text
|
|
4313
4905
|
}, index))
|
|
4314
4906
|
]
|
|
4315
4907
|
}),
|
|
4316
4908
|
jsxs("div", {
|
|
4317
|
-
className: css$
|
|
4909
|
+
className: css$17.footer,
|
|
4318
4910
|
children: [
|
|
4319
4911
|
"└ +",
|
|
4320
4912
|
added,
|
|
4321
4913
|
" -",
|
|
4322
4914
|
removed,
|
|
4323
4915
|
" · ",
|
|
4324
|
-
files
|
|
4325
|
-
" file",
|
|
4326
|
-
files === 1 ? "" : "s"
|
|
4916
|
+
labels.files(files)
|
|
4327
4917
|
]
|
|
4328
4918
|
})
|
|
4329
4919
|
]
|
|
@@ -4371,8 +4961,7 @@ function shownCount(props) {
|
|
|
4371
4961
|
* @returns the summary text.
|
|
4372
4962
|
*/
|
|
4373
4963
|
function summaryText(props, shown, truncated, total) {
|
|
4374
|
-
|
|
4375
|
-
return props.kind === "paths" ? `${count} 个路径` : `${count} 处匹配 · ${props.files.length} 个文件`;
|
|
4964
|
+
return props.kind === "paths" ? props.labels.pathsSummary(shown, total, truncated) : props.labels.matchesSummary(shown, total, props.files.length, truncated);
|
|
4376
4965
|
}
|
|
4377
4966
|
/**
|
|
4378
4967
|
* Flatten a card's shape into its render rows, dropping a collapsed file
|
|
@@ -4453,60 +5042,60 @@ function SearchBlock(props) {
|
|
|
4453
5042
|
const tail = tailHeader === void 0 ? naturalTail : naturalTail.slice(1);
|
|
4454
5043
|
const renderRow = (row) => {
|
|
4455
5044
|
if (row.type === "path") return jsx("div", {
|
|
4456
|
-
className: css$
|
|
5045
|
+
className: css$18.line,
|
|
4457
5046
|
children: row.path
|
|
4458
5047
|
});
|
|
4459
5048
|
if (row.type === "match") return jsxs("div", {
|
|
4460
|
-
className: css$
|
|
5049
|
+
className: css$18.line,
|
|
4461
5050
|
children: [jsxs("span", {
|
|
4462
|
-
className: css$
|
|
5051
|
+
className: css$18.lineNumber,
|
|
4463
5052
|
children: [row.lineNumber, ": "]
|
|
4464
5053
|
}), row.line]
|
|
4465
5054
|
});
|
|
4466
5055
|
return jsxs("button", {
|
|
4467
5056
|
type: "button",
|
|
4468
|
-
className: css$
|
|
5057
|
+
className: css$18.fileHeader,
|
|
4469
5058
|
"aria-expanded": !row.collapsed,
|
|
4470
5059
|
onClick: () => {
|
|
4471
5060
|
toggleFile(row.index);
|
|
4472
5061
|
},
|
|
4473
5062
|
children: [jsx("span", {
|
|
4474
|
-
className: css$
|
|
5063
|
+
className: css$18.filePath,
|
|
4475
5064
|
children: row.path
|
|
4476
5065
|
}), jsx("span", {
|
|
4477
|
-
className: css$
|
|
5066
|
+
className: css$18.fileCount,
|
|
4478
5067
|
children: row.count
|
|
4479
5068
|
})]
|
|
4480
5069
|
});
|
|
4481
5070
|
};
|
|
4482
5071
|
return jsxs("div", {
|
|
4483
|
-
className: clsx(css$
|
|
5072
|
+
className: clsx(css$18.block, className),
|
|
4484
5073
|
"data-search": props.kind,
|
|
4485
5074
|
children: [jsxs("div", {
|
|
4486
|
-
className: css$
|
|
5075
|
+
className: css$18.header,
|
|
4487
5076
|
children: [jsx("span", {
|
|
4488
|
-
className: css$
|
|
5077
|
+
className: css$18.summary,
|
|
4489
5078
|
children: summaryText(props, shown, truncated, total)
|
|
4490
5079
|
}), !empty && jsx("button", {
|
|
4491
5080
|
type: "button",
|
|
4492
|
-
className: css$
|
|
5081
|
+
className: css$18.copyButton,
|
|
4493
5082
|
onClick: onCopy,
|
|
4494
|
-
children: copied ?
|
|
5083
|
+
children: copied ? props.labels.copied : props.labels.copy
|
|
4495
5084
|
})]
|
|
4496
5085
|
}), empty ? jsx("div", {
|
|
4497
|
-
className: css$
|
|
4498
|
-
children:
|
|
5086
|
+
className: css$18.empty,
|
|
5087
|
+
children: props.labels.noResults
|
|
4499
5088
|
}) : jsxs("div", {
|
|
4500
|
-
className: css$
|
|
5089
|
+
className: css$18.body,
|
|
4501
5090
|
children: [
|
|
4502
5091
|
head.map((row) => jsx("div", { children: renderRow(row) }, rowKey(row))),
|
|
4503
5092
|
hidden > 0 && jsx("button", {
|
|
4504
5093
|
type: "button",
|
|
4505
|
-
className: css$
|
|
5094
|
+
className: css$18.expand,
|
|
4506
5095
|
"aria-expanded": expanded,
|
|
4507
|
-
"aria-label": expanded ?
|
|
5096
|
+
"aria-label": expanded ? props.labels.collapseAria : props.labels.expandAria(hidden),
|
|
4508
5097
|
onClick: onToggle,
|
|
4509
|
-
children: expanded ?
|
|
5098
|
+
children: expanded ? props.labels.collapse : props.labels.expand(hidden)
|
|
4510
5099
|
}),
|
|
4511
5100
|
tailHeader !== void 0 && jsx("div", { children: renderRow(tailHeader) }, `tailHeader:${rowKey(tailHeader)}`),
|
|
4512
5101
|
tail.map((row) => jsx("div", { children: renderRow(row) }, rowKey(row)))
|
|
@@ -4522,19 +5111,23 @@ function SearchBlock(props) {
|
|
|
4522
5111
|
* Re-parsing the whole accumulated document on every streaming chunk is
|
|
4523
5112
|
* quadratic in the final reply length. CommonMark block parsing is line-based
|
|
4524
5113
|
* and appended text can only reshape the parse frontier — the last top-level
|
|
4525
|
-
* block (a paragraph becoming a setext heading or a table, a list
|
|
4526
|
-
* after a blank line
|
|
4527
|
-
*
|
|
4528
|
-
*
|
|
4529
|
-
*
|
|
4530
|
-
*
|
|
5114
|
+
* block (a paragraph becoming a setext heading or a table, or a list
|
|
5115
|
+
* continuing after a blank line) — so earlier blocks are final. This parser
|
|
5116
|
+
* therefore freezes all but the trailing {@link UNSTABLE_TAIL_BLOCKS} blocks
|
|
5117
|
+
* and re-parses only the source tail behind them. A final unclosed top-level
|
|
5118
|
+
* fence cannot freeze as a block, so its completed content lines use a second
|
|
5119
|
+
* frontier: only the last completed line and current partial line return
|
|
5120
|
+
* through the caller's grammar. Each source region is therefore parsed a
|
|
5121
|
+
* bounded number of times over the stream instead of once per chunk.
|
|
4531
5122
|
*
|
|
4532
|
-
* The freeze boundary comes from the parser's own `position` offsets
|
|
4533
|
-
*
|
|
4534
|
-
*
|
|
4535
|
-
*
|
|
4536
|
-
*
|
|
4537
|
-
*
|
|
5123
|
+
* The block freeze boundary comes from the parser's own `position` offsets.
|
|
5124
|
+
* The cut sits at the *end offset* of the last frozen block (not the next
|
|
5125
|
+
* block's start): a following block's start offset excludes up to three spaces
|
|
5126
|
+
* of insignificant leading indentation, which is harmless to drop, but
|
|
5127
|
+
* cutting at the previous end also keeps the inter-block blank lines in the
|
|
5128
|
+
* tail so the sliced source stays verbatim. Fence scanning only recognizes a
|
|
5129
|
+
* parser-confirmed code node and closing delimiter; ambiguous input returns to
|
|
5130
|
+
* the normal tail parse.
|
|
4538
5131
|
*
|
|
4539
5132
|
* Known deviation, shared with any prefix-freeze scheme: micromark resolves
|
|
4540
5133
|
* reference-style links and footnotes document-wide at parse time, so a
|
|
@@ -4559,6 +5152,84 @@ function blockKey(node, base, index) {
|
|
|
4559
5152
|
const offset = node.position?.start.offset;
|
|
4560
5153
|
return offset === void 0 ? -(index + 1) : base + offset;
|
|
4561
5154
|
}
|
|
5155
|
+
/** Return the first line terminator at or after `start`, including a CRLF pair. */
|
|
5156
|
+
function lineTerminatorEnd(text, start) {
|
|
5157
|
+
for (let index = start; index < text.length; index += 1) {
|
|
5158
|
+
const char = text[index];
|
|
5159
|
+
if (char === "\n") return index + 1;
|
|
5160
|
+
if (char === "\r") return text[index + 1] === "\n" ? index + 2 : index + 1;
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
/**
|
|
5164
|
+
* Source prefix before the last completed line. Keeping that line beside the
|
|
5165
|
+
* current partial line lets the grammar retain its trailing-newline semantics.
|
|
5166
|
+
*/
|
|
5167
|
+
function committableLinePrefixLength(text) {
|
|
5168
|
+
let previousEnd = 0;
|
|
5169
|
+
let end = 0;
|
|
5170
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
5171
|
+
const char = text[index];
|
|
5172
|
+
if (char === "\n") {
|
|
5173
|
+
previousEnd = end;
|
|
5174
|
+
end = index + 1;
|
|
5175
|
+
continue;
|
|
5176
|
+
}
|
|
5177
|
+
if (char !== "\r" || index + 1 >= text.length) continue;
|
|
5178
|
+
if (text[index + 1] === "\n") index += 1;
|
|
5179
|
+
previousEnd = end;
|
|
5180
|
+
end = index + 1;
|
|
5181
|
+
}
|
|
5182
|
+
return previousEnd;
|
|
5183
|
+
}
|
|
5184
|
+
/** Exact source terminator ending a non-empty committable prefix. */
|
|
5185
|
+
function trailingLineTerminator(text) {
|
|
5186
|
+
return text.endsWith("\r\n") ? "\r\n" : text.endsWith("\r") ? "\r" : "\n";
|
|
5187
|
+
}
|
|
5188
|
+
/** Whether `text` contains a CommonMark closing fence on one of its logical lines. */
|
|
5189
|
+
function containsClosingFence(text, marker, markerLength) {
|
|
5190
|
+
let start = 0;
|
|
5191
|
+
while (start <= text.length) {
|
|
5192
|
+
let end = start;
|
|
5193
|
+
while (end < text.length && text[end] !== "\n" && text[end] !== "\r") end += 1;
|
|
5194
|
+
const line = text.slice(start, end);
|
|
5195
|
+
let indent = 0;
|
|
5196
|
+
while (indent < 3 && line[indent] === " ") indent += 1;
|
|
5197
|
+
let run = indent;
|
|
5198
|
+
while (line[run] === marker) run += 1;
|
|
5199
|
+
if (run - indent >= markerLength && /^[ \t]*$/.test(line.slice(run))) return true;
|
|
5200
|
+
if (end === text.length) return false;
|
|
5201
|
+
start = text[end] === "\r" && text[end + 1] === "\n" ? end + 2 : end + 1;
|
|
5202
|
+
}
|
|
5203
|
+
/* v8 ignore next -- each loop iteration returns at EOF or advances past a line terminator. */
|
|
5204
|
+
return false;
|
|
5205
|
+
}
|
|
5206
|
+
/** Advance an mdast point across one append while treating a split CRLF as one line ending. */
|
|
5207
|
+
function advancePoint(point, appended, precededByCarriageReturn) {
|
|
5208
|
+
let line = point.line;
|
|
5209
|
+
let column = point.column;
|
|
5210
|
+
let afterCarriageReturn = precededByCarriageReturn;
|
|
5211
|
+
for (const char of appended) {
|
|
5212
|
+
if (char === "\n") {
|
|
5213
|
+
if (!afterCarriageReturn) line += 1;
|
|
5214
|
+
column = 1;
|
|
5215
|
+
afterCarriageReturn = false;
|
|
5216
|
+
continue;
|
|
5217
|
+
}
|
|
5218
|
+
if (char === "\r") {
|
|
5219
|
+
line += 1;
|
|
5220
|
+
column = 1;
|
|
5221
|
+
afterCarriageReturn = true;
|
|
5222
|
+
continue;
|
|
5223
|
+
}
|
|
5224
|
+
column += 1;
|
|
5225
|
+
afterCarriageReturn = false;
|
|
5226
|
+
}
|
|
5227
|
+
return {
|
|
5228
|
+
line,
|
|
5229
|
+
column,
|
|
5230
|
+
offset: point.offset + appended.length
|
|
5231
|
+
};
|
|
5232
|
+
}
|
|
4562
5233
|
/**
|
|
4563
5234
|
* Append-only incremental parser over a caller-supplied grammar. One instance
|
|
4564
5235
|
* accumulates one streaming document; non-append input resets it.
|
|
@@ -4570,10 +5241,112 @@ var IncrementalMarkdownParser = class {
|
|
|
4570
5241
|
frozen = [];
|
|
4571
5242
|
generation = 0;
|
|
4572
5243
|
cached = null;
|
|
5244
|
+
openFence = null;
|
|
4573
5245
|
/** @param parse - Grammar shared with whatever renders the blocks, so boundaries agree. */
|
|
4574
5246
|
constructor(parse) {
|
|
4575
5247
|
this.parse = parse;
|
|
4576
5248
|
}
|
|
5249
|
+
/** Parse one unclosed-fence content slice through the caller's grammar. */
|
|
5250
|
+
fenceValue(state, text) {
|
|
5251
|
+
const root = this.parse(`${state.syntheticPrefix}${text}`);
|
|
5252
|
+
if (root.children.length !== 1) return void 0;
|
|
5253
|
+
const node = root.children[0];
|
|
5254
|
+
return node.type === "code" ? node.value : void 0;
|
|
5255
|
+
}
|
|
5256
|
+
/** Recognize the parsed tail's final unclosed fence and prepare its incremental content frontier. */
|
|
5257
|
+
openFenceState(text, base, tail, frozen) {
|
|
5258
|
+
const codeIndex = tail.length - 1;
|
|
5259
|
+
const block = tail[codeIndex];
|
|
5260
|
+
if (block?.node.type !== "code") return null;
|
|
5261
|
+
const node = block.node;
|
|
5262
|
+
const startOffset = node.position?.start.offset;
|
|
5263
|
+
const end = node.position?.end;
|
|
5264
|
+
if (startOffset === void 0 || end?.offset === void 0) return null;
|
|
5265
|
+
/* v8 ignore next -- the caller's parse slice ends at text.length, so its final node ends there. */
|
|
5266
|
+
if (base + end.offset !== text.length) return null;
|
|
5267
|
+
const source = text.slice(base);
|
|
5268
|
+
const previousLf = source.lastIndexOf("\n", startOffset - 1);
|
|
5269
|
+
const previousCr = source.lastIndexOf("\r", startOffset - 1);
|
|
5270
|
+
const lineStart = Math.max(previousLf, previousCr) + 1;
|
|
5271
|
+
const terminatorEnd = lineTerminatorEnd(source, startOffset);
|
|
5272
|
+
/* v8 ignore next -- a parser-confirmed fenced code node requires its opening line terminator. */
|
|
5273
|
+
if (terminatorEnd === void 0) return null;
|
|
5274
|
+
if (terminatorEnd === source.length && source.endsWith("\r")) return null;
|
|
5275
|
+
const openingLine = source.slice(lineStart, terminatorEnd).replace(/[\r\n]+$/, "");
|
|
5276
|
+
const opening = /^( {0,3})(`{3,}|~{3,})/.exec(openingLine);
|
|
5277
|
+
if (opening === null) return null;
|
|
5278
|
+
const indent = opening[1];
|
|
5279
|
+
const run = opening[2];
|
|
5280
|
+
/* v8 ignore next -- mdast positions a fenced code node at the matched delimiter after indentation. */
|
|
5281
|
+
if (lineStart + indent.length !== startOffset) return null;
|
|
5282
|
+
const marker = run[0];
|
|
5283
|
+
const contentStart = base + terminatorEnd;
|
|
5284
|
+
const content = text.slice(contentStart);
|
|
5285
|
+
if (containsClosingFence(content, marker, run.length)) return null;
|
|
5286
|
+
const syntheticPrefix = `${indent}${run}\n`;
|
|
5287
|
+
const stableLength = committableLinePrefixLength(content);
|
|
5288
|
+
const stableValue = stableLength === 0 ? "" : this.fenceValue({ syntheticPrefix }, content.slice(0, stableLength));
|
|
5289
|
+
if (stableValue === void 0) return null;
|
|
5290
|
+
const pendingStart = contentStart + stableLength;
|
|
5291
|
+
const stableSource = content.slice(0, stableLength);
|
|
5292
|
+
const valuePrefix = stableLength === 0 ? "" : `${stableValue}${trailingLineTerminator(stableSource)}`;
|
|
5293
|
+
const pendingValue = this.fenceValue({ syntheticPrefix }, text.slice(pendingStart));
|
|
5294
|
+
if (pendingValue === void 0 || `${valuePrefix}${pendingValue}` !== node.value) return null;
|
|
5295
|
+
return {
|
|
5296
|
+
marker,
|
|
5297
|
+
markerLength: run.length,
|
|
5298
|
+
syntheticPrefix,
|
|
5299
|
+
codeIndex,
|
|
5300
|
+
frozen,
|
|
5301
|
+
tail,
|
|
5302
|
+
pendingStart,
|
|
5303
|
+
valuePrefix,
|
|
5304
|
+
end: {
|
|
5305
|
+
line: end.line,
|
|
5306
|
+
column: end.column,
|
|
5307
|
+
offset: end.offset
|
|
5308
|
+
},
|
|
5309
|
+
endedWithCarriageReturn: text.endsWith("\r")
|
|
5310
|
+
};
|
|
5311
|
+
}
|
|
5312
|
+
/** Extend a recognized unclosed fence without parsing its completed content prefix again. */
|
|
5313
|
+
updateOpenFence(state, text, previousText) {
|
|
5314
|
+
const pending = text.slice(state.pendingStart);
|
|
5315
|
+
if (containsClosingFence(pending, state.marker, state.markerLength)) return void 0;
|
|
5316
|
+
const pendingValue = this.fenceValue(state, pending);
|
|
5317
|
+
if (pendingValue === void 0) return void 0;
|
|
5318
|
+
const stableLength = committableLinePrefixLength(pending);
|
|
5319
|
+
const stableValue = stableLength === 0 ? "" : this.fenceValue(state, pending.slice(0, stableLength));
|
|
5320
|
+
if (stableValue === void 0) return void 0;
|
|
5321
|
+
const previousNode = state.tail[state.codeIndex].node;
|
|
5322
|
+
const end = advancePoint(state.end, text.slice(previousText.length), state.endedWithCarriageReturn);
|
|
5323
|
+
const node = {
|
|
5324
|
+
...previousNode,
|
|
5325
|
+
value: `${state.valuePrefix}${pendingValue}`,
|
|
5326
|
+
position: {
|
|
5327
|
+
start: previousNode.position.start,
|
|
5328
|
+
end
|
|
5329
|
+
}
|
|
5330
|
+
};
|
|
5331
|
+
const tail = state.tail.map((entry, index) => index === state.codeIndex ? {
|
|
5332
|
+
...entry,
|
|
5333
|
+
node
|
|
5334
|
+
} : entry);
|
|
5335
|
+
const cached = {
|
|
5336
|
+
frozen: state.frozen,
|
|
5337
|
+
tail,
|
|
5338
|
+
generation: this.generation
|
|
5339
|
+
};
|
|
5340
|
+
this.openFence = {
|
|
5341
|
+
...state,
|
|
5342
|
+
tail,
|
|
5343
|
+
pendingStart: state.pendingStart + stableLength,
|
|
5344
|
+
valuePrefix: stableLength === 0 ? state.valuePrefix : `${state.valuePrefix}${stableValue}${trailingLineTerminator(pending.slice(0, stableLength))}`,
|
|
5345
|
+
end,
|
|
5346
|
+
endedWithCarriageReturn: text.endsWith("\r")
|
|
5347
|
+
};
|
|
5348
|
+
return cached;
|
|
5349
|
+
}
|
|
4577
5350
|
/**
|
|
4578
5351
|
* Fold the current accumulated text and return the frozen/tail split.
|
|
4579
5352
|
* Idempotent for identical input (the previous result is returned as-is),
|
|
@@ -4587,8 +5360,19 @@ var IncrementalMarkdownParser = class {
|
|
|
4587
5360
|
this.prevText = "";
|
|
4588
5361
|
this.tailStart = 0;
|
|
4589
5362
|
this.frozen = [];
|
|
5363
|
+
this.openFence = null;
|
|
4590
5364
|
this.generation += 1;
|
|
4591
5365
|
}
|
|
5366
|
+
const previousText = this.prevText;
|
|
5367
|
+
if (previousText !== "" && this.openFence !== null) {
|
|
5368
|
+
const incremental = this.updateOpenFence(this.openFence, text, previousText);
|
|
5369
|
+
if (incremental !== void 0) {
|
|
5370
|
+
this.prevText = text;
|
|
5371
|
+
this.cached = incremental;
|
|
5372
|
+
return incremental;
|
|
5373
|
+
}
|
|
5374
|
+
this.openFence = null;
|
|
5375
|
+
}
|
|
4592
5376
|
this.prevText = text;
|
|
4593
5377
|
const base = this.tailStart;
|
|
4594
5378
|
const blocks = this.parse(text.slice(base)).children;
|
|
@@ -4613,6 +5397,7 @@ var IncrementalMarkdownParser = class {
|
|
|
4613
5397
|
tail,
|
|
4614
5398
|
generation: this.generation
|
|
4615
5399
|
};
|
|
5400
|
+
this.openFence = this.openFenceState(text, base, tail, this.cached.frozen);
|
|
4616
5401
|
return this.cached;
|
|
4617
5402
|
}
|
|
4618
5403
|
};
|
|
@@ -4987,14 +5772,114 @@ function parseGfmWithMath(text) {
|
|
|
4987
5772
|
}
|
|
4988
5773
|
//#endregion
|
|
4989
5774
|
//#region lib/types/markdown/CodeBlock.js
|
|
4990
|
-
|
|
5775
|
+
/**
|
|
5776
|
+
* The `pre` attributes shiki's HTML arm emits for the css-variables theme,
|
|
5777
|
+
* mirrored so the streaming arm's tree is interchangeable with the settled
|
|
5778
|
+
* swap (`tests/streaming-code-block.client.spec.tsx` pins the two arms'
|
|
5779
|
+
* parity).
|
|
5780
|
+
*/
|
|
5781
|
+
const SHIKI_PRE_PROPS = {
|
|
5782
|
+
className: "shiki css-variables",
|
|
5783
|
+
style: {
|
|
5784
|
+
backgroundColor: "var(--shiki-background)",
|
|
5785
|
+
color: "var(--shiki-foreground)"
|
|
5786
|
+
},
|
|
5787
|
+
tabIndex: 0
|
|
5788
|
+
};
|
|
5789
|
+
/** Completed-line group size; React reconciles groups while the DOM remains line-for-line identical. */
|
|
5790
|
+
const STREAMING_LINE_GROUP_SIZE = 32;
|
|
5791
|
+
function renderLine(line, index) {
|
|
5792
|
+
return jsxs(Fragment$1, { children: [index > 0 && "\n", jsx("span", {
|
|
5793
|
+
className: "line",
|
|
5794
|
+
children: line.map((span, spanIndex) => jsx("span", {
|
|
5795
|
+
style: span.style,
|
|
5796
|
+
children: span.text
|
|
5797
|
+
}, spanIndex))
|
|
5798
|
+
})] }, index);
|
|
5799
|
+
}
|
|
5800
|
+
function CodeBlock({ code, lang, streaming, className, copyLabel, copiedLabel }) {
|
|
4991
5801
|
const trimmed = code.endsWith("\n") ? code.slice(0, -1) : code;
|
|
4992
|
-
const
|
|
5802
|
+
const rootRef = useRef(null);
|
|
5803
|
+
const highlighting = useViewportHighlighting(rootRef, lang);
|
|
5804
|
+
const loaded = useSyncExternalStore(subscribeGrammarLoaded, grammarLoadCount, grammarLoadCount);
|
|
5805
|
+
const sessionRef = useRef(null);
|
|
5806
|
+
const lineCacheRef = useRef(null);
|
|
5807
|
+
const settledRef = useRef(false);
|
|
5808
|
+
const streamedBody = useMemo(() => {
|
|
5809
|
+
if (!highlighting) {
|
|
5810
|
+
sessionRef.current = null;
|
|
5811
|
+
lineCacheRef.current = null;
|
|
5812
|
+
settledRef.current = false;
|
|
5813
|
+
return;
|
|
5814
|
+
}
|
|
5815
|
+
if (streaming !== true) {
|
|
5816
|
+
const previous = lineCacheRef.current;
|
|
5817
|
+
if (previous !== null && previous.code === trimmed && previous.lang === lang) {
|
|
5818
|
+
settledRef.current = true;
|
|
5819
|
+
return previous.body;
|
|
5820
|
+
}
|
|
5821
|
+
sessionRef.current = null;
|
|
5822
|
+
lineCacheRef.current = null;
|
|
5823
|
+
settledRef.current = true;
|
|
5824
|
+
return;
|
|
5825
|
+
}
|
|
5826
|
+
if (settledRef.current) {
|
|
5827
|
+
sessionRef.current = null;
|
|
5828
|
+
lineCacheRef.current = null;
|
|
5829
|
+
settledRef.current = false;
|
|
5830
|
+
}
|
|
5831
|
+
sessionRef.current ??= new StreamingHighlightSession();
|
|
5832
|
+
const frame = sessionRef.current.updateFrame(trimmed, lang);
|
|
5833
|
+
if (frame === void 0) {
|
|
5834
|
+
lineCacheRef.current = null;
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
const previous = lineCacheRef.current;
|
|
5838
|
+
if (previous?.frame === frame && previous.code === trimmed && previous.lang === lang) return previous.body;
|
|
5839
|
+
const sameGeneration = previous?.generation === frame.generation;
|
|
5840
|
+
const groups = sameGeneration ? [...previous.groups] : [];
|
|
5841
|
+
let pending = sameGeneration ? [...previous.pending] : [];
|
|
5842
|
+
let nextLine = sameGeneration ? previous.nextLine : 0;
|
|
5843
|
+
for (const line of frame.appended) {
|
|
5844
|
+
pending.push(renderLine(line, nextLine));
|
|
5845
|
+
nextLine += 1;
|
|
5846
|
+
if (pending.length !== STREAMING_LINE_GROUP_SIZE) continue;
|
|
5847
|
+
const start = nextLine - pending.length;
|
|
5848
|
+
groups.push(jsx(Fragment$1, { children: pending }, start));
|
|
5849
|
+
pending = [];
|
|
5850
|
+
}
|
|
5851
|
+
const tail = frame.tail.map((line, index) => renderLine(line, nextLine + index));
|
|
5852
|
+
const tailGroup = jsx(Fragment$1, { children: [...pending, ...tail] }, nextLine - pending.length);
|
|
5853
|
+
const body = jsx("pre", {
|
|
5854
|
+
...SHIKI_PRE_PROPS,
|
|
5855
|
+
children: jsxs("code", { children: [groups, tailGroup] })
|
|
5856
|
+
});
|
|
5857
|
+
lineCacheRef.current = {
|
|
5858
|
+
code: trimmed,
|
|
5859
|
+
lang,
|
|
5860
|
+
generation: frame.generation,
|
|
5861
|
+
frame,
|
|
5862
|
+
groups,
|
|
5863
|
+
pending,
|
|
5864
|
+
nextLine,
|
|
5865
|
+
body
|
|
5866
|
+
};
|
|
5867
|
+
return body;
|
|
5868
|
+
}, [
|
|
5869
|
+
streaming,
|
|
5870
|
+
highlighting,
|
|
4993
5871
|
trimmed,
|
|
4994
5872
|
lang,
|
|
4995
|
-
|
|
5873
|
+
loaded
|
|
5874
|
+
]);
|
|
5875
|
+
const html = useMemo(() => highlighting && streaming !== true && streamedBody === void 0 ? highlightToHtml(trimmed, lang) : void 0, [
|
|
5876
|
+
streaming,
|
|
5877
|
+
highlighting,
|
|
5878
|
+
streamedBody,
|
|
5879
|
+
trimmed,
|
|
5880
|
+
lang,
|
|
5881
|
+
loaded
|
|
4996
5882
|
]);
|
|
4997
|
-
const rootRef = useRef(null);
|
|
4998
5883
|
const [copied, setCopied] = useState(false);
|
|
4999
5884
|
const onCopy = useCallback(() => {
|
|
5000
5885
|
if (copied) return;
|
|
@@ -5006,25 +5891,25 @@ function CodeBlock({ code, lang, className, copyLabel = "复制", copiedLabel =
|
|
|
5006
5891
|
}, 1e3);
|
|
5007
5892
|
});
|
|
5008
5893
|
}, [copied, trimmed]);
|
|
5009
|
-
const body = html === void 0 ? jsx("pre", {
|
|
5010
|
-
className: css$
|
|
5894
|
+
const body = streamedBody !== void 0 ? streamedBody : html === void 0 ? jsx("pre", {
|
|
5895
|
+
className: css$19.plain,
|
|
5011
5896
|
children: jsx("code", { children: trimmed })
|
|
5012
5897
|
}) : jsx("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
5013
5898
|
return jsxs("div", {
|
|
5014
5899
|
ref: rootRef,
|
|
5015
|
-
className: clsx(css$
|
|
5900
|
+
className: clsx(css$19.block, "md-code-block", className),
|
|
5016
5901
|
children: [jsx("div", {
|
|
5017
|
-
className: css$
|
|
5902
|
+
className: css$19.bannerWrap,
|
|
5018
5903
|
children: jsxs("div", {
|
|
5019
|
-
className: css$
|
|
5904
|
+
className: css$19.banner,
|
|
5020
5905
|
children: [jsx("div", {
|
|
5021
|
-
className: css$
|
|
5906
|
+
className: css$19.infostring,
|
|
5022
5907
|
children: lang ?? ""
|
|
5023
5908
|
}), jsx("div", {
|
|
5024
|
-
className: css$
|
|
5909
|
+
className: css$19.action,
|
|
5025
5910
|
children: jsx("button", {
|
|
5026
5911
|
type: "button",
|
|
5027
|
-
className: css$
|
|
5912
|
+
className: css$19.copyButton,
|
|
5028
5913
|
onClick: onCopy,
|
|
5029
5914
|
children: copied ? copiedLabel : copyLabel
|
|
5030
5915
|
})
|
|
@@ -5244,7 +6129,7 @@ function renderNode(node, key, context) {
|
|
|
5244
6129
|
const mention = context.inLink === true ? void 0 : context.fileMentions?.resolve(value);
|
|
5245
6130
|
if (mention !== void 0) return jsx("code", { children: jsx("button", {
|
|
5246
6131
|
type: "button",
|
|
5247
|
-
className: css$
|
|
6132
|
+
className: css$20.fileMention,
|
|
5248
6133
|
title: mention.title,
|
|
5249
6134
|
"aria-label": mention.label,
|
|
5250
6135
|
onClick: mention.open,
|
|
@@ -5279,9 +6164,10 @@ function renderCode(node, key, context) {
|
|
|
5279
6164
|
if (!context.streaming && lang === "math") return jsx(Fragment$1, { children: renderTexToReact(`${node.value}\n`, true) }, key);
|
|
5280
6165
|
return jsx(CodeBlock, {
|
|
5281
6166
|
code: `${node.value}\n`,
|
|
5282
|
-
lang
|
|
5283
|
-
|
|
5284
|
-
|
|
6167
|
+
lang,
|
|
6168
|
+
streaming: context.streaming,
|
|
6169
|
+
copyLabel: context.labels.code.copyLabel,
|
|
6170
|
+
copiedLabel: context.labels.code.copiedLabel
|
|
5285
6171
|
}, key);
|
|
5286
6172
|
}
|
|
5287
6173
|
/** A list is loose when it or any of its items is spread; every item then keeps its paragraphs. */
|
|
@@ -5338,7 +6224,7 @@ function renderTable(node, key, context) {
|
|
|
5338
6224
|
const [headRow, ...bodyRows] = node.children;
|
|
5339
6225
|
const wide = (align === null ? headRow?.children.length ?? 0 : align.length) >= 4 && context.inBlockquote !== true;
|
|
5340
6226
|
return jsx("div", {
|
|
5341
|
-
className: clsx(css$
|
|
6227
|
+
className: clsx(css$20.tableScroll, wide ? "md-table-wide" : css$20.tableFill),
|
|
5342
6228
|
tabIndex: wide ? 0 : void 0,
|
|
5343
6229
|
children: jsxs("table", { children: [headRow !== void 0 && jsx("thead", { children: renderTableRow(headRow, "th", align, 0, context) }), bodyRows.length > 0 && jsx("tbody", { children: bodyRows.map((row, index) => renderTableRow(row, "td", align, index + 1, context)) })] })
|
|
5344
6230
|
}, key);
|
|
@@ -5389,11 +6275,11 @@ function inlineCodeHttpUrl(value) {
|
|
|
5389
6275
|
function renderImage(url, alt, key) {
|
|
5390
6276
|
const imageSrc = remoteImageUrl(sanitizeUrl(normalizeUri(url)));
|
|
5391
6277
|
if (imageSrc === void 0) return jsx("span", {
|
|
5392
|
-
className: css$
|
|
6278
|
+
className: css$20.imageAlt,
|
|
5393
6279
|
children: alt
|
|
5394
6280
|
}, key);
|
|
5395
6281
|
return jsx("img", {
|
|
5396
|
-
className: css$
|
|
6282
|
+
className: css$20.image,
|
|
5397
6283
|
src: imageSrc,
|
|
5398
6284
|
alt,
|
|
5399
6285
|
loading: "lazy",
|
|
@@ -5466,7 +6352,7 @@ function renderFootnoteSection(context) {
|
|
|
5466
6352
|
children: [jsx("h2", {
|
|
5467
6353
|
id: "footnote-label",
|
|
5468
6354
|
className: "sr-only",
|
|
5469
|
-
children:
|
|
6355
|
+
children: context.labels.footnotes
|
|
5470
6356
|
}), jsx("ol", { children: items })]
|
|
5471
6357
|
}, "footnotes");
|
|
5472
6358
|
}
|
|
@@ -5485,13 +6371,13 @@ function renderFootnoteSection(context) {
|
|
|
5485
6371
|
* full parse self-heals it.
|
|
5486
6372
|
*/
|
|
5487
6373
|
/** One settled full render: parse with math, resolve references, append the footnote section. */
|
|
5488
|
-
function renderSettled(text,
|
|
6374
|
+
function renderSettled(text, labels, fileMentions) {
|
|
5489
6375
|
const root = parseGfmWithMath(text);
|
|
5490
6376
|
const targets = createReferenceTargets();
|
|
5491
6377
|
collectReferenceTargets(root.children, targets);
|
|
5492
6378
|
const context = {
|
|
5493
6379
|
streaming: false,
|
|
5494
|
-
|
|
6380
|
+
labels,
|
|
5495
6381
|
fileMentions,
|
|
5496
6382
|
targets,
|
|
5497
6383
|
footnoteOrder: [],
|
|
@@ -5499,7 +6385,8 @@ function renderSettled(text, codeLabels, fileMentions) {
|
|
|
5499
6385
|
};
|
|
5500
6386
|
const blocks = wrapBlockChildren(renderBlocks(root.children.map((node, index) => ({
|
|
5501
6387
|
node,
|
|
5502
|
-
|
|
6388
|
+
/* v8 ignore next -- parseFull uses parseGfm, which stamps every top-level node. */
|
|
6389
|
+
key: node.position?.start.offset ?? -(index + 1)
|
|
5503
6390
|
})), context), false);
|
|
5504
6391
|
const section = renderFootnoteSection(context);
|
|
5505
6392
|
return section === null ? blocks : [
|
|
@@ -5515,7 +6402,7 @@ function renderSettled(text, codeLabels, fileMentions) {
|
|
|
5515
6402
|
* final, so the tail continues from a copy of it each frame).
|
|
5516
6403
|
*/
|
|
5517
6404
|
var StreamingRenderer = class {
|
|
5518
|
-
|
|
6405
|
+
labels;
|
|
5519
6406
|
parser = new IncrementalMarkdownParser(parseGfm);
|
|
5520
6407
|
generation = -1;
|
|
5521
6408
|
frozenCount = 0;
|
|
@@ -5525,9 +6412,9 @@ var StreamingRenderer = class {
|
|
|
5525
6412
|
frozenFootnoteCounts = /* @__PURE__ */ new Map();
|
|
5526
6413
|
lastText = null;
|
|
5527
6414
|
lastRendered = [];
|
|
5528
|
-
/** @param
|
|
5529
|
-
constructor(
|
|
5530
|
-
this.
|
|
6415
|
+
/** @param labels - Localized Markdown chrome baked into cached elements; the owner replaces the renderer when it changes. */
|
|
6416
|
+
constructor(labels) {
|
|
6417
|
+
this.labels = labels;
|
|
5531
6418
|
}
|
|
5532
6419
|
/**
|
|
5533
6420
|
* Render the current accumulated text. Idempotent per text value, so React
|
|
@@ -5556,7 +6443,7 @@ var StreamingRenderer = class {
|
|
|
5556
6443
|
if (newlyFrozen.length > 0) {
|
|
5557
6444
|
const frozenContext = {
|
|
5558
6445
|
streaming: true,
|
|
5559
|
-
|
|
6446
|
+
labels: this.labels,
|
|
5560
6447
|
fileMentions: void 0,
|
|
5561
6448
|
targets: frameTargets,
|
|
5562
6449
|
footnoteOrder: this.frozenFootnoteOrder,
|
|
@@ -5572,7 +6459,7 @@ var StreamingRenderer = class {
|
|
|
5572
6459
|
}
|
|
5573
6460
|
const tailContext = {
|
|
5574
6461
|
streaming: true,
|
|
5575
|
-
|
|
6462
|
+
labels: this.labels,
|
|
5576
6463
|
fileMentions: void 0,
|
|
5577
6464
|
targets: frameTargets,
|
|
5578
6465
|
footnoteOrder: [...this.frozenFootnoteOrder],
|
|
@@ -5593,9 +6480,10 @@ var StreamingRenderer = class {
|
|
|
5593
6480
|
/**
|
|
5594
6481
|
* Render untrusted assistant-authored Markdown as semantic React elements.
|
|
5595
6482
|
* @param props - Markdown source text preserved by the session projection;
|
|
5596
|
-
* `streaming`
|
|
5597
|
-
*
|
|
5598
|
-
*
|
|
6483
|
+
* `streaming` parses incrementally across chunks and highlights fences as
|
|
6484
|
+
* they grow (each fence re-tokenizes only appended text; TeX stays literal
|
|
6485
|
+
* until the finalize swap so incomplete formulae never flash errors);
|
|
6486
|
+
* `labels` forwards localized fence and footnote chrome — pass a
|
|
5599
6487
|
* reference-stable object (memoized per locale revision), because a new
|
|
5600
6488
|
* identity discards the streaming render cache mid-message. `fileMentions`
|
|
5601
6489
|
* links inline-code tokens its resolver recognizes as real files; this is
|
|
@@ -5606,27 +6494,27 @@ var StreamingRenderer = class {
|
|
|
5606
6494
|
* relative links, and unsafe protocols are disabled, while absolute HTTP(S)
|
|
5607
6495
|
* images render directly.
|
|
5608
6496
|
*/
|
|
5609
|
-
const MarkdownText = memo(function MarkdownText({ text, streaming = false,
|
|
6497
|
+
const MarkdownText = memo(function MarkdownText({ text, streaming = false, labels, fileMentions }) {
|
|
5610
6498
|
const streamRef = useRef(null);
|
|
5611
|
-
const streamLabelsRef = useRef(
|
|
6499
|
+
const streamLabelsRef = useRef(labels);
|
|
5612
6500
|
const children = useMemo(() => {
|
|
5613
6501
|
if (!streaming) {
|
|
5614
6502
|
streamRef.current = null;
|
|
5615
|
-
return renderSettled(text,
|
|
6503
|
+
return renderSettled(text, labels, fileMentions);
|
|
5616
6504
|
}
|
|
5617
|
-
if (streamRef.current === null || streamLabelsRef.current !==
|
|
5618
|
-
streamRef.current = new StreamingRenderer(
|
|
5619
|
-
streamLabelsRef.current =
|
|
6505
|
+
if (streamRef.current === null || streamLabelsRef.current !== labels) {
|
|
6506
|
+
streamRef.current = new StreamingRenderer(labels);
|
|
6507
|
+
streamLabelsRef.current = labels;
|
|
5620
6508
|
}
|
|
5621
6509
|
return streamRef.current.render(text);
|
|
5622
6510
|
}, [
|
|
5623
6511
|
text,
|
|
5624
6512
|
streaming,
|
|
5625
|
-
|
|
6513
|
+
labels,
|
|
5626
6514
|
fileMentions
|
|
5627
6515
|
]);
|
|
5628
6516
|
return jsx("div", {
|
|
5629
|
-
className: css$
|
|
6517
|
+
className: css$20.markdown,
|
|
5630
6518
|
children
|
|
5631
6519
|
});
|
|
5632
6520
|
});
|
|
@@ -5701,20 +6589,20 @@ function SafeLink({ url, label, className }) {
|
|
|
5701
6589
|
*/
|
|
5702
6590
|
function SourceItem({ source, ordinal }) {
|
|
5703
6591
|
return jsxs("li", {
|
|
5704
|
-
className: css$
|
|
6592
|
+
className: css$21.source,
|
|
5705
6593
|
value: ordinal,
|
|
5706
6594
|
children: [
|
|
5707
6595
|
jsx(SafeLink, {
|
|
5708
6596
|
url: source.url,
|
|
5709
6597
|
label: linkLabel(source.url, source.title),
|
|
5710
|
-
className: css$
|
|
6598
|
+
className: css$21.sourceLink
|
|
5711
6599
|
}),
|
|
5712
6600
|
source.snippet !== void 0 && source.snippet !== "" && jsx("div", {
|
|
5713
|
-
className: css$
|
|
6601
|
+
className: css$21.snippet,
|
|
5714
6602
|
children: source.snippet
|
|
5715
6603
|
}),
|
|
5716
6604
|
source.publishedAt !== void 0 && source.publishedAt !== "" && jsx("div", {
|
|
5717
|
-
className: css$
|
|
6605
|
+
className: css$21.published,
|
|
5718
6606
|
children: source.publishedAt
|
|
5719
6607
|
})
|
|
5720
6608
|
]
|
|
@@ -5726,29 +6614,32 @@ function SourceItem({ source, ordinal }) {
|
|
|
5726
6614
|
* @param props - see {@link WebSearchBlockProps}.
|
|
5727
6615
|
* @returns the search card element.
|
|
5728
6616
|
*/
|
|
5729
|
-
function WebSearchBlock({ answer, sources, truncated, className }) {
|
|
6617
|
+
function WebSearchBlock({ answer, sources, truncated, labels, className }) {
|
|
5730
6618
|
const empty = (answer === void 0 || answer === "") && sources.length === 0;
|
|
5731
6619
|
return jsxs("div", {
|
|
5732
|
-
className: clsx(css$
|
|
6620
|
+
className: clsx(css$21.block, className),
|
|
5733
6621
|
"data-web": "search",
|
|
5734
6622
|
children: [
|
|
5735
6623
|
answer !== void 0 && answer !== "" && jsx("div", {
|
|
5736
|
-
className: css$
|
|
5737
|
-
children: jsx(MarkdownText, {
|
|
6624
|
+
className: css$21.answer,
|
|
6625
|
+
children: jsx(MarkdownText, {
|
|
6626
|
+
text: answer,
|
|
6627
|
+
labels: labels.markdown
|
|
6628
|
+
})
|
|
5738
6629
|
}),
|
|
5739
6630
|
empty ? jsx("div", {
|
|
5740
|
-
className: css$
|
|
5741
|
-
children:
|
|
6631
|
+
className: css$21.empty,
|
|
6632
|
+
children: labels.noResults
|
|
5742
6633
|
}) : jsx("ol", {
|
|
5743
|
-
className: css$
|
|
6634
|
+
className: css$21.sources,
|
|
5744
6635
|
children: sources.map((source, index) => jsx(SourceItem, {
|
|
5745
6636
|
source,
|
|
5746
6637
|
ordinal: index + 1
|
|
5747
6638
|
}, index))
|
|
5748
6639
|
}),
|
|
5749
6640
|
truncated && jsx("div", {
|
|
5750
|
-
className: css$
|
|
5751
|
-
children:
|
|
6641
|
+
className: css$21.truncated,
|
|
6642
|
+
children: labels.sourcesTruncated
|
|
5752
6643
|
})
|
|
5753
6644
|
]
|
|
5754
6645
|
});
|
|
@@ -5758,22 +6649,26 @@ function WebSearchBlock({ answer, sources, truncated, className }) {
|
|
|
5758
6649
|
* @param props - see {@link WebFetchBlockProps}.
|
|
5759
6650
|
* @returns the fetch card element.
|
|
5760
6651
|
*/
|
|
5761
|
-
function WebFetchBlock({ url, statusCode, truncated, className }) {
|
|
6652
|
+
function WebFetchBlock({ url, statusCode, truncated, labels, className }) {
|
|
5762
6653
|
return jsxs("div", {
|
|
5763
|
-
className: clsx(css$
|
|
6654
|
+
className: clsx(css$21.block, css$21.fetch, className),
|
|
5764
6655
|
"data-web": "fetch",
|
|
5765
6656
|
children: [jsx(SafeLink, {
|
|
5766
6657
|
url,
|
|
5767
6658
|
label: url,
|
|
5768
|
-
className: css$
|
|
6659
|
+
className: css$21.fetchUrl
|
|
5769
6660
|
}), jsxs("div", {
|
|
5770
|
-
className: css$
|
|
6661
|
+
className: css$21.fetchMeta,
|
|
5771
6662
|
children: [jsxs("span", {
|
|
5772
|
-
className: css$
|
|
5773
|
-
children: [
|
|
6663
|
+
className: css$21.status,
|
|
6664
|
+
children: [
|
|
6665
|
+
labels.http,
|
|
6666
|
+
" ",
|
|
6667
|
+
statusCode
|
|
6668
|
+
]
|
|
5774
6669
|
}), truncated && jsx("span", {
|
|
5775
|
-
className: css$
|
|
5776
|
-
children:
|
|
6670
|
+
className: css$21.truncated,
|
|
6671
|
+
children: labels.contentTruncated
|
|
5777
6672
|
})]
|
|
5778
6673
|
})]
|
|
5779
6674
|
});
|
|
@@ -5789,11 +6684,7 @@ function WebBlock(props) {
|
|
|
5789
6684
|
//#endregion
|
|
5790
6685
|
//#region lib/types/markdown/JsonBlock.js
|
|
5791
6686
|
const MAX_CHARS = 2e4;
|
|
5792
|
-
|
|
5793
|
-
function defaultTruncatedLabel(total) {
|
|
5794
|
-
return `… 已截断,共 ${total} 字符`;
|
|
5795
|
-
}
|
|
5796
|
-
function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defaultTruncatedLabel }) {
|
|
6687
|
+
function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel }) {
|
|
5797
6688
|
const [open, setOpen] = useState(defaultOpen);
|
|
5798
6689
|
const body = useMemo(() => {
|
|
5799
6690
|
if (!open) return "";
|
|
@@ -5810,10 +6701,10 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defau
|
|
|
5810
6701
|
truncatedLabel
|
|
5811
6702
|
]);
|
|
5812
6703
|
return jsxs("div", {
|
|
5813
|
-
className: css$
|
|
6704
|
+
className: css$22.root,
|
|
5814
6705
|
children: [jsxs("button", {
|
|
5815
6706
|
type: "button",
|
|
5816
|
-
className: css$
|
|
6707
|
+
className: css$22.toggle,
|
|
5817
6708
|
onClick: () => {
|
|
5818
6709
|
setOpen((v) => !v);
|
|
5819
6710
|
},
|
|
@@ -5823,7 +6714,7 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defau
|
|
|
5823
6714
|
label
|
|
5824
6715
|
]
|
|
5825
6716
|
}), open && jsx("pre", {
|
|
5826
|
-
className: css$
|
|
6717
|
+
className: css$22.body,
|
|
5827
6718
|
children: body
|
|
5828
6719
|
})]
|
|
5829
6720
|
});
|
|
@@ -5832,7 +6723,7 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defau
|
|
|
5832
6723
|
//#region lib/types/markdown/MessageText.js
|
|
5833
6724
|
function MessageText({ text }) {
|
|
5834
6725
|
return jsx("div", {
|
|
5835
|
-
className: css$
|
|
6726
|
+
className: css$23.text,
|
|
5836
6727
|
children: text
|
|
5837
6728
|
});
|
|
5838
6729
|
}
|
|
@@ -5908,6 +6799,6 @@ function extractMarkdownPlainText(markdown, options = {}) {
|
|
|
5908
6799
|
}
|
|
5909
6800
|
}
|
|
5910
6801
|
//#endregion
|
|
5911
|
-
export { BrandWordmark, Button, CodeBlock,
|
|
6802
|
+
export { BrandWordmark, Button, CodeBlock, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, FISH_LOGO_PATH, FISH_LOGO_VIEWBOX, FishLogo, HoverCard, IconAgentPresetOutline16, IconAlarmClockOutline16, IconApiOutline14, IconArchiveOutline20, IconBranchOutline16, IconBrowseOutline16, IconCheckOutline14, IconCheckOutline16, IconChecklistOutline14, IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconClockOutline16, IconCloseFill14, IconCloseOutline16, IconCodeOutline16, IconContextInjectionOutline16, IconCopyOutline16, IconCordisPluginOutline14, IconDarkOutline16, IconDataOutline16, IconDatabaseOutline16, IconDislikeFill16, IconDislikeOutline16, IconDownloadOutline16, IconEditOutline16, IconEllipsisOutline16, IconEnhanceOutline16, IconFolderClose16, IconFolderOpen16, IconFolderOpenOutline16, IconFollowsystemOutline16, IconFullscreenOutline16, IconGlobeOutline14, IconGoalOutline16, IconInspectOutline12, IconLightOutline16, IconLikeFill16, IconLikeOutline16, IconLinkOutline14, IconLinkOutline16, IconListPenOutline16, IconLoadingOutline16, IconNewChatOutline16, IconPanelLeftOutline16, IconPaperclipOutline16, IconPauseOutline16, IconPersonalizationOutline16, IconPlayOutline16, IconPlusOutline16, IconProjectAddOutline16, IconQuestionOutline14, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconRightUpOutline16, IconSearchOutline16, IconSendOutline14, IconSendOutline16, IconSettingsOutline14, IconSettingsOutline16, IconShareOutline16, IconSkillOutline16, IconSparkle16, IconStopFill16, IconThinkOutline14, IconThinkOutline16, IconTrashOutline16, IconTreeCorner8x10, IconTriangleRightFill14, IconUserOutline16, IconWarningOutline16, Input, JsonBlock, JsonTree, MarkdownText, Menu, MessageText, Modal, OnboardingSurface, Pill, ReadBlock, ReferenceIcon, RiskConfirmation, SearchBlock, StateDot, TerminalBlock, Toast, Tooltip, WebBlock, diffTotals, extractMarkdownPlainText, projectUserText, relativeTime, useAnchoredMaxHeight, useAnchoredPosition, useDismissOnOutsidePointer, writeClipboard };
|
|
5912
6803
|
|
|
5913
6804
|
//# sourceMappingURL=index.js.map
|