dsh-model-everything 0.5.1 → 0.6.0
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/lib/annotation.js +323 -0
- package/lib/client.js +2138 -184
- package/lib/diagram-render-tool.js +13 -1
- package/lib/diagram-review-tool.js +1 -1
- package/lib/diagram.js +33 -0
- package/lib/gate.js +8 -6
- package/lib/render/paint.js +671 -207
- package/lib/render/svg.js +11 -2
- package/lib/render/trunk.js +213 -0
- package/lib/routing.js +12 -0
- package/lib/transcript.js +327 -0
- package/lib/types/annotation.d.ts +128 -0
- package/lib/types/client.d.ts +89 -6
- package/lib/types/diagram.d.ts +7 -0
- package/lib/types/gate.d.ts +8 -6
- package/lib/types/render/paint.d.ts +122 -6
- package/lib/types/render/place.d.ts +8 -3
- package/lib/types/render/trunk.d.ts +104 -0
- package/lib/types/transcript.d.ts +82 -0
- package/package.json +149 -148
- package/skills/model-everything/SKILL.md +2 -1
- package/skills/model-everything-diagram/SKILL.md +22 -1
- package/src/annotation.ts +333 -0
- package/src/client.ts +1841 -307
- package/src/diagram-render-tool.ts +13 -1
- package/src/diagram-review-tool.ts +1 -1
- package/src/diagram.ts +31 -0
- package/src/gate.ts +8 -6
- package/src/render/paint.ts +774 -205
- package/src/render/place.ts +8 -3
- package/src/render/svg.ts +12 -2
- package/src/render/trunk.ts +275 -0
- package/src/routing.ts +12 -0
- package/src/transcript.ts +349 -0
package/lib/client.js
CHANGED
|
@@ -29,9 +29,13 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
TOOL_NAMES: () => TOOL_NAMES,
|
|
30
30
|
WHITEBOARD_ID: () => WHITEBOARD_ID,
|
|
31
31
|
WHITEBOARD_SAMPLE: () => WHITEBOARD_SAMPLE,
|
|
32
|
+
annotationPanel: () => annotationPanel,
|
|
32
33
|
default: () => factory,
|
|
33
34
|
mountWhiteboard: () => mountWhiteboard,
|
|
35
|
+
onMessageSent: () => onMessageSent,
|
|
36
|
+
setPendingBlock: () => setPendingBlock,
|
|
34
37
|
themeMode: () => themeMode,
|
|
38
|
+
updateDockLayer: () => updateDockLayer,
|
|
35
39
|
viewFor: () => viewFor,
|
|
36
40
|
whiteboardFor: () => whiteboardFor
|
|
37
41
|
});
|
|
@@ -1617,13 +1621,121 @@ window.__ModuleLoader__.load({
|
|
|
1617
1621
|
return out;
|
|
1618
1622
|
}
|
|
1619
1623
|
|
|
1624
|
+
// src/render/trunk.ts
|
|
1625
|
+
var TRUNK_MIN_EDGES = 3;
|
|
1626
|
+
function isVerticalSide(side) {
|
|
1627
|
+
return side === "top" || side === "bottom";
|
|
1628
|
+
}
|
|
1629
|
+
function trunkAt(box, side) {
|
|
1630
|
+
if (side === "top") return box.y - ROUTE_STUB;
|
|
1631
|
+
if (side === "bottom") return box.y + box.height + ROUTE_STUB;
|
|
1632
|
+
if (side === "left") return box.x - ROUTE_STUB;
|
|
1633
|
+
return box.x + box.width + ROUTE_STUB;
|
|
1634
|
+
}
|
|
1635
|
+
function alongSide(point, side) {
|
|
1636
|
+
return isVerticalSide(side) ? point.x : point.y;
|
|
1637
|
+
}
|
|
1638
|
+
function alongNormal(point, side) {
|
|
1639
|
+
return isVerticalSide(side) ? point.y : point.x;
|
|
1640
|
+
}
|
|
1641
|
+
function facesTrunk(point, side, at) {
|
|
1642
|
+
const normal = normalOf(side);
|
|
1643
|
+
const outward = isVerticalSide(side) ? normal.y : normal.x;
|
|
1644
|
+
return (at - alongNormal(point, side)) * outward > 0.5;
|
|
1645
|
+
}
|
|
1646
|
+
function keyOf(end) {
|
|
1647
|
+
return end.to + "\0" + end.entry + "\0" + (end.arrow ?? "");
|
|
1648
|
+
}
|
|
1649
|
+
function clearSegment(boxes, skip, x1, y1, x2, y2) {
|
|
1650
|
+
for (const box of boxes) {
|
|
1651
|
+
if (skip.includes(box)) continue;
|
|
1652
|
+
if (crossesBox(x1, y1, x2, y2, box)) return false;
|
|
1653
|
+
}
|
|
1654
|
+
return true;
|
|
1655
|
+
}
|
|
1656
|
+
function onOneLine(group, side, at, low, high) {
|
|
1657
|
+
if (isVerticalSide(group.side) !== isVerticalSide(side)) return false;
|
|
1658
|
+
if (group.at !== at) return false;
|
|
1659
|
+
return Math.min(group.to, high) - Math.max(group.from, low) > 0.5;
|
|
1660
|
+
}
|
|
1661
|
+
function planTrunks(input) {
|
|
1662
|
+
const groups = [];
|
|
1663
|
+
const groupOf = /* @__PURE__ */ new Map();
|
|
1664
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1665
|
+
for (const end of input.ends) {
|
|
1666
|
+
if (end.runY === void 0) continue;
|
|
1667
|
+
if (isVerticalSide(end.exit) !== isVerticalSide(end.entry)) continue;
|
|
1668
|
+
const source = input.boxOf(end.from);
|
|
1669
|
+
const target = input.boxOf(end.to);
|
|
1670
|
+
if (source === void 0 || target === void 0) continue;
|
|
1671
|
+
if (source === target) continue;
|
|
1672
|
+
const exit = input.exitPortOf(end.index);
|
|
1673
|
+
if (exit === void 0) continue;
|
|
1674
|
+
const at = trunkAt(target, end.entry);
|
|
1675
|
+
if (!facesTrunk(exit, end.exit, at)) continue;
|
|
1676
|
+
const candidate = { end, source, target, at, exit, branch: alongSide(exit, end.exit) };
|
|
1677
|
+
const key = keyOf(end);
|
|
1678
|
+
const list = buckets.get(key);
|
|
1679
|
+
if (list === void 0) buckets.set(key, [candidate]);
|
|
1680
|
+
else list.push(candidate);
|
|
1681
|
+
}
|
|
1682
|
+
for (const list of buckets.values()) {
|
|
1683
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1684
|
+
for (const candidate of list) {
|
|
1685
|
+
const seen = bySource.get(candidate.end.from);
|
|
1686
|
+
if (seen === void 0) bySource.set(candidate.end.from, [candidate]);
|
|
1687
|
+
else seen.push(candidate);
|
|
1688
|
+
}
|
|
1689
|
+
const members = list.filter((candidate) => bySource.get(candidate.end.from).length === 1);
|
|
1690
|
+
if (members.length < TRUNK_MIN_EDGES) continue;
|
|
1691
|
+
const target = members[0].target;
|
|
1692
|
+
const side = members[0].end.entry;
|
|
1693
|
+
const at = members[0].at;
|
|
1694
|
+
const entryAlong = isVerticalSide(side) ? target.x + target.width / 2 : target.y + target.height / 2;
|
|
1695
|
+
let lead = members[0];
|
|
1696
|
+
for (const candidate of members) {
|
|
1697
|
+
if (Math.abs(candidate.branch - entryAlong) < Math.abs(lead.branch - entryAlong) - 1e-9) lead = candidate;
|
|
1698
|
+
}
|
|
1699
|
+
let low = entryAlong;
|
|
1700
|
+
let high = entryAlong;
|
|
1701
|
+
for (const candidate of members) {
|
|
1702
|
+
low = Math.min(low, candidate.branch);
|
|
1703
|
+
high = Math.max(high, candidate.branch);
|
|
1704
|
+
}
|
|
1705
|
+
const vertical = isVerticalSide(side);
|
|
1706
|
+
const run = vertical ? [low, at, high, at] : [at, low, at, high];
|
|
1707
|
+
if (!clearSegment(input.boxes, [target], run[0], run[1], run[2], run[3])) continue;
|
|
1708
|
+
let blocked = false;
|
|
1709
|
+
for (const candidate of members) {
|
|
1710
|
+
const drop = vertical ? [candidate.branch, candidate.exit.y, candidate.branch, at] : [candidate.exit.x, candidate.branch, at, candidate.branch];
|
|
1711
|
+
if (!clearSegment(input.boxes, [candidate.source, target], drop[0], drop[1], drop[2], drop[3])) blocked = true;
|
|
1712
|
+
}
|
|
1713
|
+
if (blocked) continue;
|
|
1714
|
+
if (groups.some((group) => onOneLine(group, side, at, low, high))) continue;
|
|
1715
|
+
const id = groups.length;
|
|
1716
|
+
groups.push({
|
|
1717
|
+
id,
|
|
1718
|
+
target: members[0].end.to,
|
|
1719
|
+
side,
|
|
1720
|
+
at,
|
|
1721
|
+
entryAlong,
|
|
1722
|
+
from: low,
|
|
1723
|
+
to: high,
|
|
1724
|
+
lead: lead.end.index,
|
|
1725
|
+
members: members.map((candidate) => candidate.end.index)
|
|
1726
|
+
});
|
|
1727
|
+
for (const candidate of members) groupOf.set(candidate.end.index, id);
|
|
1728
|
+
}
|
|
1729
|
+
return { groups, groupOf };
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1620
1732
|
// src/render/symbol.ts
|
|
1621
|
-
function cubicPoint(curve,
|
|
1622
|
-
const u = 1 -
|
|
1733
|
+
function cubicPoint(curve, t2) {
|
|
1734
|
+
const u = 1 - t2;
|
|
1623
1735
|
const a = u * u * u;
|
|
1624
|
-
const b = 3 * u * u *
|
|
1625
|
-
const c = 3 * u *
|
|
1626
|
-
const d =
|
|
1736
|
+
const b = 3 * u * u * t2;
|
|
1737
|
+
const c = 3 * u * t2 * t2;
|
|
1738
|
+
const d = t2 * t2 * t2;
|
|
1627
1739
|
return {
|
|
1628
1740
|
x: a * curve.from.x + b * curve.c1.x + c * curve.c2.x + d * curve.to.x,
|
|
1629
1741
|
y: a * curve.from.y + b * curve.c1.y + c * curve.c2.y + d * curve.to.y
|
|
@@ -1786,7 +1898,7 @@ window.__ModuleLoader__.load({
|
|
|
1786
1898
|
var CAPTION_CHAR_WIDTH = 6.2;
|
|
1787
1899
|
var PORT_SEPARATION = 14;
|
|
1788
1900
|
var PORT_SIDES = ["top", "right", "bottom", "left"];
|
|
1789
|
-
var
|
|
1901
|
+
var isVerticalSide2 = (side) => side === "top" || side === "bottom";
|
|
1790
1902
|
var MULTI_EDGE_SPACING = 20;
|
|
1791
1903
|
function packPorts(wanted, low, high) {
|
|
1792
1904
|
const placed = [];
|
|
@@ -1802,6 +1914,14 @@ window.__ModuleLoader__.load({
|
|
|
1802
1914
|
}
|
|
1803
1915
|
return placed;
|
|
1804
1916
|
}
|
|
1917
|
+
function centreRun(placed, low, high) {
|
|
1918
|
+
if (placed.length === 0) return placed;
|
|
1919
|
+
const first = placed[0];
|
|
1920
|
+
const last = placed[placed.length - 1];
|
|
1921
|
+
const middle = (low + high) / 2;
|
|
1922
|
+
const shift = Math.min(high - last, Math.max(low - first, middle - (first + last) / 2));
|
|
1923
|
+
return shift === 0 ? placed : placed.map((value) => value + shift);
|
|
1924
|
+
}
|
|
1805
1925
|
function roleOfNode(node) {
|
|
1806
1926
|
if (node.role === "error") return "fail";
|
|
1807
1927
|
if (node.role === "warning") return "findings";
|
|
@@ -1835,42 +1955,65 @@ window.__ModuleLoader__.load({
|
|
|
1835
1955
|
var LABEL_HEIGHT = 12;
|
|
1836
1956
|
var LABEL_PAD = 2;
|
|
1837
1957
|
var LABEL_STEP = 3;
|
|
1838
|
-
function
|
|
1958
|
+
function runsOf(points) {
|
|
1959
|
+
const runs = [];
|
|
1960
|
+
for (let index = 0; index + 1 < points.length; index += 1) {
|
|
1961
|
+
const from = points[index];
|
|
1962
|
+
const to = points[index + 1];
|
|
1963
|
+
const length = Math.abs(to.x - from.x) + Math.abs(to.y - from.y);
|
|
1964
|
+
if (length < 1) continue;
|
|
1965
|
+
runs.push({ from, to, length, index });
|
|
1966
|
+
}
|
|
1967
|
+
return runs.sort((left, right) => right.length - left.length || left.index - right.index);
|
|
1968
|
+
}
|
|
1969
|
+
function placeLabel(label, points, boxes, placed, otherRoutes) {
|
|
1839
1970
|
const width = textWidth(label);
|
|
1840
1971
|
const height = LABEL_HEIGHT;
|
|
1841
1972
|
const spotAt = (x, y) => ({ x, y, width, height });
|
|
1842
1973
|
const hitsBox = (spot, box) => spot.x - spot.width / 2 < box.x + box.width + LABEL_PAD && spot.x + spot.width / 2 > box.x - LABEL_PAD && spot.y - spot.height / 2 < box.y + box.height + LABEL_PAD && spot.y + spot.height / 2 > box.y - LABEL_PAD;
|
|
1843
1974
|
const hitsSpot = (spot, other) => spot.x - spot.width / 2 < other.x + other.width / 2 + LABEL_PAD && spot.x + spot.width / 2 > other.x - other.width / 2 - LABEL_PAD && spot.y - spot.height / 2 < other.y + other.height / 2 + LABEL_PAD && spot.y + spot.height / 2 > other.y - other.height / 2 - LABEL_PAD;
|
|
1844
1975
|
const hitsSegment = (spot, segment) => spot.x + spot.width / 2 > Math.min(segment.x1, segment.x2) - LABEL_PAD && spot.x - spot.width / 2 < Math.max(segment.x1, segment.x2) + LABEL_PAD && spot.y + spot.height / 2 > Math.min(segment.y1, segment.y2) - LABEL_PAD && spot.y - spot.height / 2 < Math.max(segment.y1, segment.y2) + LABEL_PAD;
|
|
1845
|
-
const
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
for (const
|
|
1852
|
-
|
|
1976
|
+
const collisionsAt = (spot, run) => {
|
|
1977
|
+
let boxHits = 0;
|
|
1978
|
+
let labelHits = 0;
|
|
1979
|
+
let edgeHits = 0;
|
|
1980
|
+
for (const box of boxes) if (hitsBox(spot, box)) boxHits += 1;
|
|
1981
|
+
for (const earlier of placed) if (hitsSpot(spot, earlier)) labelHits += 1;
|
|
1982
|
+
for (const route of otherRoutes) {
|
|
1983
|
+
for (const segment of route) {
|
|
1984
|
+
if (!hitsSegment(spot, segment)) continue;
|
|
1985
|
+
edgeHits += 1;
|
|
1986
|
+
break;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
return { run, collisions: boxHits + labelHits + edgeHits, boxes: boxHits, labels: labelHits, edges: edgeHits };
|
|
1853
1990
|
};
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
const
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1991
|
+
const runs = runsOf(points);
|
|
1992
|
+
if (runs.length === 0) {
|
|
1993
|
+
const point = points[0] ?? { x: 0, y: 0 };
|
|
1994
|
+
return { ...spotAt(point.x, point.y), run: -1, collisions: 0, boxes: 0, labels: 0, edges: 0 };
|
|
1995
|
+
}
|
|
1996
|
+
let best;
|
|
1997
|
+
for (const [runIndex, run] of runs.entries()) {
|
|
1998
|
+
const dx = run.to.x - run.from.x;
|
|
1999
|
+
const dy = run.to.y - run.from.y;
|
|
2000
|
+
const ux = dx / run.length;
|
|
2001
|
+
const uy = dy / run.length;
|
|
2002
|
+
const middle = { x: (run.from.x + run.to.x) / 2, y: (run.from.y + run.to.y) / 2 };
|
|
2003
|
+
const along = Math.abs(ux) * width / 2 + Math.abs(uy) * height / 2;
|
|
2004
|
+
const bound = Math.max(0, run.length / 2 - along);
|
|
2005
|
+
const walk = [0];
|
|
2006
|
+
for (let step = LABEL_STEP; step <= bound + 1e-3; step += LABEL_STEP) walk.push(step, -step);
|
|
2007
|
+
for (const offset of walk) {
|
|
2008
|
+
const spot = spotAt(middle.x + ux * offset, middle.y + uy * offset);
|
|
2009
|
+
const placement = collisionsAt(spot, runIndex);
|
|
2010
|
+
if (placement.collisions === 0) return { ...spot, ...placement };
|
|
2011
|
+
if (best === void 0 || placement.collisions < best.collisions || placement.collisions === best.collisions && placement.boxes + placement.labels < best.boxes + best.labels) {
|
|
2012
|
+
best = { ...spot, ...placement };
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
return best;
|
|
1874
2017
|
}
|
|
1875
2018
|
function drawnExtent(shapes) {
|
|
1876
2019
|
let width = 0;
|
|
@@ -1898,10 +2041,24 @@ window.__ModuleLoader__.load({
|
|
|
1898
2041
|
case "title":
|
|
1899
2042
|
consider(textWidth(shape.lines?.[0] ?? "") + shape.x, shape.y + 12);
|
|
1900
2043
|
break;
|
|
2044
|
+
case "junction":
|
|
2045
|
+
break;
|
|
1901
2046
|
}
|
|
1902
2047
|
}
|
|
1903
2048
|
return { width, height };
|
|
1904
2049
|
}
|
|
2050
|
+
function boxesCrossedBy(points, from, to, boxes) {
|
|
2051
|
+
const crossed = [];
|
|
2052
|
+
for (let index = 0; index + 1 < points.length; index += 1) {
|
|
2053
|
+
const a = points[index];
|
|
2054
|
+
const b = points[index + 1];
|
|
2055
|
+
for (const box of boxes) {
|
|
2056
|
+
if (box === from || box === to) continue;
|
|
2057
|
+
if (crossesBox(a.x, a.y, b.x, b.y, box)) crossed.push(box);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
return crossed;
|
|
2061
|
+
}
|
|
1905
2062
|
function paintWith(diagram, overrides = {}, options = {}) {
|
|
1906
2063
|
const colours = palette(overrides, options);
|
|
1907
2064
|
const shapes = [];
|
|
@@ -1928,6 +2085,7 @@ window.__ModuleLoader__.load({
|
|
|
1928
2085
|
const text2 = textOf(node);
|
|
1929
2086
|
shapes.push({
|
|
1930
2087
|
kind: "box",
|
|
2088
|
+
id: node.id,
|
|
1931
2089
|
x: box.x,
|
|
1932
2090
|
y: box.y,
|
|
1933
2091
|
width: box.width,
|
|
@@ -1965,21 +2123,100 @@ window.__ModuleLoader__.load({
|
|
|
1965
2123
|
endsAt.set(edge.from, (endsAt.get(edge.from) ?? 0) + 1);
|
|
1966
2124
|
endsAt.set(edge.to, (endsAt.get(edge.to) ?? 0) + 1);
|
|
1967
2125
|
}
|
|
2126
|
+
const spanOf2 = (boxId, side, corner) => {
|
|
2127
|
+
const box = endBox(boxId);
|
|
2128
|
+
const vertical = side === "top" || side === "bottom";
|
|
2129
|
+
const middle = vertical ? box.x + box.width / 2 : box.y + box.height / 2;
|
|
2130
|
+
const within = (low, high) => ({ low, high: Math.max(low, high) });
|
|
2131
|
+
switch (shapeOf(boxId)) {
|
|
2132
|
+
// The four vertices of a diamond, and the four cardinal points of an ellipse, are the middles of its sides.
|
|
2133
|
+
case "diamond":
|
|
2134
|
+
case "circle":
|
|
2135
|
+
return within(middle, middle);
|
|
2136
|
+
case "hexagon":
|
|
2137
|
+
return vertical ? within(box.x + hexagonCut(box.width), box.x + box.width - hexagonCut(box.width)) : within(middle, middle);
|
|
2138
|
+
case "stadium": {
|
|
2139
|
+
const radius = stadiumRadius(box);
|
|
2140
|
+
return vertical ? within(box.x + radius, box.x + box.width - radius) : within(middle, middle);
|
|
2141
|
+
}
|
|
2142
|
+
case "cylinder": {
|
|
2143
|
+
if (vertical) return within(middle, middle);
|
|
2144
|
+
const cap = cylinderCaps(box).cap;
|
|
2145
|
+
return within(box.y + cap, box.y + box.height - cap);
|
|
2146
|
+
}
|
|
2147
|
+
default: {
|
|
2148
|
+
const inset = corner ? 2 : vertical ? PORT_INSET : 8;
|
|
2149
|
+
return vertical ? within(box.x + inset, box.x + box.width - inset) : within(box.y + inset, box.y + box.height - inset);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
const clampTo = (boxId, side, value, corner) => {
|
|
2154
|
+
const span = spanOf2(boxId, side, corner);
|
|
2155
|
+
return Math.min(span.high, Math.max(span.low, value));
|
|
2156
|
+
};
|
|
2157
|
+
const pointOn = (boxId, side, along) => {
|
|
2158
|
+
const box = endBox(boxId);
|
|
2159
|
+
if (side === "left") return { x: box.x, y: along };
|
|
2160
|
+
if (side === "right") return { x: box.x + box.width, y: along };
|
|
2161
|
+
if (side === "top") return { x: along, y: shapeOf(boxId) === "cylinder" ? cylinderApex(box, "top").y : box.y };
|
|
2162
|
+
return { x: along, y: shapeOf(boxId) === "cylinder" ? cylinderApex(box, "bottom").y : box.y + box.height };
|
|
2163
|
+
};
|
|
1968
2164
|
const claimed = /* @__PURE__ */ new Map();
|
|
1969
2165
|
const claimedAt = /* @__PURE__ */ new Map();
|
|
1970
2166
|
const claim = (id, index, end, side) => {
|
|
1971
2167
|
claimed.set(endKey(index, end), side);
|
|
1972
|
-
claimedAt.set(id, [...claimedAt.get(id) ?? [], side]);
|
|
2168
|
+
claimedAt.set(id, [...claimedAt.get(id) ?? [], { index, side }]);
|
|
2169
|
+
};
|
|
2170
|
+
const takenSides = (id, except) => (claimedAt.get(id) ?? []).filter((entry) => entry.index !== except).map((entry) => entry.side);
|
|
2171
|
+
const sideMiddle = (box, side) => isVerticalSide2(side) ? box.x + box.width / 2 : box.y + box.height / 2;
|
|
2172
|
+
const sidePoint = (boxId, side) => pointOn(boxId, side, sideMiddle(endBox(boxId), side));
|
|
2173
|
+
const facesAway2 = (box, other, side) => {
|
|
2174
|
+
const out = normalOf(side);
|
|
2175
|
+
const dx = other.x + other.width / 2 - (box.x + box.width / 2);
|
|
2176
|
+
const dy = other.y + other.height / 2 - (box.y + box.height / 2);
|
|
2177
|
+
return out.x * dx + out.y * dy < 0;
|
|
2178
|
+
};
|
|
2179
|
+
const stubAlong = (pinnedId, pinnedSide, freeSide) => {
|
|
2180
|
+
const point = sidePoint(pinnedId, pinnedSide);
|
|
2181
|
+
const out = normalOf(pinnedSide);
|
|
2182
|
+
return isVerticalSide2(freeSide) ? point.x + out.x * ROUTE_STUB : point.y + out.y * ROUTE_STUB;
|
|
1973
2183
|
};
|
|
1974
|
-
const
|
|
2184
|
+
const offers = (boxId, side, value) => {
|
|
2185
|
+
const span = spanOf2(boxId, side, true);
|
|
2186
|
+
return value >= span.low - 0.01 && value <= span.high + 0.01;
|
|
2187
|
+
};
|
|
2188
|
+
const bendBudget = (fromId, toId, exit, entry, index) => {
|
|
2189
|
+
const from = endBox(fromId);
|
|
2190
|
+
const to = endBox(toId);
|
|
2191
|
+
if (facesAway2(from, to, exit) || facesAway2(to, from, entry)) return 4;
|
|
2192
|
+
if (isVerticalSide2(exit) === isVerticalSide2(entry)) {
|
|
2193
|
+
const out = normalOf(exit);
|
|
2194
|
+
const into = normalOf(entry);
|
|
2195
|
+
const sameWay = out.x === -into.x && out.y === -into.y;
|
|
2196
|
+
const fromSpan = spanOf2(fromId, exit, true);
|
|
2197
|
+
const toSpan = spanOf2(toId, entry, true);
|
|
2198
|
+
const meet = fromSpan.low <= toSpan.high + 0.01 && toSpan.low <= fromSpan.high + 0.01;
|
|
2199
|
+
return sameWay && meet ? 0 : 2;
|
|
2200
|
+
}
|
|
2201
|
+
const horizontalId = isVerticalSide2(exit) ? toId : fromId;
|
|
2202
|
+
const horizontalSide = isVerticalSide2(exit) ? entry : exit;
|
|
2203
|
+
const verticalId = isVerticalSide2(exit) ? fromId : toId;
|
|
2204
|
+
const verticalSide = isVerticalSide2(exit) ? exit : entry;
|
|
2205
|
+
if (!offers(verticalId, verticalSide, stubAlong(horizontalId, horizontalSide, verticalSide))) return 2;
|
|
2206
|
+
const crowded = takenSides(fromId, index).includes(exit) || takenSides(toId, index).includes(entry);
|
|
2207
|
+
return crowded ? 2 : 1;
|
|
2208
|
+
};
|
|
2209
|
+
const bestSide = (id, otherId, end, partner, index) => {
|
|
1975
2210
|
const box = endBox(id);
|
|
2211
|
+
const other = endBox(otherId);
|
|
1976
2212
|
const centre = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
1977
2213
|
const toward = { x: other.x + other.width / 2 - centre.x, y: other.y + other.height / 2 - centre.y };
|
|
1978
2214
|
const reach = Math.hypot(toward.x, toward.y);
|
|
1979
|
-
const taken =
|
|
2215
|
+
const taken = takenSides(id, index);
|
|
1980
2216
|
let best;
|
|
1981
2217
|
for (const side of PORT_SIDES) {
|
|
1982
2218
|
if (taken.includes(side)) continue;
|
|
2219
|
+
const bends = end === "exit" ? bendBudget(id, otherId, side, partner, index) : bendBudget(otherId, id, partner, side, index);
|
|
1983
2220
|
const normal = normalOf(side);
|
|
1984
2221
|
const score = reach === 0 ? 0 : (normal.x * toward.x + normal.y * toward.y) / reach;
|
|
1985
2222
|
const point = anchor(box, side);
|
|
@@ -1987,11 +2224,43 @@ window.__ModuleLoader__.load({
|
|
|
1987
2224
|
Math.max(other.x - point.x, 0, point.x - (other.x + other.width)),
|
|
1988
2225
|
Math.max(other.y - point.y, 0, point.y - (other.y + other.height))
|
|
1989
2226
|
);
|
|
1990
|
-
const
|
|
1991
|
-
|
|
2227
|
+
const partnerPoint = anchor(other, partner);
|
|
2228
|
+
const ground = Math.abs(point.x - partnerPoint.x) + Math.abs(point.y - partnerPoint.y);
|
|
2229
|
+
const better = best === void 0 || bends < best.bends || bends === best.bends && (score > best.score + 1e-9 || Math.abs(score - best.score) <= 1e-9 && (near < best.near - 1e-9 || Math.abs(near - best.near) <= 1e-9 && ground < best.ground - 1e-9));
|
|
2230
|
+
if (better) best = { side, bends, score, near, ground };
|
|
1992
2231
|
}
|
|
1993
2232
|
return best?.side;
|
|
1994
2233
|
};
|
|
2234
|
+
const bestPair = (fromId, toId, index) => {
|
|
2235
|
+
const approach = approachSides(endBox(fromId), endBox(toId));
|
|
2236
|
+
const pairs = [
|
|
2237
|
+
{ exit: "bottom", entry: "top" },
|
|
2238
|
+
{ exit: "top", entry: "bottom" },
|
|
2239
|
+
{ exit: "right", entry: "left" },
|
|
2240
|
+
{ exit: "left", entry: "right" }
|
|
2241
|
+
];
|
|
2242
|
+
let best = approach;
|
|
2243
|
+
let bestBends = bendBudget(fromId, toId, approach.exit, approach.entry, index);
|
|
2244
|
+
for (const pair of pairs) {
|
|
2245
|
+
const bends = bendBudget(fromId, toId, pair.exit, pair.entry, index);
|
|
2246
|
+
if (bends >= bestBends) continue;
|
|
2247
|
+
best = pair;
|
|
2248
|
+
bestBends = bends;
|
|
2249
|
+
}
|
|
2250
|
+
return best;
|
|
2251
|
+
};
|
|
2252
|
+
const bestEnd = (id, otherId, end, partner, index) => {
|
|
2253
|
+
const approach = end === "exit" ? approachSides(endBox(id), endBox(otherId)).exit : approachSides(endBox(otherId), endBox(id)).entry;
|
|
2254
|
+
let best = approach;
|
|
2255
|
+
let bestBends = end === "exit" ? bendBudget(id, otherId, approach, partner, index) : bendBudget(otherId, id, partner, approach, index);
|
|
2256
|
+
for (const side of PORT_SIDES) {
|
|
2257
|
+
const bends = end === "exit" ? bendBudget(id, otherId, side, partner, index) : bendBudget(otherId, id, partner, side, index);
|
|
2258
|
+
if (bends >= bestBends) continue;
|
|
2259
|
+
best = side;
|
|
2260
|
+
bestBends = bends;
|
|
2261
|
+
}
|
|
2262
|
+
return best;
|
|
2263
|
+
};
|
|
1995
2264
|
diagram.edges.forEach((edge, index) => {
|
|
1996
2265
|
if (edge.from === edge.to) return;
|
|
1997
2266
|
const source = endBox(edge.from);
|
|
@@ -2018,7 +2287,9 @@ window.__ModuleLoader__.load({
|
|
|
2018
2287
|
const id = end === "exit" ? edge.from : edge.to;
|
|
2019
2288
|
if ((endsAt.get(id) ?? 0) < 2) continue;
|
|
2020
2289
|
if (!hasDiscretePorts(shapeOf(id))) continue;
|
|
2021
|
-
const
|
|
2290
|
+
const approached = approachSides(source, target);
|
|
2291
|
+
const partner = claimed.get(endKey(index, end === "exit" ? "entry" : "exit")) ?? (end === "exit" ? approached.entry : approached.exit);
|
|
2292
|
+
const side = bestSide(id, end === "exit" ? edge.to : edge.from, end, partner, index);
|
|
2022
2293
|
if (side === void 0) continue;
|
|
2023
2294
|
claim(id, index, end, side);
|
|
2024
2295
|
}
|
|
@@ -2027,20 +2298,22 @@ window.__ModuleLoader__.load({
|
|
|
2027
2298
|
const source = endBox(edge.from);
|
|
2028
2299
|
const target = endBox(edge.to);
|
|
2029
2300
|
if (source === void 0 || target === void 0 || edge.from === edge.to) return void 0;
|
|
2030
|
-
|
|
2301
|
+
if (placement.across.has(index)) return { exit: "right", entry: "left" };
|
|
2031
2302
|
const lane = placement.lanes.get(index);
|
|
2032
|
-
|
|
2303
|
+
const down = target.y > source.y;
|
|
2304
|
+
if (lane !== void 0 && lane.points.length > 0) {
|
|
2033
2305
|
return {
|
|
2034
|
-
exit: claimed.get(endKey(index, "exit")) ??
|
|
2035
|
-
entry: claimed.get(endKey(index, "entry")) ??
|
|
2306
|
+
exit: claimed.get(endKey(index, "exit")) ?? (down ? "bottom" : "top"),
|
|
2307
|
+
entry: claimed.get(endKey(index, "entry")) ?? (down ? "top" : "bottom"),
|
|
2308
|
+
at: lane.points[0].x
|
|
2036
2309
|
};
|
|
2037
2310
|
}
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2311
|
+
const exit = claimed.get(endKey(index, "exit"));
|
|
2312
|
+
const entry = claimed.get(endKey(index, "entry"));
|
|
2313
|
+
if (exit !== void 0 && entry !== void 0) return { exit, entry };
|
|
2314
|
+
if (exit !== void 0) return { exit, entry: bestEnd(edge.to, edge.from, "entry", exit, index) };
|
|
2315
|
+
if (entry !== void 0) return { exit: bestEnd(edge.from, edge.to, "exit", entry, index), entry };
|
|
2316
|
+
return bestPair(edge.from, edge.to, index);
|
|
2044
2317
|
});
|
|
2045
2318
|
const laneClear = (points, x, from, to) => {
|
|
2046
2319
|
for (let at = 0; at + 1 < points.length; at += 1) {
|
|
@@ -2079,50 +2352,12 @@ window.__ModuleLoader__.load({
|
|
|
2079
2352
|
}
|
|
2080
2353
|
const wanted = /* @__PURE__ */ new Map();
|
|
2081
2354
|
const slotKey = (boxId, side, index) => boxId + ":" + side + ":" + String(index);
|
|
2082
|
-
const spanOf2 = (boxId, side, corner) => {
|
|
2083
|
-
const box = endBox(boxId);
|
|
2084
|
-
const vertical = side === "top" || side === "bottom";
|
|
2085
|
-
const middle = vertical ? box.x + box.width / 2 : box.y + box.height / 2;
|
|
2086
|
-
const within = (low, high) => ({ low, high: Math.max(low, high) });
|
|
2087
|
-
switch (shapeOf(boxId)) {
|
|
2088
|
-
// The four vertices of a diamond, and the four cardinal points of an ellipse, are the middles of its sides.
|
|
2089
|
-
case "diamond":
|
|
2090
|
-
case "circle":
|
|
2091
|
-
return within(middle, middle);
|
|
2092
|
-
case "hexagon":
|
|
2093
|
-
return vertical ? within(box.x + hexagonCut(box.width), box.x + box.width - hexagonCut(box.width)) : within(middle, middle);
|
|
2094
|
-
case "stadium": {
|
|
2095
|
-
const radius = stadiumRadius(box);
|
|
2096
|
-
return vertical ? within(box.x + radius, box.x + box.width - radius) : within(middle, middle);
|
|
2097
|
-
}
|
|
2098
|
-
case "cylinder": {
|
|
2099
|
-
if (vertical) return within(middle, middle);
|
|
2100
|
-
const cap = cylinderCaps(box).cap;
|
|
2101
|
-
return within(box.y + cap, box.y + box.height - cap);
|
|
2102
|
-
}
|
|
2103
|
-
default: {
|
|
2104
|
-
const inset = corner ? 2 : vertical ? PORT_INSET : 8;
|
|
2105
|
-
return vertical ? within(box.x + inset, box.x + box.width - inset) : within(box.y + inset, box.y + box.height - inset);
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
};
|
|
2109
|
-
const clampTo = (boxId, side, value, corner) => {
|
|
2110
|
-
const span = spanOf2(boxId, side, corner);
|
|
2111
|
-
return Math.min(span.high, Math.max(span.low, value));
|
|
2112
|
-
};
|
|
2113
|
-
const pointOn = (boxId, side, along) => {
|
|
2114
|
-
const box = endBox(boxId);
|
|
2115
|
-
if (side === "left") return { x: box.x, y: along };
|
|
2116
|
-
if (side === "right") return { x: box.x + box.width, y: along };
|
|
2117
|
-
if (side === "top") return { x: along, y: shapeOf(boxId) === "cylinder" ? cylinderApex(box, "top").y : box.y };
|
|
2118
|
-
return { x: along, y: shapeOf(boxId) === "cylinder" ? cylinderApex(box, "bottom").y : box.y + box.height };
|
|
2119
|
-
};
|
|
2120
2355
|
for (const [index, entry] of facing.entries()) {
|
|
2121
2356
|
if (entry === void 0) continue;
|
|
2122
2357
|
const edge = diagram.edges[index];
|
|
2123
2358
|
const from = endBox(edge.from);
|
|
2124
2359
|
const to = endBox(edge.to);
|
|
2125
|
-
const middle = (box, side) =>
|
|
2360
|
+
const middle = (box, side) => isVerticalSide2(side) ? box.x + box.width / 2 : box.y + box.height / 2;
|
|
2126
2361
|
const fromMiddle = middle(from, entry.exit);
|
|
2127
2362
|
const toMiddle = middle(to, entry.entry);
|
|
2128
2363
|
const corner = entry.at !== void 0;
|
|
@@ -2133,14 +2368,14 @@ window.__ModuleLoader__.load({
|
|
|
2133
2368
|
const chosen = entry.at + (laneShift.get(index) ?? 0);
|
|
2134
2369
|
exitWanted = clampTo(edge.from, entry.exit, chosen, corner);
|
|
2135
2370
|
entryWanted = clampTo(edge.to, entry.entry, chosen, corner);
|
|
2136
|
-
} else if (
|
|
2371
|
+
} else if (isVerticalSide2(entry.exit) === isVerticalSide2(entry.entry)) {
|
|
2137
2372
|
const target = spanOf2(edge.to, entry.entry, false);
|
|
2138
2373
|
const chosen = (fromMiddle >= target.low && fromMiddle <= target.high ? fromMiddle : clampTo(edge.from, entry.exit, toMiddle, false)) + offset;
|
|
2139
2374
|
exitWanted = clampTo(edge.from, entry.exit, chosen, corner);
|
|
2140
2375
|
entryWanted = clampTo(edge.to, entry.entry, chosen, corner);
|
|
2141
2376
|
} else {
|
|
2142
|
-
exitWanted = hasDiscretePorts(shapeOf(edge.from)) ? fromMiddle : clampTo(edge.from, entry.exit,
|
|
2143
|
-
entryWanted = hasDiscretePorts(shapeOf(edge.to)) ? toMiddle : clampTo(edge.to, entry.entry,
|
|
2377
|
+
exitWanted = hasDiscretePorts(shapeOf(edge.from)) ? fromMiddle : clampTo(edge.from, entry.exit, stubAlong(edge.to, entry.entry, entry.exit), false) + offset;
|
|
2378
|
+
entryWanted = hasDiscretePorts(shapeOf(edge.to)) ? toMiddle : clampTo(edge.to, entry.entry, stubAlong(edge.from, entry.exit, entry.entry), false) + offset;
|
|
2144
2379
|
}
|
|
2145
2380
|
wanted.set(slotKey(edge.from, entry.exit, index), exitWanted);
|
|
2146
2381
|
wanted.set(slotKey(edge.to, entry.entry, index), entryWanted);
|
|
@@ -2189,6 +2424,109 @@ window.__ModuleLoader__.load({
|
|
|
2189
2424
|
}
|
|
2190
2425
|
if (!moved) break;
|
|
2191
2426
|
}
|
|
2427
|
+
const trunkEnds = [];
|
|
2428
|
+
diagram.edges.forEach((edge, index) => {
|
|
2429
|
+
const entry = facing[index];
|
|
2430
|
+
if (entry === void 0) return;
|
|
2431
|
+
const runY = placement.runs.get(index);
|
|
2432
|
+
trunkEnds.push({
|
|
2433
|
+
index,
|
|
2434
|
+
from: edge.from,
|
|
2435
|
+
to: edge.to,
|
|
2436
|
+
exit: entry.exit,
|
|
2437
|
+
entry: entry.entry,
|
|
2438
|
+
...edge.arrow === void 0 ? {} : { arrow: edge.arrow },
|
|
2439
|
+
...runY === void 0 ? {} : { runY }
|
|
2440
|
+
});
|
|
2441
|
+
});
|
|
2442
|
+
const planFor = (exitPortOf) => planTrunks({
|
|
2443
|
+
ends: trunkEnds,
|
|
2444
|
+
// Only a node's own box is a trunk target: an arrow into a boundary points past it, not at it.
|
|
2445
|
+
boxOf: (id) => boxes.get(id),
|
|
2446
|
+
boxes: boxList,
|
|
2447
|
+
exitPortOf
|
|
2448
|
+
});
|
|
2449
|
+
const packedExit = (index) => {
|
|
2450
|
+
const entry = facing[index];
|
|
2451
|
+
if (entry === void 0) return void 0;
|
|
2452
|
+
const edge = diagram.edges[index];
|
|
2453
|
+
const coordinate = placedPort.get(slotKey(edge.from, entry.exit, index));
|
|
2454
|
+
return coordinate === void 0 ? void 0 : pointOn(edge.from, entry.exit, coordinate);
|
|
2455
|
+
};
|
|
2456
|
+
const planned = planFor(packedExit);
|
|
2457
|
+
const onLane = (index, coordinate) => {
|
|
2458
|
+
if (planned.groupOf.has(index)) return false;
|
|
2459
|
+
const lane = placement.lanes.get(index);
|
|
2460
|
+
if (lane === void 0 || lane.points.length === 0) return false;
|
|
2461
|
+
return Math.abs(lane.points[0].x - coordinate) <= 0.5;
|
|
2462
|
+
};
|
|
2463
|
+
const rows = [];
|
|
2464
|
+
const rowOf = /* @__PURE__ */ new Map();
|
|
2465
|
+
for (const [key, list] of sides) {
|
|
2466
|
+
const [boxId, side] = key.split(":");
|
|
2467
|
+
const span = spanOf2(boxId, side, true);
|
|
2468
|
+
const slots = [...list].map((entry) => ({ index: entry.index, at: placedPort.get(slotKey(boxId, side, entry.index)) })).sort((left, right) => left.at - right.at || left.index - right.index);
|
|
2469
|
+
const centred = centreRun(
|
|
2470
|
+
slots.map((slot) => slot.at),
|
|
2471
|
+
span.low,
|
|
2472
|
+
span.high
|
|
2473
|
+
);
|
|
2474
|
+
const first = slots[0];
|
|
2475
|
+
const row = {
|
|
2476
|
+
boxId,
|
|
2477
|
+
side,
|
|
2478
|
+
indexes: slots.map((slot) => slot.index),
|
|
2479
|
+
at: slots.map((slot) => slot.at),
|
|
2480
|
+
position: new Map(slots.map((slot, at) => [slot.index, at])),
|
|
2481
|
+
shift: centred[0] - first.at,
|
|
2482
|
+
allowed: true
|
|
2483
|
+
};
|
|
2484
|
+
rows.push(row);
|
|
2485
|
+
rowOf.set(key, row);
|
|
2486
|
+
}
|
|
2487
|
+
const movedAt = (row, at) => row.at[at] + (row.allowed ? row.shift : 0);
|
|
2488
|
+
const partnerOf = (boxId, side, index) => {
|
|
2489
|
+
const entry = facing[index];
|
|
2490
|
+
if (entry === void 0) return void 0;
|
|
2491
|
+
const edge = diagram.edges[index];
|
|
2492
|
+
const isExit = edge.from === boxId && entry.exit === side;
|
|
2493
|
+
const groupId = planned.groupOf.get(index);
|
|
2494
|
+
if (groupId !== void 0) {
|
|
2495
|
+
if (!isExit) return void 0;
|
|
2496
|
+
const group = planned.groups[groupId];
|
|
2497
|
+
if (isVerticalSide2(side) !== isVerticalSide2(group.side)) return void 0;
|
|
2498
|
+
return { coordinate: group.entryAlong };
|
|
2499
|
+
}
|
|
2500
|
+
const partnerId = isExit ? edge.to : edge.from;
|
|
2501
|
+
const partnerSide = isExit ? entry.entry : entry.exit;
|
|
2502
|
+
if (isVerticalSide2(side) !== isVerticalSide2(partnerSide)) return void 0;
|
|
2503
|
+
const coordinate = placedPort.get(slotKey(partnerId, partnerSide, index));
|
|
2504
|
+
if (coordinate === void 0) return void 0;
|
|
2505
|
+
const row = rowOf.get(partnerId + ":" + partnerSide);
|
|
2506
|
+
return { coordinate, ...row === void 0 ? {} : { row, at: row.position.get(index) } };
|
|
2507
|
+
};
|
|
2508
|
+
for (let pass = 0; pass < rows.length; pass += 1) {
|
|
2509
|
+
let refused = false;
|
|
2510
|
+
for (const row of rows) {
|
|
2511
|
+
if (!row.allowed || row.shift === 0) continue;
|
|
2512
|
+
const pinned = row.indexes.some((index, at) => {
|
|
2513
|
+
if (onLane(index, row.at[at])) return true;
|
|
2514
|
+
const partner = partnerOf(row.boxId, row.side, index);
|
|
2515
|
+
if (partner === void 0) return false;
|
|
2516
|
+
if (Math.abs(row.at[at] - partner.coordinate) > 0.5) return false;
|
|
2517
|
+
const partnerAt = partner.row === void 0 ? partner.coordinate : movedAt(partner.row, partner.at);
|
|
2518
|
+
return Math.abs(movedAt(row, at) - partnerAt) > 0.5;
|
|
2519
|
+
});
|
|
2520
|
+
if (!pinned) continue;
|
|
2521
|
+
row.allowed = false;
|
|
2522
|
+
refused = true;
|
|
2523
|
+
}
|
|
2524
|
+
if (!refused) break;
|
|
2525
|
+
}
|
|
2526
|
+
for (const row of rows) {
|
|
2527
|
+
if (!row.allowed || row.shift === 0) continue;
|
|
2528
|
+
row.indexes.forEach((index, at) => placedPort.set(slotKey(row.boxId, row.side, index), movedAt(row, at)));
|
|
2529
|
+
}
|
|
2192
2530
|
const portOf = (boxId, side, edgeIndex) => {
|
|
2193
2531
|
const coordinate = placedPort.get(slotKey(boxId, side, edgeIndex));
|
|
2194
2532
|
return pointOn(boxId, side, coordinate);
|
|
@@ -2204,16 +2542,9 @@ window.__ModuleLoader__.load({
|
|
|
2204
2542
|
entrySide: entry.entry
|
|
2205
2543
|
});
|
|
2206
2544
|
}
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const from = points[index];
|
|
2211
|
-
const to = points[index + 1];
|
|
2212
|
-
const length = Math.abs(to.x - from.x) + Math.abs(to.y - from.y);
|
|
2213
|
-
if (length > best.length) best = { from, to, length };
|
|
2214
|
-
}
|
|
2215
|
-
return best;
|
|
2216
|
-
};
|
|
2545
|
+
const trunks = planFor((index) => anchorsFor.get(index)?.exit);
|
|
2546
|
+
const sharedEntry = /* @__PURE__ */ new Map();
|
|
2547
|
+
for (const group of trunks.groups) sharedEntry.set(group.id, pointOn(group.target, group.side, group.entryAlong));
|
|
2217
2548
|
const pointsOfRoute = (shape) => shape.kind === "path" ? shape.points : [{ x: shape.x1, y: shape.y1 }, { x: shape.x2, y: shape.y2 }];
|
|
2218
2549
|
const segmentsOfRoute = (shape) => {
|
|
2219
2550
|
const points = pointsOfRoute(shape);
|
|
@@ -2225,7 +2556,7 @@ window.__ModuleLoader__.load({
|
|
|
2225
2556
|
}
|
|
2226
2557
|
return out;
|
|
2227
2558
|
};
|
|
2228
|
-
const paintPath = (edge, points, colour) => {
|
|
2559
|
+
const paintPath = (edgeIndex, edge, points, colour) => {
|
|
2229
2560
|
const shape = {
|
|
2230
2561
|
kind: "path",
|
|
2231
2562
|
points,
|
|
@@ -2237,11 +2568,11 @@ window.__ModuleLoader__.load({
|
|
|
2237
2568
|
...edge.arrow === void 0 ? {} : { arrow: edge.arrow }
|
|
2238
2569
|
};
|
|
2239
2570
|
shapes.push(shape);
|
|
2240
|
-
routes.push(shape);
|
|
2571
|
+
routes.push({ shape, edge: edgeIndex, fixed: false });
|
|
2241
2572
|
};
|
|
2242
|
-
const paintRoute = (edge, points, colour) => {
|
|
2573
|
+
const paintRoute = (edgeIndex, edge, points, colour) => {
|
|
2243
2574
|
if (points.length > 2) {
|
|
2244
|
-
paintPath(edge, points, colour);
|
|
2575
|
+
paintPath(edgeIndex, edge, points, colour);
|
|
2245
2576
|
return;
|
|
2246
2577
|
}
|
|
2247
2578
|
const exit = points[0];
|
|
@@ -2260,38 +2591,31 @@ window.__ModuleLoader__.load({
|
|
|
2260
2591
|
...edge.arrow === void 0 ? {} : { arrow: edge.arrow }
|
|
2261
2592
|
};
|
|
2262
2593
|
shapes.push(shape);
|
|
2263
|
-
routes.push(shape);
|
|
2594
|
+
routes.push({ shape, edge: edgeIndex, fixed: false });
|
|
2264
2595
|
};
|
|
2596
|
+
const clearOfBoxes = (points, from, to) => boxesCrossedBy(points, from, to, boxList).length === 0;
|
|
2265
2597
|
const runRoute = (ports, runY) => {
|
|
2266
|
-
const out =
|
|
2267
|
-
const into =
|
|
2598
|
+
const out = isVerticalSide2(ports.exitSide) ? 0 : normalOf(ports.exitSide).x * ROUTE_STUB;
|
|
2599
|
+
const into = isVerticalSide2(ports.entrySide) ? 0 : normalOf(ports.entrySide).x * ROUTE_STUB;
|
|
2600
|
+
const exitRun = out !== 0 && into === 0 && Math.sign(ports.entry.x - ports.exit.x) === Math.sign(out) ? ports.entry.x : ports.exit.x + out;
|
|
2601
|
+
const entryRun = into !== 0 && out === 0 && Math.sign(ports.exit.x - ports.entry.x) === Math.sign(into) ? ports.exit.x : ports.entry.x + into;
|
|
2268
2602
|
const points = [ports.exit];
|
|
2269
|
-
if (out !== 0) points.push({ x:
|
|
2270
|
-
points.push({ x:
|
|
2271
|
-
if (into !== 0) points.push({ x:
|
|
2603
|
+
if (out !== 0) points.push({ x: exitRun, y: ports.exit.y });
|
|
2604
|
+
points.push({ x: exitRun, y: runY }, { x: entryRun, y: runY });
|
|
2605
|
+
if (into !== 0) points.push({ x: entryRun, y: ports.entry.y });
|
|
2272
2606
|
points.push(ports.entry);
|
|
2273
2607
|
return simplify(points);
|
|
2274
2608
|
};
|
|
2275
2609
|
const runFits = (ports, runY) => {
|
|
2276
|
-
if (
|
|
2610
|
+
if (isVerticalSide2(ports.exitSide) && Math.abs(runY - ports.exit.y) > 0.5) {
|
|
2277
2611
|
if (Math.sign(runY - ports.exit.y) !== Math.sign(normalOf(ports.exitSide).y)) return false;
|
|
2278
2612
|
}
|
|
2279
|
-
if (
|
|
2613
|
+
if (isVerticalSide2(ports.entrySide) && Math.abs(ports.entry.y - runY) > 0.5) {
|
|
2280
2614
|
if (Math.sign(ports.entry.y - runY) !== -Math.sign(normalOf(ports.entrySide).y)) return false;
|
|
2281
2615
|
}
|
|
2282
2616
|
return true;
|
|
2283
2617
|
};
|
|
2284
|
-
const
|
|
2285
|
-
for (let at = 0; at + 1 < points.length; at += 1) {
|
|
2286
|
-
const a = points[at];
|
|
2287
|
-
const b = points[at + 1];
|
|
2288
|
-
for (const box of boxList) {
|
|
2289
|
-
if (box === from || box === to) continue;
|
|
2290
|
-
if (crossesBox(a.x, a.y, b.x, b.y, box)) return false;
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
return true;
|
|
2294
|
-
};
|
|
2618
|
+
const junctions = [];
|
|
2295
2619
|
for (const [edgeIndex, edge] of diagram.edges.entries()) {
|
|
2296
2620
|
const source = endBox(edge.from);
|
|
2297
2621
|
const target = endBox(edge.to);
|
|
@@ -2300,7 +2624,7 @@ window.__ModuleLoader__.load({
|
|
|
2300
2624
|
if (edge.from === edge.to) {
|
|
2301
2625
|
const x = source.x + source.width;
|
|
2302
2626
|
const y = source.y + source.height / 2;
|
|
2303
|
-
|
|
2627
|
+
const shape = {
|
|
2304
2628
|
kind: "path",
|
|
2305
2629
|
points: [
|
|
2306
2630
|
{ x, y: y - 9 },
|
|
@@ -2316,10 +2640,25 @@ window.__ModuleLoader__.load({
|
|
|
2316
2640
|
colour,
|
|
2317
2641
|
...edge.dashed === void 0 ? {} : { dashed: edge.dashed },
|
|
2318
2642
|
...edge.arrow === void 0 ? {} : { arrow: edge.arrow }
|
|
2319
|
-
}
|
|
2643
|
+
};
|
|
2644
|
+
shapes.push(shape);
|
|
2645
|
+
routes.push({ shape, edge: edgeIndex, fixed: true });
|
|
2320
2646
|
continue;
|
|
2321
2647
|
}
|
|
2322
2648
|
const anchors = anchorsFor.get(edgeIndex);
|
|
2649
|
+
const groupId = trunks.groupOf.get(edgeIndex);
|
|
2650
|
+
if (groupId !== void 0 && anchors !== void 0) {
|
|
2651
|
+
const group = trunks.groups[groupId];
|
|
2652
|
+
const entry = sharedEntry.get(groupId);
|
|
2653
|
+
const branch = isVerticalSide2(group.side) ? [anchors.exit, { x: anchors.exit.x, y: group.at }, { x: entry.x, y: group.at }, entry] : [anchors.exit, { x: group.at, y: anchors.exit.y }, { x: group.at, y: entry.y }, entry];
|
|
2654
|
+
const points = simplify(branch);
|
|
2655
|
+
if (clearOfBoxes(points, source, target)) {
|
|
2656
|
+
paintRoute(edgeIndex, edge, points, colour);
|
|
2657
|
+
const meet = isVerticalSide2(group.side) ? { x: anchors.exit.x, y: group.at } : { x: group.at, y: anchors.exit.y };
|
|
2658
|
+
junctions.push({ kind: "junction", x: meet.x, y: meet.y, colour, trunk: group.id });
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2323
2662
|
const lane = placement.lanes.get(edgeIndex);
|
|
2324
2663
|
if (lane !== void 0 && lane.points.length > 0 && anchors !== void 0) {
|
|
2325
2664
|
const shift = laneShift.get(edgeIndex) ?? 0;
|
|
@@ -2334,32 +2673,76 @@ window.__ModuleLoader__.load({
|
|
|
2334
2673
|
}
|
|
2335
2674
|
if (Math.abs(previous.x - anchors.entry.x) > 0.5 && Math.abs(previous.y - anchors.entry.y) > 0.5) points.push({ x: anchors.entry.x, y: previous.y });
|
|
2336
2675
|
points.push(anchors.entry);
|
|
2337
|
-
|
|
2338
|
-
|
|
2676
|
+
const simplified = simplify(points);
|
|
2677
|
+
if (clearOfBoxes(simplified, source, target)) {
|
|
2678
|
+
paintPath(edgeIndex, edge, simplified, colour);
|
|
2679
|
+
continue;
|
|
2680
|
+
}
|
|
2339
2681
|
}
|
|
2340
2682
|
const runY = placement.runs.get(edgeIndex);
|
|
2341
2683
|
if (runY !== void 0 && anchors !== void 0 && runFits(anchors, runY)) {
|
|
2342
2684
|
const points = runRoute(anchors, runY);
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
paintRoute(edge, points, colour);
|
|
2685
|
+
if (clearOfBoxes(points, source, target)) {
|
|
2686
|
+
paintRoute(edgeIndex, edge, points, colour);
|
|
2346
2687
|
continue;
|
|
2347
2688
|
}
|
|
2348
2689
|
}
|
|
2349
|
-
paintRoute(edge, routeEdge(source, target, boxList, anchors).points, colour);
|
|
2690
|
+
paintRoute(edgeIndex, edge, routeEdge(source, target, boxList, anchors).points, colour);
|
|
2691
|
+
}
|
|
2692
|
+
for (const dot of junctions) shapes.push(dot);
|
|
2693
|
+
const boxEntries = Array.from(boxes.entries());
|
|
2694
|
+
const routingFailures = [];
|
|
2695
|
+
for (const { shape, edge } of routes) {
|
|
2696
|
+
const diagramEdge = diagram.edges[edge];
|
|
2697
|
+
const source = endBox(diagramEdge.from);
|
|
2698
|
+
const target = endBox(diagramEdge.to);
|
|
2699
|
+
if (source === void 0 || target === void 0) continue;
|
|
2700
|
+
for (const segment of segmentsOfRoute(shape)) {
|
|
2701
|
+
for (const [id, box] of boxEntries) {
|
|
2702
|
+
if (box === source || box === target) continue;
|
|
2703
|
+
if (!crossesBox(segment.x1, segment.y1, segment.x2, segment.y2, box)) continue;
|
|
2704
|
+
routingFailures.push({
|
|
2705
|
+
edge,
|
|
2706
|
+
from: diagramEdge.from,
|
|
2707
|
+
to: diagramEdge.to,
|
|
2708
|
+
box: id,
|
|
2709
|
+
x1: segment.x1,
|
|
2710
|
+
y1: segment.y1,
|
|
2711
|
+
x2: segment.x2,
|
|
2712
|
+
y2: segment.y2
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2350
2716
|
}
|
|
2351
|
-
const routeSegments = routes.map((shape) => segmentsOfRoute(shape));
|
|
2717
|
+
const routeSegments = routes.map(({ shape }) => segmentsOfRoute(shape));
|
|
2352
2718
|
const placedLabels = [];
|
|
2353
|
-
routes.forEach((shape,
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
}
|
|
2360
|
-
shape.
|
|
2361
|
-
|
|
2719
|
+
routes.forEach(({ shape, fixed }, at) => {
|
|
2720
|
+
if (fixed) {
|
|
2721
|
+
if (shape.label !== "") {
|
|
2722
|
+
placedLabels.push({ x: shape.labelX, y: shape.labelY, width: textWidth(shape.label), height: LABEL_HEIGHT });
|
|
2723
|
+
}
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
if (shape.label === "") return;
|
|
2727
|
+
const placement2 = placeLabel(
|
|
2728
|
+
shape.label,
|
|
2729
|
+
pointsOfRoute(shape),
|
|
2730
|
+
boxList,
|
|
2731
|
+
placedLabels,
|
|
2732
|
+
routeSegments.filter((_, other) => other !== at)
|
|
2733
|
+
);
|
|
2734
|
+
shape.labelX = placement2.x;
|
|
2735
|
+
shape.labelY = placement2.y;
|
|
2736
|
+
shape.labelPlacement = {
|
|
2737
|
+
run: placement2.run,
|
|
2738
|
+
collisions: placement2.collisions,
|
|
2739
|
+
boxes: placement2.boxes,
|
|
2740
|
+
labels: placement2.labels,
|
|
2741
|
+
edges: placement2.edges
|
|
2742
|
+
};
|
|
2743
|
+
placedLabels.push({ x: placement2.x, y: placement2.y, width: placement2.width, height: placement2.height });
|
|
2362
2744
|
});
|
|
2745
|
+
const labelCollisions = routes.reduce((total, { shape }) => total + ((shape.labelPlacement?.collisions ?? 0) > 0 ? 1 : 0), 0);
|
|
2363
2746
|
shapes.push({ kind: "title", x: MARGIN, y: MARGIN + 12, text: diagram.title, colour: "text" });
|
|
2364
2747
|
const contentRight = Math.max(
|
|
2365
2748
|
MARGIN,
|
|
@@ -2380,7 +2763,10 @@ window.__ModuleLoader__.load({
|
|
|
2380
2763
|
const extent = drawnExtent(shapes);
|
|
2381
2764
|
const canvasWidth = Math.max(width, extent.width + MARGIN);
|
|
2382
2765
|
const canvasHeight = Math.max(height, extent.height + MARGIN);
|
|
2383
|
-
return {
|
|
2766
|
+
return {
|
|
2767
|
+
canvas: { width: Math.round(canvasWidth), height: Math.round(canvasHeight), shapes, routingFailures, labelCollisions },
|
|
2768
|
+
colours
|
|
2769
|
+
};
|
|
2384
2770
|
}
|
|
2385
2771
|
|
|
2386
2772
|
// src/render/from-mermaid.ts
|
|
@@ -3107,13 +3493,16 @@ window.__ModuleLoader__.load({
|
|
|
3107
3493
|
const after = shape.points[1] ?? first;
|
|
3108
3494
|
return '<polyline points="' + points + '" fill="none" stroke="' + stroke + '" stroke-width="1" stroke-linejoin="round"' + dash(shape) + " />" + edgeMarks(first, after, before, last, shape.arrow, stroke, colours.background) + edgeLabel(shape.label, shape.labelX, shape.labelY, colours);
|
|
3109
3495
|
},
|
|
3496
|
+
// A junction dot: the visible answer to "is this a join or a crossing?". Filled in the line's own colour, so it
|
|
3497
|
+
// reads as the line thickening where a branch meets it rather than as a new mark on the page.
|
|
3498
|
+
junction: (shape, colours) => '<circle cx="' + n(shape.x) + '" cy="' + n(shape.y) + '" r="2.5" fill="' + colours[shape.colour] + '" />',
|
|
3110
3499
|
title: (shape, colours) => {
|
|
3111
3500
|
return '<text x="' + n(shape.x) + '" y="' + n(shape.y) + '" font-size="12" fill="' + colours[shape.colour] + '">' + escape(shape.text) + "</text>";
|
|
3112
3501
|
},
|
|
3113
3502
|
caption: (shape, colours) => {
|
|
3114
3503
|
const colour = colours[shape.colour];
|
|
3115
3504
|
const lines = shape.lines.map((line, index) => '<tspan x="' + n(shape.x) + '" dy="' + (index === 0 ? 0 : 15) + '">' + escape(line) + "</tspan>").join("");
|
|
3116
|
-
return '<text x="' + n(shape.x) + '" y="' + n(shape.y + 11) + '" font-size="10"
|
|
3505
|
+
return '<text x="' + n(shape.x) + '" y="' + n(shape.y + 11) + '" font-size="10" fill="' + colour + '">' + lines + "</text>";
|
|
3117
3506
|
}
|
|
3118
3507
|
};
|
|
3119
3508
|
function dash(shape) {
|
|
@@ -3184,7 +3573,9 @@ window.__ModuleLoader__.load({
|
|
|
3184
3573
|
dropped += 1;
|
|
3185
3574
|
continue;
|
|
3186
3575
|
}
|
|
3187
|
-
|
|
3576
|
+
const drawn = writer(shape, colours);
|
|
3577
|
+
const nodeId = shape.id;
|
|
3578
|
+
body.push(shape.kind === "box" && typeof nodeId === "string" ? '<g data-node-id="' + escape(nodeId) + '">' + drawn + "</g>" : drawn);
|
|
3188
3579
|
}
|
|
3189
3580
|
const header = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + n(canvas.width) + " " + n(canvas.height) + '" width="' + n(canvas.width) + '" height="' + n(canvas.height) + '" role="img"' + (label === void 0 ? "" : ' aria-label="' + escape(label) + '"') + ' font-family="system-ui, -apple-system, Segoe UI, sans-serif">';
|
|
3190
3581
|
const background = '<rect x="0" y="0" width="' + n(canvas.width) + '" height="' + n(canvas.height) + '" fill="' + colours.background + '" />';
|
|
@@ -3297,8 +3688,409 @@ window.__ModuleLoader__.load({
|
|
|
3297
3688
|
};
|
|
3298
3689
|
}
|
|
3299
3690
|
|
|
3691
|
+
// src/annotation.ts
|
|
3692
|
+
function annotationBlock(annotations) {
|
|
3693
|
+
const count = annotations.length;
|
|
3694
|
+
const lines = [
|
|
3695
|
+
"Diagram annotations - " + String(count) + (count === 1 ? " item" : " items") + " (numbers match the picture).",
|
|
3696
|
+
'Please answer each item separately, and start each answer with "Annotation N:".'
|
|
3697
|
+
];
|
|
3698
|
+
const caption = (annotations[0]?.caption ?? "").trim();
|
|
3699
|
+
if (caption !== "") lines.push("Drawing: " + caption);
|
|
3700
|
+
annotations.forEach((annotation, index) => {
|
|
3701
|
+
const label = (annotation.label ?? "").trim();
|
|
3702
|
+
const node = label === "" || label === annotation.nodeId ? annotation.nodeId : label + " (" + annotation.nodeId + ")";
|
|
3703
|
+
const note = (annotation.note ?? "").trim();
|
|
3704
|
+
lines.push("");
|
|
3705
|
+
lines.push("Diagram note [" + String(index + 1) + "] node " + node + " - " + (note === "" ? "(marked, no note)" : note));
|
|
3706
|
+
});
|
|
3707
|
+
lines.push("", "Question:");
|
|
3708
|
+
return lines.join(String.fromCharCode(10));
|
|
3709
|
+
}
|
|
3710
|
+
var COMPOSER_MARKS = [
|
|
3711
|
+
"[data-focus-flow] textarea",
|
|
3712
|
+
"textarea[data-focus-flow]",
|
|
3713
|
+
'[data-focus-flow] [contenteditable="true"]',
|
|
3714
|
+
'[data-focus-flow][contenteditable="true"]',
|
|
3715
|
+
// No marker on this host - measured, not assumed. Everything this plugin writes carries
|
|
3716
|
+
// `data-dme-own`, so what remains is somebody else's writing surface.
|
|
3717
|
+
"textarea:not([data-dme-own])",
|
|
3718
|
+
'[contenteditable="true"]:not([data-dme-own])'
|
|
3719
|
+
];
|
|
3720
|
+
function findComposer(doc) {
|
|
3721
|
+
if (doc === void 0) return void 0;
|
|
3722
|
+
try {
|
|
3723
|
+
for (const mark of COMPOSER_MARKS) {
|
|
3724
|
+
const found = doc.querySelector(mark);
|
|
3725
|
+
if (found !== null && typeof found === "object") return found;
|
|
3726
|
+
}
|
|
3727
|
+
const marked = doc.querySelector("[data-focus-flow]");
|
|
3728
|
+
if (marked !== null && typeof marked === "object") {
|
|
3729
|
+
const inner = marked.querySelector?.('textarea, [contenteditable="true"]');
|
|
3730
|
+
if (inner !== null && inner !== void 0 && typeof inner === "object") return inner;
|
|
3731
|
+
}
|
|
3732
|
+
return void 0;
|
|
3733
|
+
} catch {
|
|
3734
|
+
return void 0;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
function eventOf(type, init) {
|
|
3738
|
+
const scope = globalThis;
|
|
3739
|
+
if (scope.Event === void 0) return void 0;
|
|
3740
|
+
try {
|
|
3741
|
+
return new scope.Event(type, init);
|
|
3742
|
+
} catch {
|
|
3743
|
+
return void 0;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
function setValue(element, next) {
|
|
3747
|
+
const scope = globalThis;
|
|
3748
|
+
for (const ctor of [scope.HTMLTextAreaElement, scope.HTMLInputElement]) {
|
|
3749
|
+
const descriptor = ctor === void 0 ? void 0 : Object.getOwnPropertyDescriptor(ctor.prototype, "value");
|
|
3750
|
+
if (descriptor !== void 0 && typeof descriptor.set === "function") {
|
|
3751
|
+
descriptor.set.call(element, next);
|
|
3752
|
+
return;
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
element.value = next;
|
|
3756
|
+
}
|
|
3757
|
+
function fillComposer(doc, text2) {
|
|
3758
|
+
const composer = findComposer(doc);
|
|
3759
|
+
if (composer === void 0) return "no-composer";
|
|
3760
|
+
const isField = typeof composer.value === "string" || composer.tagName === "TEXTAREA" || composer.tagName === "INPUT";
|
|
3761
|
+
if (isField) {
|
|
3762
|
+
const current = typeof composer.value === "string" ? composer.value : "";
|
|
3763
|
+
setValue(composer, current.trim() === "" ? text2 : current.replace(/\s*$/, "") + "\n\n" + text2);
|
|
3764
|
+
} else {
|
|
3765
|
+
if (composer.focus !== void 0) composer.focus();
|
|
3766
|
+
const editing = globalThis;
|
|
3767
|
+
const escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3768
|
+
const insertHtml = () => {
|
|
3769
|
+
const command = editing.document?.execCommand;
|
|
3770
|
+
if (command === void 0 || editing.document === void 0) return false;
|
|
3771
|
+
const html = text2.split(String.fromCharCode(10)).map(escapeHtml).join("<br>");
|
|
3772
|
+
return command.call(editing.document, "insertHTML", false, html) === true;
|
|
3773
|
+
};
|
|
3774
|
+
const insertLines = () => {
|
|
3775
|
+
const command = editing.document?.execCommand;
|
|
3776
|
+
if (command === void 0 || editing.document === void 0) return false;
|
|
3777
|
+
const lines = text2.split(String.fromCharCode(10));
|
|
3778
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3779
|
+
if (index > 0 && command.call(editing.document, "insertParagraph", false, "") !== true) return false;
|
|
3780
|
+
if (lines[index] !== "" && command.call(editing.document, "insertText", false, lines[index]) !== true) return false;
|
|
3781
|
+
}
|
|
3782
|
+
return true;
|
|
3783
|
+
};
|
|
3784
|
+
const inserted = insertHtml() || insertLines();
|
|
3785
|
+
if (!inserted) {
|
|
3786
|
+
const current = typeof composer.textContent === "string" ? composer.textContent : "";
|
|
3787
|
+
composer.textContent = current.trim() === "" ? text2 : current.replace(/\s*$/, "") + "\n\n" + text2;
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
const input = eventOf("input", { bubbles: true });
|
|
3791
|
+
if (input !== void 0 && composer.dispatchEvent !== void 0) composer.dispatchEvent(input);
|
|
3792
|
+
if (composer.focus !== void 0) composer.focus();
|
|
3793
|
+
return "filled";
|
|
3794
|
+
}
|
|
3795
|
+
function submitComposer(doc) {
|
|
3796
|
+
const composer = findComposer(doc);
|
|
3797
|
+
if (composer === void 0 || composer.dispatchEvent === void 0) return false;
|
|
3798
|
+
const scope = globalThis;
|
|
3799
|
+
if (scope.KeyboardEvent === void 0) return false;
|
|
3800
|
+
try {
|
|
3801
|
+
composer.dispatchEvent(new scope.KeyboardEvent("keydown", { key: "Enter", code: "Enter", bubbles: true, cancelable: true }));
|
|
3802
|
+
return true;
|
|
3803
|
+
} catch {
|
|
3804
|
+
return false;
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
function composerRect(doc) {
|
|
3808
|
+
const composer = findComposer(doc);
|
|
3809
|
+
const inner = composer?.querySelector?.('textarea, [contenteditable="true"]');
|
|
3810
|
+
const surface = inner !== null && inner !== void 0 && typeof inner === "object" ? inner : composer;
|
|
3811
|
+
const measure2 = surface?.getBoundingClientRect;
|
|
3812
|
+
if (surface === void 0 || typeof measure2 !== "function") return void 0;
|
|
3813
|
+
try {
|
|
3814
|
+
const rect = measure2.call(surface);
|
|
3815
|
+
return { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
|
|
3816
|
+
} catch {
|
|
3817
|
+
return void 0;
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
function prependBlock(document, annotations, caption) {
|
|
3821
|
+
if (annotations.length === 0) return "";
|
|
3822
|
+
const block = annotationBlock(annotations.map((annotation) => ({ ...annotation, caption })));
|
|
3823
|
+
const composer = findComposer(document);
|
|
3824
|
+
if (composer === void 0) return "\u6CA1\u627E\u5230\u8F93\u5165\u6846\uFF1A\u5148\u628A\u56FE\u8D34\u8FDB\u4F1A\u8BDD\uFF0C\u6216\u624B\u52A8\u590D\u5236\u8FD9\u6BB5\u6587\u5B57";
|
|
3825
|
+
const isField = typeof composer.value === "string" || composer.tagName === "TEXTAREA" || composer.tagName === "INPUT";
|
|
3826
|
+
if (isField) {
|
|
3827
|
+
const current = typeof composer.value === "string" ? composer.value : "";
|
|
3828
|
+
setValue(composer, current.trim() === "" ? block : block + String.fromCharCode(10, 10) + current);
|
|
3829
|
+
} else if (fillComposer(document, block) === "no-composer") {
|
|
3830
|
+
return "\u6CA1\u627E\u5230\u8F93\u5165\u6846\uFF1A\u5148\u628A\u56FE\u8D34\u8FDB\u4F1A\u8BDD\uFF0C\u6216\u624B\u52A8\u590D\u5236\u8FD9\u6BB5\u6587\u5B57";
|
|
3831
|
+
}
|
|
3832
|
+
const input = eventOf("input", { bubbles: true });
|
|
3833
|
+
if (input !== void 0 && composer.dispatchEvent !== void 0) composer.dispatchEvent(input);
|
|
3834
|
+
return "\u5DF2\u653E\u5165\u8F93\u5165\u6846\uFF08" + String(annotations.length) + " \u6761\u56FE\u6CE8\uFF09";
|
|
3835
|
+
}
|
|
3836
|
+
function withoutEmptyQuestion(block) {
|
|
3837
|
+
return block.replace(/\n+Question:\s*$/, "");
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
// src/transcript.ts
|
|
3841
|
+
var TRANSCRIPT_MARKER = "Diagram annotations -";
|
|
3842
|
+
var QUESTION = "Question:";
|
|
3843
|
+
var HANDLED = "data-dme-annotated";
|
|
3844
|
+
var ORIGINAL = "data-dme-annotation-text";
|
|
3845
|
+
var SELECTOR = "div, p, span, li";
|
|
3846
|
+
var BORDER = "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,0.35))";
|
|
3847
|
+
function annotationCountOf(text2) {
|
|
3848
|
+
const found = /Diagram annotations - (\d+)/.exec(text2);
|
|
3849
|
+
return found === null ? null : Number(found[1]);
|
|
3850
|
+
}
|
|
3851
|
+
function itemsOf(text2) {
|
|
3852
|
+
const items = [];
|
|
3853
|
+
for (const raw of text2.split(String.fromCharCode(10))) {
|
|
3854
|
+
const line = raw.trim();
|
|
3855
|
+
const head = /^Diagram note \[(\d+)\] node (.+)$/.exec(line);
|
|
3856
|
+
if (head !== null) {
|
|
3857
|
+
const rest = head[2];
|
|
3858
|
+
const dash2 = rest.lastIndexOf(" - ");
|
|
3859
|
+
items.push({ index: Number(head[1]), node: dash2 === -1 ? rest : rest.slice(0, dash2), note: dash2 === -1 ? "" : rest.slice(dash2 + 3) });
|
|
3860
|
+
continue;
|
|
3861
|
+
}
|
|
3862
|
+
const note = /^Note: (.*)$/.exec(line);
|
|
3863
|
+
if (note !== null && items.length > 0) items[items.length - 1].note = note[1];
|
|
3864
|
+
}
|
|
3865
|
+
return items;
|
|
3866
|
+
}
|
|
3867
|
+
function textNodesOf(root) {
|
|
3868
|
+
const out = [];
|
|
3869
|
+
const visit = (node) => {
|
|
3870
|
+
const children = node.childNodes;
|
|
3871
|
+
if (children === void 0 || children === null || children.length === 0) {
|
|
3872
|
+
if (typeof node.nodeValue === "string") out.push(node);
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
3875
|
+
for (let index = 0; index < children.length; index += 1) visit(children[index]);
|
|
3876
|
+
};
|
|
3877
|
+
visit(root);
|
|
3878
|
+
return out;
|
|
3879
|
+
}
|
|
3880
|
+
function cutTextNodes(nodes, cut2) {
|
|
3881
|
+
let position = 0;
|
|
3882
|
+
let done = false;
|
|
3883
|
+
for (const node of nodes) {
|
|
3884
|
+
const value = typeof node.nodeValue === "string" ? node.nodeValue : "";
|
|
3885
|
+
const length = value.length;
|
|
3886
|
+
if (done) {
|
|
3887
|
+
position += length;
|
|
3888
|
+
continue;
|
|
3889
|
+
}
|
|
3890
|
+
if (cut2 <= position + length) {
|
|
3891
|
+
node.nodeValue = value.slice(cut2 - position).replace(/^\s+/, "");
|
|
3892
|
+
done = true;
|
|
3893
|
+
} else {
|
|
3894
|
+
node.nodeValue = "";
|
|
3895
|
+
}
|
|
3896
|
+
position += length;
|
|
3897
|
+
}
|
|
3898
|
+
return done;
|
|
3899
|
+
}
|
|
3900
|
+
function isForeign(node) {
|
|
3901
|
+
for (const name of ["data-annotation-for-dsh", "data-annotation-overlay", "data-annotation-chip", "data-annotation-tip-layer"]) {
|
|
3902
|
+
const value = node.getAttribute?.(name);
|
|
3903
|
+
if (value !== void 0 && value !== null) return true;
|
|
3904
|
+
}
|
|
3905
|
+
return false;
|
|
3906
|
+
}
|
|
3907
|
+
function holdsAnotherBlock(node) {
|
|
3908
|
+
const inner = node.querySelectorAll?.(SELECTOR);
|
|
3909
|
+
if (inner === void 0 || inner === null) return false;
|
|
3910
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
3911
|
+
const text2 = inner[index].textContent;
|
|
3912
|
+
if (typeof text2 === "string" && text2.indexOf(TRANSCRIPT_MARKER) !== -1) return true;
|
|
3913
|
+
}
|
|
3914
|
+
return false;
|
|
3915
|
+
}
|
|
3916
|
+
function cardFor(doc, items, title, full) {
|
|
3917
|
+
const card = doc.createElement?.("div");
|
|
3918
|
+
if (card === void 0) return void 0;
|
|
3919
|
+
styleOf(card, {
|
|
3920
|
+
position: "fixed",
|
|
3921
|
+
zIndex: "40",
|
|
3922
|
+
display: "none",
|
|
3923
|
+
maxWidth: "360px",
|
|
3924
|
+
padding: "8px 10px",
|
|
3925
|
+
borderRadius: "10px",
|
|
3926
|
+
background: "var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-layer-1, Canvas))",
|
|
3927
|
+
border: BORDER,
|
|
3928
|
+
boxShadow: "0 10px 30px rgba(0,0,0,0.28)",
|
|
3929
|
+
color: "var(--dsw-alias-label-primary, CanvasText)",
|
|
3930
|
+
font: "12px system-ui, sans-serif"
|
|
3931
|
+
});
|
|
3932
|
+
const head = doc.createElement?.("div");
|
|
3933
|
+
if (head !== void 0) {
|
|
3934
|
+
head.textContent = title;
|
|
3935
|
+
styleOf(head, {
|
|
3936
|
+
color: "var(--dsw-alias-label-secondary, GrayText)",
|
|
3937
|
+
paddingBottom: "6px",
|
|
3938
|
+
marginBottom: "4px",
|
|
3939
|
+
borderBottom: BORDER,
|
|
3940
|
+
fontSize: "11px"
|
|
3941
|
+
});
|
|
3942
|
+
card.appendChild?.(head);
|
|
3943
|
+
}
|
|
3944
|
+
for (const item of items) {
|
|
3945
|
+
const row = doc.createElement?.("div");
|
|
3946
|
+
if (row === void 0) continue;
|
|
3947
|
+
styleOf(row, { display: "flex", gap: "6px", alignItems: "flex-start", padding: "3px 0" });
|
|
3948
|
+
const badge = doc.createElement?.("span");
|
|
3949
|
+
if (badge !== void 0) {
|
|
3950
|
+
badge.textContent = String(item.index);
|
|
3951
|
+
styleOf(badge, {
|
|
3952
|
+
flex: "0 0 auto",
|
|
3953
|
+
width: "16px",
|
|
3954
|
+
height: "16px",
|
|
3955
|
+
lineHeight: "16px",
|
|
3956
|
+
textAlign: "center",
|
|
3957
|
+
borderRadius: "8px",
|
|
3958
|
+
fontSize: "10px",
|
|
3959
|
+
background: "var(--dsw-alias-brand-primary, #2563eb)",
|
|
3960
|
+
color: "#fff"
|
|
3961
|
+
});
|
|
3962
|
+
row.appendChild?.(badge);
|
|
3963
|
+
}
|
|
3964
|
+
const body = doc.createElement?.("div");
|
|
3965
|
+
if (body !== void 0) {
|
|
3966
|
+
body.textContent = item.node + (item.note === "" ? "" : String.fromCharCode(10) + item.note);
|
|
3967
|
+
styleOf(body, { flex: "1 1 auto", whiteSpace: "pre-wrap", wordBreak: "break-word" });
|
|
3968
|
+
row.appendChild?.(body);
|
|
3969
|
+
}
|
|
3970
|
+
card.appendChild?.(row);
|
|
3971
|
+
}
|
|
3972
|
+
const more = doc.createElement?.("div");
|
|
3973
|
+
if (more !== void 0) {
|
|
3974
|
+
more.textContent = full;
|
|
3975
|
+
styleOf(more, {
|
|
3976
|
+
display: "none",
|
|
3977
|
+
marginTop: "6px",
|
|
3978
|
+
paddingTop: "6px",
|
|
3979
|
+
borderTop: BORDER,
|
|
3980
|
+
fontSize: "11px",
|
|
3981
|
+
whiteSpace: "pre-wrap",
|
|
3982
|
+
color: "var(--dsw-alias-label-secondary, GrayText)",
|
|
3983
|
+
maxHeight: "40vh",
|
|
3984
|
+
overflow: "auto"
|
|
3985
|
+
});
|
|
3986
|
+
card.appendChild?.(more);
|
|
3987
|
+
}
|
|
3988
|
+
return card;
|
|
3989
|
+
}
|
|
3990
|
+
function styleOf(node, map) {
|
|
3991
|
+
const decl = node.style;
|
|
3992
|
+
if (typeof decl?.setProperty === "function") {
|
|
3993
|
+
const target = decl;
|
|
3994
|
+
for (const key of Object.keys(map)) {
|
|
3995
|
+
const value = map[key];
|
|
3996
|
+
if (value === void 0) continue;
|
|
3997
|
+
target.setProperty(key.replace(/[A-Z]/g, (letter) => "-" + letter.toLowerCase()), value);
|
|
3998
|
+
}
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
node.style = map;
|
|
4002
|
+
}
|
|
4003
|
+
function chipFor(doc, label, title, text2) {
|
|
4004
|
+
const chip = doc.createElement?.("div");
|
|
4005
|
+
if (chip === void 0) return void 0;
|
|
4006
|
+
chip.textContent = label;
|
|
4007
|
+
chip.setAttribute?.("role", "button");
|
|
4008
|
+
styleOf(chip, {
|
|
4009
|
+
display: "inline-flex",
|
|
4010
|
+
alignItems: "center",
|
|
4011
|
+
height: "20px",
|
|
4012
|
+
padding: "0 8px",
|
|
4013
|
+
margin: "4px 0 0",
|
|
4014
|
+
fontSize: "11px",
|
|
4015
|
+
lineHeight: "20px",
|
|
4016
|
+
borderRadius: "10px",
|
|
4017
|
+
verticalAlign: "middle",
|
|
4018
|
+
userSelect: "none",
|
|
4019
|
+
border: BORDER,
|
|
4020
|
+
background: "var(--dsw-alias-bg-layer-2, rgba(127,127,127,0.10))",
|
|
4021
|
+
color: "var(--dsw-alias-label-secondary, GrayText)",
|
|
4022
|
+
cursor: "pointer",
|
|
4023
|
+
maxWidth: "100%"
|
|
4024
|
+
});
|
|
4025
|
+
const card = cardFor(doc, itemsOf(text2), title, text2);
|
|
4026
|
+
if (card === void 0) return chip;
|
|
4027
|
+
const place2 = () => {
|
|
4028
|
+
const box = chip.getBoundingClientRect?.();
|
|
4029
|
+
styleOf(card, box === void 0 ? { display: "block" } : { display: "block", left: String(Math.max(8, box.left)) + "px", top: String(box.top + box.height + 6) + "px" });
|
|
4030
|
+
};
|
|
4031
|
+
const hide = () => {
|
|
4032
|
+
styleOf(card, { display: "none" });
|
|
4033
|
+
};
|
|
4034
|
+
chip.addEventListener?.("mouseenter", place2);
|
|
4035
|
+
chip.addEventListener?.("mouseleave", hide);
|
|
4036
|
+
chip.addEventListener?.("click", () => {
|
|
4037
|
+
if ((card.style ?? {}).display === "block") {
|
|
4038
|
+
hide();
|
|
4039
|
+
} else {
|
|
4040
|
+
place2();
|
|
4041
|
+
}
|
|
4042
|
+
});
|
|
4043
|
+
chip.appendChild?.(card);
|
|
4044
|
+
return chip;
|
|
4045
|
+
}
|
|
4046
|
+
function decorateTranscript(doc, label = "Notes", title = "Notes in this message") {
|
|
4047
|
+
if (doc === void 0 || typeof doc.querySelectorAll !== "function") return 0;
|
|
4048
|
+
let nodes;
|
|
4049
|
+
try {
|
|
4050
|
+
nodes = doc.querySelectorAll(SELECTOR);
|
|
4051
|
+
} catch {
|
|
4052
|
+
return 0;
|
|
4053
|
+
}
|
|
4054
|
+
let folded = 0;
|
|
4055
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
4056
|
+
const element = nodes[index];
|
|
4057
|
+
const text2 = typeof element.textContent === "string" ? element.textContent : "";
|
|
4058
|
+
if (!text2.includes(TRANSCRIPT_MARKER)) continue;
|
|
4059
|
+
if (element.getAttribute?.(HANDLED) === "1") continue;
|
|
4060
|
+
if (isForeign(element)) continue;
|
|
4061
|
+
if (holdsAnotherBlock(element)) continue;
|
|
4062
|
+
const texts = textNodesOf(element);
|
|
4063
|
+
if (texts.length === 0) continue;
|
|
4064
|
+
const joined = texts.map((node) => typeof node.nodeValue === "string" ? node.nodeValue : "").join("");
|
|
4065
|
+
if (!joined.includes(TRANSCRIPT_MARKER)) continue;
|
|
4066
|
+
const at = joined.lastIndexOf(QUESTION);
|
|
4067
|
+
const cut2 = at === -1 ? joined.length : at + QUESTION.length;
|
|
4068
|
+
const count = annotationCountOf(joined);
|
|
4069
|
+
const chip = chipFor(doc, label + (count === null ? "" : " \xD7" + String(count)), title, joined);
|
|
4070
|
+
if (chip === void 0) continue;
|
|
4071
|
+
try {
|
|
4072
|
+
if (!cutTextNodes(texts, cut2)) continue;
|
|
4073
|
+
element.setAttribute?.(ORIGINAL, joined);
|
|
4074
|
+
element.setAttribute?.(HANDLED, "1");
|
|
4075
|
+
const parent = element.parentNode;
|
|
4076
|
+
if (parent?.insertBefore !== void 0) parent.insertBefore(chip, element.nextSibling ?? null);
|
|
4077
|
+
else element.appendChild?.(chip);
|
|
4078
|
+
folded += 1;
|
|
4079
|
+
} catch {
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
return folded;
|
|
4083
|
+
}
|
|
4084
|
+
function startTranscriptDecoration(doc, label = "Notes", title = "Notes in this message") {
|
|
4085
|
+
decorateTranscript(doc, label, title);
|
|
4086
|
+
return () => {
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
|
|
3300
4090
|
// src/client.ts
|
|
3301
|
-
var
|
|
4091
|
+
var services;
|
|
4092
|
+
var DRAWING_TOOL = "diagram_render";
|
|
4093
|
+
var TOOL_NAMES = ["model_verify", DRAWING_TOOL];
|
|
3302
4094
|
function themeMode() {
|
|
3303
4095
|
try {
|
|
3304
4096
|
const scope = globalThis;
|
|
@@ -3319,8 +4111,1040 @@ window.__ModuleLoader__.load({
|
|
|
3319
4111
|
}
|
|
3320
4112
|
var WHITEBOARD_ID = "model-everything-diagram";
|
|
3321
4113
|
var WHITEBOARD_SAMPLE = ["graph TD", " a[Idea] --> b{Feasible?}", " b -->|yes| c[Build]", " b -->|no| d[Revise]", " d --> a"].join("\n");
|
|
4114
|
+
function argOf(args, key) {
|
|
4115
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
4116
|
+
const view = args;
|
|
4117
|
+
if (typeof view.value === "function") return view.value(key);
|
|
4118
|
+
return args[key];
|
|
4119
|
+
}
|
|
4120
|
+
var MESSAGES = {
|
|
4121
|
+
note: { en: "Note", zh: "\u6279\u6CE8" },
|
|
4122
|
+
noteOn: { en: "Note: on", zh: "\u6279\u6CE8: \u5F00" },
|
|
4123
|
+
noteHint: { en: "Click a node to mark it", zh: "\u70B9\u4E00\u4E2A\u8282\u70B9\u6807\u8BB0\u5B83" },
|
|
4124
|
+
node: { en: "Node", zh: "\u8282\u70B9" },
|
|
4125
|
+
add: { en: "Add", zh: "\u52A0\u5165" },
|
|
4126
|
+
addAndSend: { en: "Add & send", zh: "\u52A0\u5165\u5E76\u53D1\u9001" },
|
|
4127
|
+
close: { en: "Close", zh: "\u5173\u95ED" },
|
|
4128
|
+
send: { en: "Send", zh: "\u53D1\u9001" },
|
|
4129
|
+
clear: { en: "Clear", zh: "\u6E05\u7A7A" },
|
|
4130
|
+
remove: { en: "Remove", zh: "\u5220\u9664" },
|
|
4131
|
+
markedOnly: { en: "(marked only)", zh: "(\u53EA\u6807\u8BB0)" },
|
|
4132
|
+
pending: { en: "Notes", zh: "\u56FE\u6CE8" },
|
|
4133
|
+
fold: { en: "hide", zh: "\u6536\u8D77" },
|
|
4134
|
+
carried: { en: "Notes in this message", zh: "\u672C\u6D88\u606F\u643A\u5E26\u56FE\u6CE8" },
|
|
4135
|
+
zoomOut: { en: "Zoom out", zh: "\u7F29\u5C0F" },
|
|
4136
|
+
zoomIn: { en: "Zoom in", zh: "\u653E\u5927" },
|
|
4137
|
+
actualSize: { en: "Actual size", zh: "\u5B9E\u9645\u5927\u5C0F" },
|
|
4138
|
+
full: { en: "\u2922 Full", zh: "\u2922 \u5168\u5C4F" },
|
|
4139
|
+
exitFull: { en: "\u2921 Exit", zh: "\u2921 \u9000\u51FA" },
|
|
4140
|
+
diagram: { en: "Diagram", zh: "\u56FE" },
|
|
4141
|
+
fitOn: { en: "fit: on", zh: "fit: on" },
|
|
4142
|
+
fitOff: { en: "fit: off", zh: "fit: off" },
|
|
4143
|
+
scheme: { en: "Colours", zh: "\u914D\u8272" },
|
|
4144
|
+
unfold: { en: "unfold", zh: "\u5C55\u5F00" },
|
|
4145
|
+
placeholder: { en: "A note about this node\u2026 (Ctrl+Enter adds and sends)", zh: "\u6279\u6CE8\u2026\uFF08Ctrl+Enter \u52A0\u5165\u5E76\u53D1\u9001\uFF09" }
|
|
4146
|
+
};
|
|
4147
|
+
function languageOf() {
|
|
4148
|
+
const scope = globalThis;
|
|
4149
|
+
const asked = (scope.document?.documentElement?.lang ?? scope.navigator?.language ?? "en").toLowerCase();
|
|
4150
|
+
return asked.startsWith("zh") ? "zh" : "en";
|
|
4151
|
+
}
|
|
4152
|
+
function t(key) {
|
|
4153
|
+
return MESSAGES[key][languageOf()];
|
|
4154
|
+
}
|
|
4155
|
+
var TOKEN = {
|
|
4156
|
+
surface: "var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-layer-1, Canvas))",
|
|
4157
|
+
border: "var(--dsw-alias-border-l1, rgba(127,127,127,0.35))",
|
|
4158
|
+
text: "var(--dsw-alias-label-primary, CanvasText)",
|
|
4159
|
+
dim: "var(--dsw-alias-label-secondary, GrayText)",
|
|
4160
|
+
accent: "var(--dsw-alias-brand-primary, #2563eb)"
|
|
4161
|
+
};
|
|
4162
|
+
function annotationPanel(h, state, actions, options = {}) {
|
|
4163
|
+
const button = (label, onClick, accent = false) => h("button", { type: "button", onClick, style: { fontSize: "12px", padding: "2px 8px", borderRadius: "6px", border: "1px solid " + TOKEN.border, background: "transparent", color: accent ? TOKEN.accent : TOKEN.text, cursor: "pointer" } }, label);
|
|
4164
|
+
const editor = state.picked === null ? null : h(
|
|
4165
|
+
"div",
|
|
4166
|
+
{
|
|
4167
|
+
style: {
|
|
4168
|
+
...options.floating === false ? {} : { position: "absolute", left: (state.anchor?.left ?? 10) + "px", top: (state.anchor?.top ?? 10) + "px", zIndex: 20 },
|
|
4169
|
+
width: "min(260px, calc(100% - 16px))",
|
|
4170
|
+
boxSizing: "border-box",
|
|
4171
|
+
display: "flex",
|
|
4172
|
+
flexDirection: "column",
|
|
4173
|
+
gap: "6px",
|
|
4174
|
+
padding: "8px",
|
|
4175
|
+
borderRadius: "10px",
|
|
4176
|
+
background: TOKEN.surface,
|
|
4177
|
+
border: "1px solid " + TOKEN.border,
|
|
4178
|
+
color: TOKEN.text,
|
|
4179
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.28)"
|
|
4180
|
+
}
|
|
4181
|
+
},
|
|
4182
|
+
h("div", { style: { fontSize: "12px", color: TOKEN.dim } }, t("node") + " " + state.picked.id),
|
|
4183
|
+
h("textarea", {
|
|
4184
|
+
"data-dme-own": "1",
|
|
4185
|
+
value: state.note,
|
|
4186
|
+
rows: 3,
|
|
4187
|
+
placeholder: t("placeholder"),
|
|
4188
|
+
style: { width: "100%", boxSizing: "border-box", font: "12px system-ui, sans-serif", padding: "6px", borderRadius: "6px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text, resize: "vertical" },
|
|
4189
|
+
onInput: (event) => actions.onNote(String(event?.target?.value ?? "")),
|
|
4190
|
+
onKeyDown: (event) => {
|
|
4191
|
+
if (event?.key === "Enter" && (event?.ctrlKey === true || event?.metaKey === true)) {
|
|
4192
|
+
event.preventDefault?.();
|
|
4193
|
+
actions.onAdd();
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
}),
|
|
4197
|
+
h("div", { style: { display: "flex", gap: "6px", justifyContent: "flex-end" } }, button("\u5173\u95ED", actions.onCancel), button("\u52A0\u5165", actions.onAdd))
|
|
4198
|
+
);
|
|
4199
|
+
const docked = options.floating !== false && options.document !== void 0 ? composerRect(options.document) : void 0;
|
|
4200
|
+
const chip = state.pending.length === 0 || options.dockChip === false ? null : h(
|
|
4201
|
+
"div",
|
|
4202
|
+
{
|
|
4203
|
+
style: {
|
|
4204
|
+
position: "fixed",
|
|
4205
|
+
zIndex: 30,
|
|
4206
|
+
left: (docked === void 0 ? 16 : docked.left) + "px",
|
|
4207
|
+
transform: "translate(" + String(state.dockFix?.dx ?? 0) + "px, " + String(state.dockFix?.dy ?? 0) + "px)",
|
|
4208
|
+
// Anchored to the composer's own top edge, in the gap above it: the count is in sight while the
|
|
4209
|
+
// reader is typing, and never covers the box they are typing into.
|
|
4210
|
+
top: (docked === void 0 ? 16 : Math.max(8, docked.top - 46)) + "px",
|
|
4211
|
+
// Hugs its two buttons. A fixed width turned the control into a wide empty bar across the composer.
|
|
4212
|
+
display: "flex",
|
|
4213
|
+
flexDirection: "column",
|
|
4214
|
+
gap: "6px",
|
|
4215
|
+
padding: "6px 10px",
|
|
4216
|
+
borderRadius: "12px",
|
|
4217
|
+
width: "max-content",
|
|
4218
|
+
// The composer's own surface and hairline: the control reads as part of that box, not as a dialog.
|
|
4219
|
+
background: "var(--dsw-alias-bg-layer-1, " + TOKEN.surface + ")",
|
|
4220
|
+
border: "1px solid var(--dsw-alias-border-l2, " + TOKEN.border + ")",
|
|
4221
|
+
color: TOKEN.text,
|
|
4222
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.22)",
|
|
4223
|
+
font: "12px system-ui, sans-serif"
|
|
4224
|
+
}
|
|
4225
|
+
},
|
|
4226
|
+
h(
|
|
4227
|
+
"div",
|
|
4228
|
+
{ "data-dme-dock": "1", style: { display: "flex", alignItems: "center", gap: "6px" } },
|
|
4229
|
+
h("button", { type: "button", title: "\u5C55\u5F00\uFF0C\u9010\u4E2A\u5220\u9664", onClick: () => actions.onToggle(), style: { fontSize: "12px", padding: "1px 6px", borderRadius: "6px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text, cursor: "pointer" } }, t("pending") + " \xD7" + String(state.pending.length)),
|
|
4230
|
+
button(t("send"), actions.onSend, true)
|
|
4231
|
+
),
|
|
4232
|
+
state.open ? h(
|
|
4233
|
+
"div",
|
|
4234
|
+
{ style: { display: "flex", flexDirection: "column", gap: "2px" } },
|
|
4235
|
+
...state.pending.map(
|
|
4236
|
+
(item, index) => h(
|
|
4237
|
+
"div",
|
|
4238
|
+
{ style: { display: "flex", alignItems: "center", gap: "6px" } },
|
|
4239
|
+
h("span", { style: { color: TOKEN.dim, fontSize: "11px" } }, item.nodeId),
|
|
4240
|
+
h("span", { style: { flex: "1 1 auto", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, item.note === "" ? t("markedOnly") : item.note),
|
|
4241
|
+
button(t("remove"), () => actions.onRemove(index))
|
|
4242
|
+
)
|
|
4243
|
+
)
|
|
4244
|
+
) : null
|
|
4245
|
+
);
|
|
4246
|
+
const mode = state.picked === null && state.pending.length === 0 ? h("div", { style: { fontSize: "11px", color: TOKEN.dim, padding: "2px 4px" } }, t("noteHint")) : null;
|
|
4247
|
+
return h(
|
|
4248
|
+
"div",
|
|
4249
|
+
{ style: { display: "flex", flexDirection: "column", gap: "2px", color: TOKEN.text } },
|
|
4250
|
+
mode,
|
|
4251
|
+
chip,
|
|
4252
|
+
editor,
|
|
4253
|
+
state.status === "" ? null : h("div", { style: { fontSize: "11px", color: TOKEN.dim, padding: "0 4px" } }, state.status)
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
function sourceOf(block) {
|
|
4257
|
+
const record2 = (value) => value !== null && typeof value === "object" ? value : void 0;
|
|
4258
|
+
const asText = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
4259
|
+
const fromJson = (value) => {
|
|
4260
|
+
const raw = asText(value);
|
|
4261
|
+
if (raw === void 0) return void 0;
|
|
4262
|
+
try {
|
|
4263
|
+
return asText(record2(JSON.parse(raw))?.source);
|
|
4264
|
+
} catch {
|
|
4265
|
+
return void 0;
|
|
4266
|
+
}
|
|
4267
|
+
};
|
|
4268
|
+
const frame = record2(block);
|
|
4269
|
+
if (frame === void 0) return void 0;
|
|
4270
|
+
const fromMeta = asText(record2(frame.meta)?.source);
|
|
4271
|
+
if (fromMeta !== void 0) return fromMeta;
|
|
4272
|
+
const args = record2(frame.args);
|
|
4273
|
+
if (args !== void 0 && typeof args.value === "function") {
|
|
4274
|
+
const lazy = asText(args.value("source"));
|
|
4275
|
+
if (lazy !== void 0) return lazy;
|
|
4276
|
+
}
|
|
4277
|
+
const plain = asText(args?.source) ?? fromJson(frame.args);
|
|
4278
|
+
if (plain !== void 0) return plain;
|
|
4279
|
+
const call = record2(frame.call);
|
|
4280
|
+
const fromCall = asText(call?.source) ?? fromJson(call?.input) ?? fromJson(call?.arguments);
|
|
4281
|
+
if (fromCall !== void 0) return fromCall;
|
|
4282
|
+
return asText(record2(frame.meta)?.source);
|
|
4283
|
+
}
|
|
4284
|
+
function drawingCard(React, props, overrides) {
|
|
4285
|
+
const block = props.block ?? {};
|
|
4286
|
+
const source = sourceOf(props.block) ?? argOf(block.args, "source");
|
|
4287
|
+
const asked = argOf(block.args, "scheme");
|
|
4288
|
+
const scheme = schemeNames().includes(asked) ? asked : "default";
|
|
4289
|
+
const payload = props.block?.result;
|
|
4290
|
+
const returned = payload !== null && typeof payload === "object" ? payload.svg : void 0;
|
|
4291
|
+
const markup = typeof returned === "string" && returned !== "" ? returned : null;
|
|
4292
|
+
const hasDrawing = markup !== null || typeof source === "string" && source.trim() !== "";
|
|
4293
|
+
const useState = React.useState;
|
|
4294
|
+
if (useState === void 0) return cardSurface(React, source, markup, scheme, overrides, null, null, null, null);
|
|
4295
|
+
const [annotate, setAnnotate] = useState(false);
|
|
4296
|
+
const [picked, setPicked] = useState(null);
|
|
4297
|
+
const [note, setNote] = useState("");
|
|
4298
|
+
const [status, setStatus] = useState("");
|
|
4299
|
+
const document = globalThis.document;
|
|
4300
|
+
const caption = typeof source === "string" ? source.split(String.fromCharCode(10)).map((line) => line.trim()).find((line) => line !== "") ?? "" : "";
|
|
4301
|
+
const [pending, setPending] = useState([]);
|
|
4302
|
+
const [open, setOpen] = useState(false);
|
|
4303
|
+
const [sent, setSent] = useState("");
|
|
4304
|
+
const [report, setReport] = useState("");
|
|
4305
|
+
const [, setDockTick] = useState(0);
|
|
4306
|
+
const [dockFix, setDockFix] = useState({ dx: 0, dy: 0 });
|
|
4307
|
+
React.useEffect?.(() => {
|
|
4308
|
+
const doc = globalThis.document;
|
|
4309
|
+
const composer = findComposer(doc);
|
|
4310
|
+
const bump = () => setDockTick((n2) => n2 + 1);
|
|
4311
|
+
const scope = globalThis;
|
|
4312
|
+
scope.addEventListener?.("resize", bump);
|
|
4313
|
+
let observer;
|
|
4314
|
+
if (composer !== void 0 && composer !== null && scope.ResizeObserver !== void 0) {
|
|
4315
|
+
try {
|
|
4316
|
+
observer = new scope.ResizeObserver(bump);
|
|
4317
|
+
observer.observe(composer);
|
|
4318
|
+
} catch {
|
|
4319
|
+
observer = void 0;
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
return () => {
|
|
4323
|
+
scope.removeEventListener?.("resize", bump);
|
|
4324
|
+
observer?.disconnect();
|
|
4325
|
+
};
|
|
4326
|
+
}, []);
|
|
4327
|
+
React.useEffect?.(() => {
|
|
4328
|
+
const doc = globalThis.document;
|
|
4329
|
+
const chip = doc?.querySelector?.("[data-dme-dock]");
|
|
4330
|
+
const surface = composerRect(doc);
|
|
4331
|
+
const landed = chip?.getBoundingClientRect?.();
|
|
4332
|
+
if (surface === void 0 || landed === void 0) return;
|
|
4333
|
+
const dx = Math.round(surface.left - landed.left);
|
|
4334
|
+
const dy = Math.round(surface.top - landed.height - 8 - landed.top);
|
|
4335
|
+
if (Math.abs(dx) > 1 || Math.abs(dy) > 1) setDockFix((was) => ({ dx: was.dx + dx, dy: was.dy + dy }));
|
|
4336
|
+
});
|
|
4337
|
+
const [anchor2, setAnchor] = useState(void 0);
|
|
4338
|
+
setPendingBlock(pending.length === 0 ? "" : annotationBlock(pending.map((item) => ({ ...item, caption }))));
|
|
4339
|
+
React.useEffect?.(() => {
|
|
4340
|
+
try {
|
|
4341
|
+
return onMessageSent(() => {
|
|
4342
|
+
setPending([]);
|
|
4343
|
+
setStatus("");
|
|
4344
|
+
});
|
|
4345
|
+
} catch {
|
|
4346
|
+
return void 0;
|
|
4347
|
+
}
|
|
4348
|
+
}, []);
|
|
4349
|
+
const [zoom, setZoom] = useState(1);
|
|
4350
|
+
const [full, setFull] = useState(false);
|
|
4351
|
+
if (!hasDrawing) {
|
|
4352
|
+
const keys = props.block === null || typeof props.block !== "object" ? "none" : Object.keys(props.block).join(",");
|
|
4353
|
+
return React.createElement("div", { style: { fontSize: "12px", opacity: 0.7 } }, "no drawing in this call \xB7 block: " + (keys === "" ? "empty" : keys));
|
|
4354
|
+
}
|
|
4355
|
+
const sendAll = (list) => {
|
|
4356
|
+
if (list.length === 0) return;
|
|
4357
|
+
const text2 = annotationBlock(list.map((item) => ({ ...item, caption })));
|
|
4358
|
+
void (async () => {
|
|
4359
|
+
if (placeBlock(text2)) {
|
|
4360
|
+
const label = "\u56FE\u6CE8 \xD7" + String(list.length);
|
|
4361
|
+
attachBelowBubble(label, "");
|
|
4362
|
+
setSent("\u5DF2\u53D1\u9001 " + label);
|
|
4363
|
+
attachSentChip(label);
|
|
4364
|
+
setStatus("\u5DF2\u653E\u5165\u8F93\u5165\u6846\u5E76\u63D0\u4EA4\uFF08" + String(list.length) + " \u6761\u56FE\u6CE8\uFF09");
|
|
4365
|
+
setPending([]);
|
|
4366
|
+
submitDraft();
|
|
4367
|
+
return;
|
|
4368
|
+
}
|
|
4369
|
+
const said = prependBlock(document, list, caption);
|
|
4370
|
+
setStatus(said);
|
|
4371
|
+
if (!said.startsWith("\u5DF2")) return;
|
|
4372
|
+
setPending([]);
|
|
4373
|
+
submitComposer(document);
|
|
4374
|
+
})();
|
|
4375
|
+
};
|
|
4376
|
+
const onPick = (event) => {
|
|
4377
|
+
if (!annotate) return;
|
|
4378
|
+
const id = event?.target?.closest?.("[data-node-id]")?.getAttribute?.("data-node-id");
|
|
4379
|
+
if (typeof id === "string" && id !== "") {
|
|
4380
|
+
const pointer = event;
|
|
4381
|
+
const frame = pointer.currentTarget?.getBoundingClientRect?.();
|
|
4382
|
+
const node = pointer.target?.closest?.("[data-node-id]")?.getBoundingClientRect?.();
|
|
4383
|
+
if (frame !== void 0 && node !== void 0) {
|
|
4384
|
+
const width = 250;
|
|
4385
|
+
const left = Math.max(4, Math.min(node.left - frame.left + node.width + 10, Math.max(4, frame.width - width)));
|
|
4386
|
+
const top = Math.max(4, Math.min(node.top - frame.top, Math.max(4, frame.height - 40)));
|
|
4387
|
+
setAnchor({ left, top });
|
|
4388
|
+
}
|
|
4389
|
+
setPicked({ id });
|
|
4390
|
+
setStatus("");
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
if (note.trim() === "") setPicked(null);
|
|
4394
|
+
};
|
|
4395
|
+
const toggle = React.createElement(
|
|
4396
|
+
"div",
|
|
4397
|
+
{ style: { display: "flex", gap: "6px", alignItems: "center" } },
|
|
4398
|
+
React.createElement("button", { type: "button", title: "Point at a node and send a note about it", onClick: () => {
|
|
4399
|
+
setAnnotate(!annotate);
|
|
4400
|
+
setStatus("");
|
|
4401
|
+
if (annotate) {
|
|
4402
|
+
setPicked(null);
|
|
4403
|
+
setNote("");
|
|
4404
|
+
}
|
|
4405
|
+
}, style: { fontSize: "11px", lineHeight: "16px", padding: "1px 6px", borderRadius: "5px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text, cursor: "pointer" } }, annotate ? t("noteOn") : t("note")),
|
|
4406
|
+
annotate && picked === null ? React.createElement("span", { style: { fontSize: "11px", opacity: 0.7 } }, "\u70B9\u4E00\u4E2A\u8282\u70B9") : null,
|
|
4407
|
+
React.createElement("button", {
|
|
4408
|
+
type: "button",
|
|
4409
|
+
onClick: () => {
|
|
4410
|
+
try {
|
|
4411
|
+
setReport(domReport());
|
|
4412
|
+
} catch (error) {
|
|
4413
|
+
setReport("report failed: " + String(error?.message ?? error));
|
|
4414
|
+
}
|
|
4415
|
+
},
|
|
4416
|
+
style: { fontSize: "11px", lineHeight: "16px", padding: "1px 6px", borderRadius: "5px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.dim, cursor: "pointer" }
|
|
4417
|
+
}, "\u8BCA\u65AD"),
|
|
4418
|
+
sent === "" ? null : React.createElement("span", {
|
|
4419
|
+
style: { fontSize: "11px", lineHeight: "16px", padding: "1px 6px", borderRadius: "9px", border: "1px solid " + TOKEN.border, color: TOKEN.dim, whiteSpace: "nowrap" }
|
|
4420
|
+
}, sent)
|
|
4421
|
+
);
|
|
4422
|
+
updateDockLayer(pending, open, {
|
|
4423
|
+
onToggle: () => setOpen(!open),
|
|
4424
|
+
onSend: () => sendAll(pending),
|
|
4425
|
+
onRemove: (index) => setPending(pending.filter((_, at) => at !== index))
|
|
4426
|
+
});
|
|
4427
|
+
const panel = annotate ? annotationPanel(
|
|
4428
|
+
(type, options, ...children) => React.createElement(type, options, ...children),
|
|
4429
|
+
{ picked, note, pending, status, anchor: anchor2, open, dockFix },
|
|
4430
|
+
{
|
|
4431
|
+
onNote: setNote,
|
|
4432
|
+
onAdd: () => {
|
|
4433
|
+
if (picked === null) return;
|
|
4434
|
+
const merged = [...pending, { nodeId: picked.id, note }];
|
|
4435
|
+
setPending(merged);
|
|
4436
|
+
setNote("");
|
|
4437
|
+
setPicked(null);
|
|
4438
|
+
setStatus("\u5DF2\u653E\u5165\u8F93\u5165\u6846\uFF0C\u76F4\u63A5\u56DE\u8F66\u5373\u53EF");
|
|
4439
|
+
},
|
|
4440
|
+
onAddAndSend: () => {
|
|
4441
|
+
if (picked === null) return;
|
|
4442
|
+
const merged = [...pending, { nodeId: picked.id, note }];
|
|
4443
|
+
setNote("");
|
|
4444
|
+
setPicked(null);
|
|
4445
|
+
sendAll(merged);
|
|
4446
|
+
},
|
|
4447
|
+
onSend: () => sendAll(pending),
|
|
4448
|
+
onClear: () => {
|
|
4449
|
+
setPending([]);
|
|
4450
|
+
setStatus("");
|
|
4451
|
+
},
|
|
4452
|
+
onCancel: () => {
|
|
4453
|
+
setPicked(null);
|
|
4454
|
+
setNote("");
|
|
4455
|
+
setStatus("");
|
|
4456
|
+
},
|
|
4457
|
+
onToggle: () => setOpen(!open),
|
|
4458
|
+
onRemove: (index) => setPending(pending.filter((_, at) => at !== index))
|
|
4459
|
+
},
|
|
4460
|
+
// The count is the body-level layer; this panel keeps the editor, which still needs its anchor.
|
|
4461
|
+
{ document, dockChip: false }
|
|
4462
|
+
) : null;
|
|
4463
|
+
const reportPanel = report === "" ? null : React.createElement("pre", {
|
|
4464
|
+
style: { margin: "6px 0 0", padding: "8px", borderRadius: "8px", border: "1px solid " + TOKEN.border, background: "rgba(127,127,127,0.08)", font: "11px ui-monospace, monospace", whiteSpace: "pre-wrap", userSelect: "text", maxHeight: "220px", overflow: "auto" }
|
|
4465
|
+
}, report);
|
|
4466
|
+
return cardSurface(React, source, markup, scheme, overrides, onPick, toggle, panel, reportPanel, { zoom: Number(zoom), full: Boolean(full), onZoom: (next) => setZoom(next), onFull: (next) => setFull(next) });
|
|
4467
|
+
}
|
|
4468
|
+
function cardSurface(React, source, returned, scheme, overrides, onClick, toggle, panel, report, view = { zoom: 1, full: false, onZoom: () => {
|
|
4469
|
+
}, onFull: () => {
|
|
4470
|
+
} }) {
|
|
4471
|
+
if (returned === null && (typeof source !== "string" || source.trim() === "")) {
|
|
4472
|
+
return React.createElement("div", { style: { fontSize: "12px", opacity: 0.7 } }, "no drawing in this call");
|
|
4473
|
+
}
|
|
4474
|
+
const drawn = returned === null ? render({ source: String(source), folded: [] }, overrides, { mode: themeMode(), scheme }) : { markup: returned, refused: [], colours: palette(overrides, { mode: themeMode(), scheme }) };
|
|
4475
|
+
const scaled = drawn.markup.replace("<svg ", '<svg style="max-width: 100%; height: auto; display: block; margin: 0 auto" ');
|
|
4476
|
+
const refused = drawn.refused.length === 0 ? null : React.createElement("pre", { style: { fontSize: "11px", color: "#b7791f", whiteSpace: "pre-wrap", margin: "6px 8px" } }, drawn.refused.join(String.fromCharCode(10)));
|
|
4477
|
+
const chrome = { border: "1px solid " + TOKEN.border, borderRadius: "8px", overflow: "hidden", background: TOKEN.surface, color: TOKEN.text };
|
|
4478
|
+
const button = (label, onPress, title) => React.createElement("button", { type: "button", title, onClick: onPress, style: { fontSize: "11px", lineHeight: "16px", padding: "1px 6px", borderRadius: "5px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text, cursor: "pointer" } }, label);
|
|
4479
|
+
const header = React.createElement(
|
|
4480
|
+
"div",
|
|
4481
|
+
{ style: { display: "flex", gap: "6px", alignItems: "center", padding: "3px 8px", borderBottom: "1px solid " + TOKEN.border, fontSize: "12px", color: TOKEN.dim } },
|
|
4482
|
+
React.createElement("span", null, t("diagram")),
|
|
4483
|
+
React.createElement("span", { style: { opacity: 0.7 } }, typeof source === "string" ? source.split(String.fromCharCode(10)).map((line) => line.trim()).find((line) => line !== "") ?? "" : ""),
|
|
4484
|
+
React.createElement(
|
|
4485
|
+
"span",
|
|
4486
|
+
{ style: { marginLeft: "auto", display: "flex", gap: "4px", alignItems: "center" } },
|
|
4487
|
+
button("\u2212", () => view.onZoom(Math.max(0.5, Math.round((view.zoom - 0.25) * 100) / 100)), t("zoomOut")),
|
|
4488
|
+
button(Math.round(view.zoom * 100) + "%", () => view.onZoom(1), t("actualSize")),
|
|
4489
|
+
button("+", () => view.onZoom(Math.min(3, Math.round((view.zoom + 0.25) * 100) / 100)), t("zoomIn")),
|
|
4490
|
+
button(view.full ? t("exitFull") : t("full"), () => view.onFull(!view.full), t("full")),
|
|
4491
|
+
toggle
|
|
4492
|
+
)
|
|
4493
|
+
);
|
|
4494
|
+
const drawing = React.createElement(
|
|
4495
|
+
"div",
|
|
4496
|
+
{
|
|
4497
|
+
// CSS zoom, not a transform: it reflows, so the scroll area grows with the picture and nothing is clipped.
|
|
4498
|
+
style: { zoom: view.zoom, cursor: onClick === null ? void 0 : "crosshair" },
|
|
4499
|
+
onClick: onClick === null ? void 0 : onClick,
|
|
4500
|
+
dangerouslySetInnerHTML: { __html: scaled }
|
|
4501
|
+
}
|
|
4502
|
+
);
|
|
4503
|
+
const body = React.createElement(
|
|
4504
|
+
"div",
|
|
4505
|
+
{ style: { position: "relative", background: drawn.colours.background, padding: "6px", maxHeight: view.full ? "none" : "260px", overflow: "auto", flex: "1 1 auto", minHeight: "0" } },
|
|
4506
|
+
drawing,
|
|
4507
|
+
panel,
|
|
4508
|
+
report
|
|
4509
|
+
);
|
|
4510
|
+
const frame = view.full ? { position: "fixed", inset: "16px", zIndex: 60, display: "flex", flexDirection: "column", ...chrome, boxShadow: "0 12px 40px rgba(0,0,0,0.35)" } : { display: "flex", flexDirection: "column", ...chrome };
|
|
4511
|
+
const children = [header, body, refused].filter((child) => child !== null);
|
|
4512
|
+
return React.createElement("div", { style: frame }, ...children);
|
|
4513
|
+
}
|
|
4514
|
+
function currentSessionId() {
|
|
4515
|
+
const sessions = services?.get?.("sessions");
|
|
4516
|
+
const snapshot = sessions?.list?.getSnapshot?.();
|
|
4517
|
+
if (typeof snapshot?.current === "string" && snapshot.current !== "") return snapshot.current;
|
|
4518
|
+
const workspace = services?.get?.("uiWorkspace");
|
|
4519
|
+
const main = workspace?.mainReference?.sessionId;
|
|
4520
|
+
if (typeof main === "string" && main !== "") return main;
|
|
4521
|
+
const picked = workspace?.selection?.getSnapshot?.()?.sessionId;
|
|
4522
|
+
if (typeof picked === "string" && picked !== "") return picked;
|
|
4523
|
+
const storage = globalThis.localStorage;
|
|
4524
|
+
const raw = storage?.getItem?.("dsh.sessions.current");
|
|
4525
|
+
if (typeof raw === "string" && raw !== "") {
|
|
4526
|
+
try {
|
|
4527
|
+
const parsed = JSON.parse(raw);
|
|
4528
|
+
if (typeof parsed?.sessionId === "string" && parsed.sessionId !== "") return parsed.sessionId;
|
|
4529
|
+
} catch {
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
return void 0;
|
|
4533
|
+
}
|
|
4534
|
+
function draftShell() {
|
|
4535
|
+
const conversation = services?.get?.("conversation");
|
|
4536
|
+
const sessions = services?.get?.("sessions");
|
|
4537
|
+
if (conversation?.input?.for === void 0 || sessions?.scope === void 0) return void 0;
|
|
4538
|
+
const id = currentSessionId();
|
|
4539
|
+
if (id === void 0) return void 0;
|
|
4540
|
+
try {
|
|
4541
|
+
return conversation.input.for(sessions.scope(id));
|
|
4542
|
+
} catch {
|
|
4543
|
+
return void 0;
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
var placedText = "";
|
|
4547
|
+
function replaceBlock(text2) {
|
|
4548
|
+
const shell = draftShell();
|
|
4549
|
+
if (shell === void 0 || typeof shell.setDraft !== "function") return false;
|
|
4550
|
+
const snapshot = shell.state?.getSnapshot?.();
|
|
4551
|
+
const current = typeof snapshot?.draft === "string" ? String(snapshot.draft) : "";
|
|
4552
|
+
let rest = current;
|
|
4553
|
+
if (placedText !== "" && rest.includes(placedText)) rest = rest.split(placedText).join("");
|
|
4554
|
+
rest = rest.replace(/^[\r\n]+/, "").replace(/\s+$/, "");
|
|
4555
|
+
const next = text2 === "" ? rest : rest === "" ? text2 : rest + String.fromCharCode(10, 10) + text2;
|
|
4556
|
+
try {
|
|
4557
|
+
shell.setDraft(next);
|
|
4558
|
+
placedText = text2;
|
|
4559
|
+
return true;
|
|
4560
|
+
} catch {
|
|
4561
|
+
return false;
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
var pendingBlock = "";
|
|
4565
|
+
var enterWatch = null;
|
|
4566
|
+
function setPendingBlock(text2) {
|
|
4567
|
+
pendingBlock = text2;
|
|
4568
|
+
installEnterCapture();
|
|
4569
|
+
}
|
|
4570
|
+
function findBlockElement(doc) {
|
|
4571
|
+
const all = Array.from(doc.querySelectorAll("div, p, span"));
|
|
4572
|
+
const hits = all.filter((node) => String(node.textContent ?? "").indexOf("Diagram annotations -") !== -1);
|
|
4573
|
+
let best;
|
|
4574
|
+
let bestArea = Number.POSITIVE_INFINITY;
|
|
4575
|
+
for (const node of hits) {
|
|
4576
|
+
const box = node.getBoundingClientRect?.();
|
|
4577
|
+
const area = box === void 0 || box === null ? Number.POSITIVE_INFINITY : box.width * box.height;
|
|
4578
|
+
if (area > 0 && area < bestArea) {
|
|
4579
|
+
bestArea = area;
|
|
4580
|
+
best = node;
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
return best;
|
|
4584
|
+
}
|
|
4585
|
+
function insertChipAfter(doc, element, label) {
|
|
4586
|
+
try {
|
|
4587
|
+
const existing = Array.from(doc.querySelectorAll("[data-dme-chip]"));
|
|
4588
|
+
if (existing.some((old) => String(old.textContent ?? "") === label)) return;
|
|
4589
|
+
for (const old of existing) old.remove?.();
|
|
4590
|
+
const chip = doc.createElement("span");
|
|
4591
|
+
chip.setAttribute("data-dme-own", "1");
|
|
4592
|
+
chip.setAttribute("data-dme-chip", "1");
|
|
4593
|
+
chip.textContent = label;
|
|
4594
|
+
chip.style.cssText = "position:fixed;z-index:1140;display:inline-block;padding:1px 8px;border-radius:9px;font:11px system-ui;border:1px solid var(--dsw-alias-border-l1, rgba(127,127,127,0.35));color:var(--dsw-alias-label-secondary, #9aa0a6);background:var(--dsw-alias-bg-layer-1, rgba(30,30,32,0.92))";
|
|
4595
|
+
doc.body.appendChild(chip);
|
|
4596
|
+
chipAnchor = element;
|
|
4597
|
+
placeChip(doc);
|
|
4598
|
+
const timer2 = globalThis.setTimeout;
|
|
4599
|
+
if (typeof timer2 === "function") {
|
|
4600
|
+
for (const delay of [300, 900, 2e3, 3500]) timer2(() => {
|
|
4601
|
+
try {
|
|
4602
|
+
placeChip(doc);
|
|
4603
|
+
} catch {
|
|
4604
|
+
}
|
|
4605
|
+
}, delay);
|
|
4606
|
+
}
|
|
4607
|
+
const scope = globalThis;
|
|
4608
|
+
if (!chipScrollWatched && typeof scope.addEventListener === "function") {
|
|
4609
|
+
chipScrollWatched = true;
|
|
4610
|
+
scope.addEventListener("scroll", () => {
|
|
4611
|
+
try {
|
|
4612
|
+
const live = doc.querySelector("[data-dme-chip]");
|
|
4613
|
+
const anchor2 = chipAnchor;
|
|
4614
|
+
const box2 = anchor2?.getBoundingClientRect?.();
|
|
4615
|
+
if (live === null || box2 === void 0 || box2 === null) return;
|
|
4616
|
+
live.style.setProperty("left", String(Math.round(box2.left)) + "px");
|
|
4617
|
+
live.style.setProperty("top", String(Math.round(box2.bottom) + 4) + "px");
|
|
4618
|
+
} catch {
|
|
4619
|
+
}
|
|
4620
|
+
});
|
|
4621
|
+
}
|
|
4622
|
+
} catch {
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
function placeChip(doc) {
|
|
4626
|
+
const chip = doc?.querySelector?.("[data-dme-chip]");
|
|
4627
|
+
if (chip === null || chip === void 0) return;
|
|
4628
|
+
if (chipAnchor === void 0 || chipAnchor.isConnected === false) chipAnchor = findBlockElement(doc);
|
|
4629
|
+
const anchor2 = chipAnchor;
|
|
4630
|
+
if (anchor2 === void 0) return;
|
|
4631
|
+
const box = anchor2.getBoundingClientRect?.();
|
|
4632
|
+
if (box === void 0 || box === null) return;
|
|
4633
|
+
if (box.width === 0) {
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
const view = globalThis;
|
|
4637
|
+
const outside = box.bottom < 0 || box.top > Number(view.innerHeight ?? 0);
|
|
4638
|
+
chip.style.setProperty("display", outside ? "none" : "inline-block");
|
|
4639
|
+
if (outside) return;
|
|
4640
|
+
chip.style.setProperty("left", String(Math.max(8, Math.round(box.left))) + "px");
|
|
4641
|
+
chip.style.setProperty("top", String(Math.max(8, Math.round(box.bottom) + 4)) + "px");
|
|
4642
|
+
}
|
|
4643
|
+
function attachBelowBubble(label, typed) {
|
|
4644
|
+
const doc = globalThis.document;
|
|
4645
|
+
const timer = globalThis.setTimeout;
|
|
4646
|
+
if (doc === void 0 || typeof timer !== "function") return;
|
|
4647
|
+
const wanted = typed.trim();
|
|
4648
|
+
let tries = 0;
|
|
4649
|
+
const attempt = () => {
|
|
4650
|
+
tries += 1;
|
|
4651
|
+
try {
|
|
4652
|
+
const all = Array.from(doc.querySelectorAll("div, p, span"));
|
|
4653
|
+
let hit;
|
|
4654
|
+
if (wanted !== "") {
|
|
4655
|
+
const hits = all.filter((node) => String(node.textContent ?? "").trim().startsWith(wanted) && node.parentElement);
|
|
4656
|
+
hit = hits[hits.length - 1];
|
|
4657
|
+
} else {
|
|
4658
|
+
hit = findBlockElement(doc);
|
|
4659
|
+
}
|
|
4660
|
+
chipAttempt = hit === void 0 ? "not found, try " + String(tries) : "found";
|
|
4661
|
+
if (hit !== void 0) {
|
|
4662
|
+
insertChipAfter(doc, hit, label);
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
} catch {
|
|
4666
|
+
}
|
|
4667
|
+
if (tries < 20) timer(attempt, 500);
|
|
4668
|
+
};
|
|
4669
|
+
timer(attempt, 300);
|
|
4670
|
+
}
|
|
4671
|
+
function attachSentChip(label) {
|
|
4672
|
+
const doc = globalThis.document;
|
|
4673
|
+
const timer = globalThis.setTimeout;
|
|
4674
|
+
if (doc === void 0 || typeof timer !== "function") return;
|
|
4675
|
+
let tries = 0;
|
|
4676
|
+
const attempt = () => {
|
|
4677
|
+
tries += 1;
|
|
4678
|
+
const anchor2 = doc.querySelector?.("[data-annotation-chip]");
|
|
4679
|
+
const bubble = anchor2?.parentElement;
|
|
4680
|
+
const row = bubble?.parentElement;
|
|
4681
|
+
const made = doc.createElement?.("span");
|
|
4682
|
+
if (made !== void 0 && bubble !== void 0 && bubble !== null && row !== void 0 && row !== null) {
|
|
4683
|
+
made.textContent = label;
|
|
4684
|
+
made.setAttribute?.("data-dme-own", "1");
|
|
4685
|
+
made.style.cssText = "display:inline-block;margin:4px 8px 0;padding:1px 8px;border-radius:9px;font:11px system-ui;border:1px solid var(--dsw-alias-border-l1, rgba(127,127,127,0.35));color:var(--dsw-alias-label-secondary, #9aa0a6)";
|
|
4686
|
+
try {
|
|
4687
|
+
row.insertBefore(made, bubble.nextSibling);
|
|
4688
|
+
return;
|
|
4689
|
+
} catch {
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
if (tries < 8) timer(attempt, 250);
|
|
4693
|
+
};
|
|
4694
|
+
timer(attempt, 300);
|
|
4695
|
+
}
|
|
4696
|
+
var dockReact;
|
|
4697
|
+
var dockEl;
|
|
4698
|
+
var dockRetries = 0;
|
|
4699
|
+
var chipAttempt = "not attempted";
|
|
4700
|
+
var chipAnchor;
|
|
4701
|
+
var chipScrollWatched = false;
|
|
4702
|
+
var dockReceipt = "";
|
|
4703
|
+
var receiptTimer;
|
|
4704
|
+
function publishDockReceipt(text2) {
|
|
4705
|
+
dockReceipt = text2;
|
|
4706
|
+
const scope = globalThis;
|
|
4707
|
+
if (scope.clearTimeout !== void 0) scope.clearTimeout(receiptTimer);
|
|
4708
|
+
receiptTimer = scope.setTimeout?.(() => {
|
|
4709
|
+
dockReceipt = "";
|
|
4710
|
+
try {
|
|
4711
|
+
scheduleDock();
|
|
4712
|
+
} catch {
|
|
4713
|
+
}
|
|
4714
|
+
}, 8e3);
|
|
4715
|
+
}
|
|
4716
|
+
var dockObserver;
|
|
4717
|
+
var dockFrame;
|
|
4718
|
+
function ensureDock() {
|
|
4719
|
+
if (dockEl !== void 0 && dockEl.isConnected !== false) return dockEl;
|
|
4720
|
+
const doc = globalThis.document;
|
|
4721
|
+
if (doc?.body === void 0 || typeof doc.createElement !== "function") return void 0;
|
|
4722
|
+
for (const old of Array.from(doc.querySelectorAll("[data-dme-dock]"))) old.remove?.();
|
|
4723
|
+
const el = doc.createElement("div");
|
|
4724
|
+
el.setAttribute("data-dme-dock", "1");
|
|
4725
|
+
el.setAttribute("data-dme-own", "1");
|
|
4726
|
+
el.style.cssText = "position:fixed;z-index:1140;display:none;align-items:center;gap:6px;padding:4px 8px;border-radius:11px;border:1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35));background:var(--dsw-alias-bg-layer-1, #2c2c2e);box-shadow:0 8px 24px rgba(0,0,0,0.24);font:11px var(--dsw-font-family, system-ui);color:var(--dsw-alias-label-primary, #eee)";
|
|
4727
|
+
doc.body.appendChild(el);
|
|
4728
|
+
dockEl = el;
|
|
4729
|
+
return el;
|
|
4730
|
+
}
|
|
4731
|
+
function scheduleDock() {
|
|
4732
|
+
const raf = globalThis.requestAnimationFrame;
|
|
4733
|
+
if (dockFrame !== void 0 || typeof raf !== "function") {
|
|
4734
|
+
updateDockChip();
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
dockFrame = raf(() => {
|
|
4738
|
+
dockFrame = void 0;
|
|
4739
|
+
updateDockChip();
|
|
4740
|
+
});
|
|
4741
|
+
}
|
|
4742
|
+
function observeDockAnchor() {
|
|
4743
|
+
const doc = globalThis.document;
|
|
4744
|
+
const card = doc?.querySelector?.("[data-composer-card]");
|
|
4745
|
+
if (card === null || card === void 0) return;
|
|
4746
|
+
const Observer = globalThis.ResizeObserver;
|
|
4747
|
+
if (Observer === void 0) return;
|
|
4748
|
+
try {
|
|
4749
|
+
dockObserver?.disconnect();
|
|
4750
|
+
const fresh = new Observer(() => {
|
|
4751
|
+
scheduleDock();
|
|
4752
|
+
});
|
|
4753
|
+
fresh.observe(card);
|
|
4754
|
+
dockObserver = fresh;
|
|
4755
|
+
} catch {
|
|
4756
|
+
dockObserver = void 0;
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
function updateDockChip() {
|
|
4760
|
+
const state = dockReact;
|
|
4761
|
+
const el = ensureDock();
|
|
4762
|
+
if (state === void 0 || el === void 0) return;
|
|
4763
|
+
const doc = globalThis.document;
|
|
4764
|
+
if (state.count === 0) {
|
|
4765
|
+
if (dockReceipt === "") {
|
|
4766
|
+
el.style.display = "none";
|
|
4767
|
+
return;
|
|
4768
|
+
}
|
|
4769
|
+
el.textContent = "";
|
|
4770
|
+
const note = doc.createElement("span");
|
|
4771
|
+
note.textContent = dockReceipt;
|
|
4772
|
+
note.style.cssText = "opacity:.75";
|
|
4773
|
+
el.appendChild(note);
|
|
4774
|
+
el.style.display = "flex";
|
|
4775
|
+
const cardRect2 = doc.querySelector("[data-composer-card]")?.getBoundingClientRect?.();
|
|
4776
|
+
if (cardRect2 === void 0) {
|
|
4777
|
+
el.style.left = "16px";
|
|
4778
|
+
el.style.top = "16px";
|
|
4779
|
+
return;
|
|
4780
|
+
}
|
|
4781
|
+
const w = Number(el.offsetWidth) || 100;
|
|
4782
|
+
const h = Number(el.offsetHeight) || 24;
|
|
4783
|
+
el.style.left = Math.max(8, Math.min(cardRect2.left, Number(globalThis.innerWidth ?? 0) - w - 8)) + "px";
|
|
4784
|
+
el.style.top = Math.max(8, cardRect2.top - h - 8) + "px";
|
|
4785
|
+
return;
|
|
4786
|
+
}
|
|
4787
|
+
const create = (tag) => doc.createElement(tag);
|
|
4788
|
+
el.textContent = "";
|
|
4789
|
+
const toggle = create("button");
|
|
4790
|
+
toggle.textContent = t("pending") + " \xD7" + String(state.count);
|
|
4791
|
+
toggle.style.cssText = "font:11px inherit;padding:1px 6px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35));background:transparent;color:inherit;cursor:pointer";
|
|
4792
|
+
toggle.addEventListener("click", () => state.handlers.onToggle());
|
|
4793
|
+
el.appendChild(toggle);
|
|
4794
|
+
const send = create("button");
|
|
4795
|
+
send.textContent = t("send");
|
|
4796
|
+
send.style.cssText = "font:11px inherit;padding:1px 8px;border-radius:6px;border:1px solid var(--dsw-alias-brand-primary,#2563eb);background:var(--dsw-alias-brand-primary,#2563eb);color:#fff;cursor:pointer";
|
|
4797
|
+
send.addEventListener("click", () => state.handlers.onSend());
|
|
4798
|
+
el.appendChild(send);
|
|
4799
|
+
if (state.open && state.items.length > 0) {
|
|
4800
|
+
for (const [index, item] of state.items.entries()) {
|
|
4801
|
+
const line = create("span");
|
|
4802
|
+
line.textContent = item.nodeId + ":" + (item.note === "" ? t("markedOnly") : item.note);
|
|
4803
|
+
line.title = "\u70B9\u51FB\u5220\u9664";
|
|
4804
|
+
line.style.cssText = "cursor:pointer;opacity:.85";
|
|
4805
|
+
line.addEventListener("click", () => state.handlers.onRemove(index));
|
|
4806
|
+
el.appendChild(line);
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
el.style.display = "flex";
|
|
4810
|
+
observeDockAnchor();
|
|
4811
|
+
const surface = findComposer(doc) ?? doc.querySelector('textarea, [contenteditable="true"]');
|
|
4812
|
+
const rect = surface?.getBoundingClientRect?.();
|
|
4813
|
+
const cardEl = doc.querySelector("[data-composer-card]") ?? null;
|
|
4814
|
+
const seatEl = cardEl?.closest?.("[data-composer-seat]") ?? null;
|
|
4815
|
+
const cardRect = cardEl?.getBoundingClientRect?.() ?? null;
|
|
4816
|
+
void surface;
|
|
4817
|
+
const width = Number(el.offsetWidth) || 100;
|
|
4818
|
+
const height = Number(el.offsetHeight) || 26;
|
|
4819
|
+
if (rect === void 0 && cardRect === null) {
|
|
4820
|
+
el.style.left = "16px";
|
|
4821
|
+
el.style.top = "16px";
|
|
4822
|
+
return;
|
|
4823
|
+
}
|
|
4824
|
+
const view = globalThis;
|
|
4825
|
+
const anchor2 = cardRect ?? rect;
|
|
4826
|
+
const seatTop = seatEl?.getBoundingClientRect?.()?.top;
|
|
4827
|
+
const topEdge = Math.min(anchor2.top, typeof seatTop === "number" ? seatTop : anchor2.top);
|
|
4828
|
+
const left = Math.max(8, Math.min(anchor2.left, Number(view.innerWidth ?? 0) - width - 8));
|
|
4829
|
+
el.style.left = left + "px";
|
|
4830
|
+
el.style.top = Math.max(8, topEdge - height - 6) + "px";
|
|
4831
|
+
observeDivider(doc);
|
|
4832
|
+
observeSurface(doc, surface);
|
|
4833
|
+
const scope2 = globalThis;
|
|
4834
|
+
if (left <= 8 && dockRetries < 12 && typeof scope2.setTimeout === "function") {
|
|
4835
|
+
dockRetries += 1;
|
|
4836
|
+
scope2.setTimeout(() => {
|
|
4837
|
+
try {
|
|
4838
|
+
updateDockChip();
|
|
4839
|
+
} catch {
|
|
4840
|
+
}
|
|
4841
|
+
}, 250);
|
|
4842
|
+
} else if (left > 8) {
|
|
4843
|
+
dockRetries = 0;
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
function updateDockLayer(items, open, handlers) {
|
|
4847
|
+
dockReact = { count: items.length, open, items, handlers };
|
|
4848
|
+
bumpDock();
|
|
4849
|
+
if (dockSlotMounted) return;
|
|
4850
|
+
try {
|
|
4851
|
+
scheduleDock();
|
|
4852
|
+
} catch {
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
var dockSlotMounted = false;
|
|
4856
|
+
var dockVersion = 0;
|
|
4857
|
+
var dockSubs = /* @__PURE__ */ new Set();
|
|
4858
|
+
function bumpDock() {
|
|
4859
|
+
dockVersion += 1;
|
|
4860
|
+
for (const listener of Array.from(dockSubs)) {
|
|
4861
|
+
try {
|
|
4862
|
+
listener();
|
|
4863
|
+
} catch {
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
function dockSlotView(React) {
|
|
4868
|
+
return function DockSlot() {
|
|
4869
|
+
const [, setVersion] = (React.useState ?? ((initial) => [initial, () => {
|
|
4870
|
+
}]))(0);
|
|
4871
|
+
React.useEffect?.(() => {
|
|
4872
|
+
dockSlotMounted = true;
|
|
4873
|
+
const listener = () => setVersion(dockVersion);
|
|
4874
|
+
dockSubs.add(listener);
|
|
4875
|
+
return () => {
|
|
4876
|
+
dockSubs.delete(listener);
|
|
4877
|
+
dockSlotMounted = false;
|
|
4878
|
+
};
|
|
4879
|
+
}, []);
|
|
4880
|
+
const [pad, setPad] = (React.useState ?? ((initial) => [initial, () => {
|
|
4881
|
+
}]))({ left: 0, right: 0 });
|
|
4882
|
+
const [diag, setDiag] = (React.useState ?? ((initial) => [initial, () => {
|
|
4883
|
+
}]))("");
|
|
4884
|
+
React.useEffect?.(() => {
|
|
4885
|
+
const doc = globalThis.document;
|
|
4886
|
+
const timer = globalThis.setTimeout;
|
|
4887
|
+
const measure2 = () => {
|
|
4888
|
+
try {
|
|
4889
|
+
const slot = doc?.querySelector?.("[data-dme-dock-slot]");
|
|
4890
|
+
const card = findComposer(doc) ?? doc?.querySelector?.('textarea, [contenteditable="true"]');
|
|
4891
|
+
if (slot === null || slot === void 0 || card === null || card === void 0) return;
|
|
4892
|
+
const s = slot.getBoundingClientRect();
|
|
4893
|
+
const c = card.getBoundingClientRect();
|
|
4894
|
+
const next = { left: Math.max(0, Math.round(c.left - s.left)), right: Math.max(0, Math.round(s.right - c.right)) };
|
|
4895
|
+
setPad(next);
|
|
4896
|
+
setDiag(" L" + String(next.left) + " R" + String(next.right) + " s" + String(Math.round(s.left)) + " c" + String(Math.round(c.left)) + " w" + String(Math.round(c.width)));
|
|
4897
|
+
} catch {
|
|
4898
|
+
}
|
|
4899
|
+
};
|
|
4900
|
+
if (typeof timer === "function") {
|
|
4901
|
+
for (const delay of [0, 100, 250, 500, 900, 1500, 2400]) timer(measure2, delay);
|
|
4902
|
+
}
|
|
4903
|
+
const scope = globalThis;
|
|
4904
|
+
scope.addEventListener?.("resize", measure2);
|
|
4905
|
+
const Observer = globalThis.ResizeObserver;
|
|
4906
|
+
let watcher;
|
|
4907
|
+
if (Observer !== void 0) {
|
|
4908
|
+
try {
|
|
4909
|
+
watcher = new Observer(measure2);
|
|
4910
|
+
watcher.observe(doc?.body);
|
|
4911
|
+
} catch {
|
|
4912
|
+
watcher = void 0;
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
return () => {
|
|
4916
|
+
watcher?.disconnect();
|
|
4917
|
+
scope.removeEventListener?.("resize", measure2);
|
|
4918
|
+
};
|
|
4919
|
+
}, []);
|
|
4920
|
+
const state = dockReact;
|
|
4921
|
+
if (state === void 0 || state.count === 0) return null;
|
|
4922
|
+
let livePad = pad;
|
|
4923
|
+
try {
|
|
4924
|
+
const doc = globalThis.document;
|
|
4925
|
+
const slotEl = doc?.querySelector?.("[data-dme-dock-slot]");
|
|
4926
|
+
const surfaceEl = findComposer(doc) ?? doc?.querySelector?.('textarea, [contenteditable="true"]');
|
|
4927
|
+
const sr = slotEl?.getBoundingClientRect?.();
|
|
4928
|
+
const cr = surfaceEl?.getBoundingClientRect?.();
|
|
4929
|
+
if (sr !== void 0 && sr !== null && cr !== void 0 && cr !== null) {
|
|
4930
|
+
livePad = { left: Math.max(0, Math.round(cr.left - sr.left)), right: Math.max(0, Math.round(sr.right - cr.right)) };
|
|
4931
|
+
}
|
|
4932
|
+
} catch {
|
|
4933
|
+
}
|
|
4934
|
+
return React.createElement(
|
|
4935
|
+
"div",
|
|
4936
|
+
{
|
|
4937
|
+
"data-dme-own": "1",
|
|
4938
|
+
"data-dme-dock-slot": "1",
|
|
4939
|
+
style: { display: "flex", alignItems: "center", justifyContent: "flex-start", gap: "6px", paddingTop: "2px", paddingBottom: "2px", paddingLeft: String(livePad.left) + "px", paddingRight: String(livePad.right) + "px", font: "11px system-ui, sans-serif", color: "inherit" }
|
|
4940
|
+
},
|
|
4941
|
+
React.createElement("button", {
|
|
4942
|
+
type: "button",
|
|
4943
|
+
onClick: () => state.handlers.onToggle(),
|
|
4944
|
+
style: { font: "inherit", padding: "1px 8px", borderRadius: "9px", border: "1px solid " + TOKEN.border, background: "transparent", color: "inherit", cursor: "pointer" }
|
|
4945
|
+
}, t("pending") + " \xD7" + String(state.count) + " L" + String(livePad.left) + diag),
|
|
4946
|
+
React.createElement("button", {
|
|
4947
|
+
type: "button",
|
|
4948
|
+
onClick: () => state.handlers.onSend(),
|
|
4949
|
+
style: { font: "inherit", padding: "1px 8px", borderRadius: "9px", border: "1px solid " + TOKEN.border, background: "transparent", color: "inherit", cursor: "pointer" }
|
|
4950
|
+
}, t("send"))
|
|
4951
|
+
);
|
|
4952
|
+
};
|
|
4953
|
+
}
|
|
4954
|
+
var dividerObserved;
|
|
4955
|
+
var surfaceObserved;
|
|
4956
|
+
var surfaceInputWatched = false;
|
|
4957
|
+
function observeSurface(doc, surface) {
|
|
4958
|
+
try {
|
|
4959
|
+
if (surface === void 0 || surface === null || surfaceObserved === surface) return;
|
|
4960
|
+
surfaceObserved = surface;
|
|
4961
|
+
const targets = [surface, surface?.parentElement].filter((node) => node !== null && node !== void 0);
|
|
4962
|
+
const Observer = globalThis.ResizeObserver;
|
|
4963
|
+
if (Observer !== void 0) {
|
|
4964
|
+
const watcher = new Observer(() => {
|
|
4965
|
+
scheduleDock();
|
|
4966
|
+
});
|
|
4967
|
+
for (const target of targets) {
|
|
4968
|
+
try {
|
|
4969
|
+
watcher.observe(target);
|
|
4970
|
+
} catch {
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
const scope = globalThis;
|
|
4975
|
+
if (typeof scope.addEventListener === "function" && !surfaceInputWatched) {
|
|
4976
|
+
surfaceInputWatched = true;
|
|
4977
|
+
scope.addEventListener("input", () => {
|
|
4978
|
+
scheduleDock();
|
|
4979
|
+
scope.setTimeout?.(() => {
|
|
4980
|
+
scheduleDock();
|
|
4981
|
+
}, 120);
|
|
4982
|
+
scope.setTimeout?.(() => {
|
|
4983
|
+
scheduleDock();
|
|
4984
|
+
}, 400);
|
|
4985
|
+
});
|
|
4986
|
+
scope.addEventListener("paste", () => {
|
|
4987
|
+
scope.setTimeout?.(() => {
|
|
4988
|
+
scheduleDock();
|
|
4989
|
+
}, 60);
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4992
|
+
} catch {
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
function observeDivider(doc) {
|
|
4996
|
+
try {
|
|
4997
|
+
const handle = doc?.querySelector?.('[class*="widthHandle"]');
|
|
4998
|
+
if (handle === null || handle === void 0 || dividerObserved === handle) return;
|
|
4999
|
+
dividerObserved = handle;
|
|
5000
|
+
const Observer = globalThis.ResizeObserver;
|
|
5001
|
+
if (Observer !== void 0) {
|
|
5002
|
+
try {
|
|
5003
|
+
new Observer(() => {
|
|
5004
|
+
scheduleDock();
|
|
5005
|
+
}).observe(handle);
|
|
5006
|
+
} catch {
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
const scope = globalThis;
|
|
5010
|
+
scope.addEventListener?.("pointerup", () => {
|
|
5011
|
+
scheduleDock();
|
|
5012
|
+
});
|
|
5013
|
+
} catch {
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
function domReport() {
|
|
5017
|
+
const doc = globalThis.document;
|
|
5018
|
+
if (doc === void 0) return "no document";
|
|
5019
|
+
const lines = [];
|
|
5020
|
+
const rect = (el) => {
|
|
5021
|
+
const r = el?.getBoundingClientRect?.();
|
|
5022
|
+
return r === void 0 || r === null ? "absent" : [Math.round(r.left), Math.round(r.top), Math.round(r.width), Math.round(r.height)].join(",");
|
|
5023
|
+
};
|
|
5024
|
+
const probe = (name, selector) => {
|
|
5025
|
+
const el = doc.querySelector(selector);
|
|
5026
|
+
lines.push(name + " " + (el === null || el === void 0 ? "absent" : "l,t,w,h=" + rect(el)));
|
|
5027
|
+
};
|
|
5028
|
+
probe("[data-composer-card]", "[data-composer-card]");
|
|
5029
|
+
probe("[data-composer-seat]", "[data-composer-seat]");
|
|
5030
|
+
probe("textarea/contenteditable", 'textarea, [contenteditable="true"]');
|
|
5031
|
+
probe("[data-annotation-chip]", "[data-annotation-chip]");
|
|
5032
|
+
probe("[data-conversation-scroll]", "[data-conversation-scroll]");
|
|
5033
|
+
probe("widthHandle", '[class*="widthHandle"]');
|
|
5034
|
+
const dock = doc.querySelector("[data-dme-dock]");
|
|
5035
|
+
lines.push("dock " + (dock === null ? "absent" : "l,t,w,h=" + rect(dock) + " connected=" + String(dock.isConnected)));
|
|
5036
|
+
const chips = doc.querySelectorAll("[data-dme-chip]");
|
|
5037
|
+
lines.push("chips " + String(chips.length) + " " + Array.from(chips).map((chip) => rect(chip)).join(" | "));
|
|
5038
|
+
lines.push("pendingBlock " + String(pendingBlock === "" ? "empty" : pendingBlock.split(String.fromCharCode(10)).length + " lines"));
|
|
5039
|
+
lines.push("chipAttempt " + chipAttempt);
|
|
5040
|
+
lines.push("dockedCount " + String(dockReact?.count ?? 0) + " placedText " + String(placedText.length));
|
|
5041
|
+
lines.push("viewport " + String(globalThis.innerWidth) + "x" + String(globalThis.innerHeight));
|
|
5042
|
+
const report = lines.join(String.fromCharCode(10));
|
|
5043
|
+
try {
|
|
5044
|
+
globalThis.console?.log?.(report);
|
|
5045
|
+
} catch {
|
|
5046
|
+
}
|
|
5047
|
+
return report;
|
|
5048
|
+
}
|
|
5049
|
+
function installEnterCapture() {
|
|
5050
|
+
if (pendingBlock === "") return;
|
|
5051
|
+
const doc = globalThis.document;
|
|
5052
|
+
const composer = findComposer(doc);
|
|
5053
|
+
if (composer === void 0 || composer === null) return;
|
|
5054
|
+
if (enterWatch === composer) return;
|
|
5055
|
+
enterWatch = composer;
|
|
5056
|
+
const listen = composer.addEventListener;
|
|
5057
|
+
if (typeof listen !== "function") return;
|
|
5058
|
+
listen.call(composer, "keydown", (event) => {
|
|
5059
|
+
const key = event;
|
|
5060
|
+
if (key?.key !== "Enter" || key.shiftKey === true || key.isComposing === true) return;
|
|
5061
|
+
if (pendingBlock === "") return;
|
|
5062
|
+
const shell = draftShell();
|
|
5063
|
+
const draft = String(shell?.state?.getSnapshot?.()?.draft ?? "");
|
|
5064
|
+
const block = draft.trim() === "" ? withoutEmptyQuestion(pendingBlock) : pendingBlock;
|
|
5065
|
+
replaceBlock(block);
|
|
5066
|
+
const count = (block.match(/^Diagram note \[/gm) ?? []).length;
|
|
5067
|
+
const label = "\u56FE\u6CE8 \xD7" + String(count === 0 ? 1 : count);
|
|
5068
|
+
attachBelowBubble(label, draft);
|
|
5069
|
+
void attachSentChip;
|
|
5070
|
+
notifySent();
|
|
5071
|
+
pendingBlock = "";
|
|
5072
|
+
}, true);
|
|
5073
|
+
}
|
|
5074
|
+
function notifySent() {
|
|
5075
|
+
if (dockReact !== void 0) {
|
|
5076
|
+
const before = dockReact.count;
|
|
5077
|
+
dockReact = { count: 0, open: false, items: [], handlers: dockReact.handlers };
|
|
5078
|
+
if (before > 0) publishDockReceipt("\u56FE\u6CE8 \xD7" + String(before) + " \u5DF2\u53D1\u9001");
|
|
5079
|
+
try {
|
|
5080
|
+
scheduleDock();
|
|
5081
|
+
} catch {
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
for (const listener of sentListeners.slice()) {
|
|
5085
|
+
try {
|
|
5086
|
+
listener();
|
|
5087
|
+
} catch {
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5091
|
+
function placeBlock(text2) {
|
|
5092
|
+
const shell = draftShell();
|
|
5093
|
+
const snapshot = shell?.state?.getSnapshot?.();
|
|
5094
|
+
const current = typeof snapshot?.draft === "string" ? String(snapshot.draft) : "";
|
|
5095
|
+
return replaceBlock(current.trim() === "" ? withoutEmptyQuestion(text2) : text2);
|
|
5096
|
+
}
|
|
5097
|
+
var sentListeners = [];
|
|
5098
|
+
var watchState = "idle";
|
|
5099
|
+
var lastSeenDraft = "";
|
|
5100
|
+
function onMessageSent(listener) {
|
|
5101
|
+
sentListeners.push(listener);
|
|
5102
|
+
watchDraft();
|
|
5103
|
+
return () => {
|
|
5104
|
+
sentListeners = sentListeners.filter((item) => item !== listener);
|
|
5105
|
+
};
|
|
5106
|
+
}
|
|
5107
|
+
function watchDraft() {
|
|
5108
|
+
if (watchState !== "idle") return;
|
|
5109
|
+
const shell = draftShell();
|
|
5110
|
+
const subscribe = shell?.state?.subscribe;
|
|
5111
|
+
if (typeof subscribe !== "function") {
|
|
5112
|
+
return;
|
|
5113
|
+
}
|
|
5114
|
+
watchState = "watching";
|
|
5115
|
+
try {
|
|
5116
|
+
lastSeenDraft = String(shell?.state?.getSnapshot?.()?.draft ?? "");
|
|
5117
|
+
subscribe(() => {
|
|
5118
|
+
const draft = String(shell?.state?.getSnapshot?.()?.draft ?? "");
|
|
5119
|
+
const before = lastSeenDraft;
|
|
5120
|
+
lastSeenDraft = draft;
|
|
5121
|
+
if (before.trim() === "" || draft.trim() !== "") return;
|
|
5122
|
+
if (placedText === "") return;
|
|
5123
|
+
placedText = "";
|
|
5124
|
+
for (const listener of sentListeners.slice()) {
|
|
5125
|
+
try {
|
|
5126
|
+
listener();
|
|
5127
|
+
} catch {
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
});
|
|
5131
|
+
} catch {
|
|
5132
|
+
watchState = "unavailable";
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
function submitDraft() {
|
|
5136
|
+
const shell = draftShell();
|
|
5137
|
+
if (shell === void 0 || typeof shell.submit !== "function") return false;
|
|
5138
|
+
try {
|
|
5139
|
+
shell.submit("queue");
|
|
5140
|
+
return true;
|
|
5141
|
+
} catch {
|
|
5142
|
+
return false;
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
3322
5145
|
function viewFor(React, overrides = {}) {
|
|
3323
5146
|
return (props) => {
|
|
5147
|
+
if (props.toolName === DRAWING_TOOL) return drawingCard(React, props, overrides);
|
|
3324
5148
|
const diagram = viewModel({ phase: props.phase, toolName: props.toolName, block: props.block });
|
|
3325
5149
|
const { canvas, colours } = paintWith(diagram, overrides, { mode: themeMode() });
|
|
3326
5150
|
const written = toSvg(canvas, colours, diagram.kind === "none" ? diagram.caption : diagram.title + ": " + diagram.caption);
|
|
@@ -3360,41 +5184,113 @@ window.__ModuleLoader__.load({
|
|
|
3360
5184
|
const [scheme, setScheme] = useState("default");
|
|
3361
5185
|
const [fit, setFit] = useState(true);
|
|
3362
5186
|
const [folded, setFolded] = useState([]);
|
|
5187
|
+
const [annotate, setAnnotate] = useState(false);
|
|
5188
|
+
const [picked, setPicked] = useState(null);
|
|
5189
|
+
const [anchor2, setAnchor] = useState(void 0);
|
|
5190
|
+
const [note, setNote] = useState("");
|
|
5191
|
+
const [status, setStatus] = useState("");
|
|
5192
|
+
const [pending, setPending] = useState([]);
|
|
5193
|
+
const [open, setOpen] = useState(false);
|
|
5194
|
+
React.useEffect?.(() => {
|
|
5195
|
+
try {
|
|
5196
|
+
return onMessageSent(() => {
|
|
5197
|
+
setPending([]);
|
|
5198
|
+
setStatus("");
|
|
5199
|
+
});
|
|
5200
|
+
} catch {
|
|
5201
|
+
return void 0;
|
|
5202
|
+
}
|
|
5203
|
+
}, []);
|
|
3363
5204
|
const drawn = drawSample(React, overrides, source, fit ? 0 : zoom, scheme, folded);
|
|
5205
|
+
const document = globalThis.document;
|
|
5206
|
+
const caption = source.split(String.fromCharCode(10)).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
5207
|
+
const sendAll = (list, submit) => {
|
|
5208
|
+
if (list.length === 0) return;
|
|
5209
|
+
const text2 = annotationBlock(list.map((item) => ({ ...item, caption })));
|
|
5210
|
+
void (async () => {
|
|
5211
|
+
if (placeBlock(text2)) {
|
|
5212
|
+
setStatus("\u5DF2\u653E\u5165\u8F93\u5165\u6846\u5E76\u63D0\u4EA4\uFF08" + String(list.length) + " \u6761\u56FE\u6CE8\uFF09");
|
|
5213
|
+
setPending([]);
|
|
5214
|
+
if (submit) submitDraft();
|
|
5215
|
+
return;
|
|
5216
|
+
}
|
|
5217
|
+
const said = prependBlock(document, list, caption);
|
|
5218
|
+
setStatus(said);
|
|
5219
|
+
if (!said.startsWith("\u5DF2")) return;
|
|
5220
|
+
setPending([]);
|
|
5221
|
+
if (submit) submitComposer(document);
|
|
5222
|
+
})();
|
|
5223
|
+
};
|
|
5224
|
+
const addDraft = () => {
|
|
5225
|
+
if (picked === null) return false;
|
|
5226
|
+
const merged = [...pending, { nodeId: picked.id, note }];
|
|
5227
|
+
setPending(merged);
|
|
5228
|
+
setNote("");
|
|
5229
|
+
setPicked(null);
|
|
5230
|
+
setStatus("\u5DF2\u653E\u5165\u8F93\u5165\u6846\uFF0C\u76F4\u63A5\u56DE\u8F66\u5373\u53EF");
|
|
5231
|
+
return true;
|
|
5232
|
+
};
|
|
5233
|
+
const addAndSend = () => {
|
|
5234
|
+
if (picked === null) return;
|
|
5235
|
+
const merged = [...pending, { nodeId: picked.id, note }];
|
|
5236
|
+
setNote("");
|
|
5237
|
+
setPicked(null);
|
|
5238
|
+
sendAll(merged, true);
|
|
5239
|
+
};
|
|
3364
5240
|
const textarea = h("textarea", {
|
|
5241
|
+
"data-dme-own": "1",
|
|
3365
5242
|
value: source,
|
|
3366
5243
|
spellcheck: false,
|
|
3367
5244
|
rows: Math.min(12, Math.max(4, String(source).split("\n").length)),
|
|
3368
|
-
style: { width: "100%", font: "12px ui-monospace, SFMono-Regular, Menlo, monospace", padding: "6px", resize: "vertical" },
|
|
5245
|
+
style: { width: "100%", boxSizing: "border-box", font: "12px ui-monospace, SFMono-Regular, Menlo, monospace", padding: "6px", resize: "vertical", borderRadius: "6px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text },
|
|
3369
5246
|
onInput: (event) => setSource(String(event?.target?.value ?? ""))
|
|
3370
5247
|
});
|
|
5248
|
+
const controlStyle = { fontSize: "11px", lineHeight: "18px", padding: "1px 7px", borderRadius: "6px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text, cursor: "pointer" };
|
|
5249
|
+
const control = (label, onPress, title) => h("button", { type: "button", title, onClick: onPress, style: controlStyle }, label);
|
|
3371
5250
|
const controls = h(
|
|
3372
5251
|
"div",
|
|
3373
|
-
{ style: { display: "flex",
|
|
3374
|
-
h("button", { type: "button", onClick: () => {
|
|
3375
|
-
setFit(false);
|
|
3376
|
-
setZoom(Math.max(0.5, Math.round((Number(zoom) - 0.25) * 100) / 100));
|
|
3377
|
-
} }, "\u2212"),
|
|
3378
|
-
h("span", { style: { fontSize: "12px", opacity: 0.7 } }, fit ? "fit" : String(Math.round(Number(zoom) * 100)) + "%"),
|
|
3379
|
-
h("button", { type: "button", onClick: () => {
|
|
3380
|
-
setFit(false);
|
|
3381
|
-
setZoom(Math.min(3, Math.round((Number(zoom) + 0.25) * 100) / 100));
|
|
3382
|
-
} }, "+"),
|
|
3383
|
-
h("button", { type: "button", onClick: () => {
|
|
3384
|
-
setFit(false);
|
|
3385
|
-
setZoom(1);
|
|
3386
|
-
} }, "reset"),
|
|
3387
|
-
h("button", { type: "button", title: "Scale to the pane width", onClick: () => setFit(!fit) }, fit ? "fit: on" : "fit: off"),
|
|
3388
|
-
folded.length === 0 ? null : h("button", { type: "button", title: "Unfold every group", onClick: () => setFolded([]) }, "unfold " + String(folded.length)),
|
|
5252
|
+
{ style: { display: "flex", flexDirection: "column", gap: "3px", padding: "2px 0" } },
|
|
3389
5253
|
h(
|
|
3390
|
-
"
|
|
3391
|
-
{
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
},
|
|
3397
|
-
|
|
5254
|
+
"div",
|
|
5255
|
+
{ style: { display: "flex", gap: "4px", alignItems: "center", flexWrap: "wrap" } },
|
|
5256
|
+
control("\u2212", () => {
|
|
5257
|
+
setFit(false);
|
|
5258
|
+
setZoom(Math.max(0.5, Math.round((Number(zoom) - 0.25) * 100) / 100));
|
|
5259
|
+
}, "\u7F29\u5C0F"),
|
|
5260
|
+
h("span", { style: { fontSize: "11px", color: TOKEN.dim, minWidth: "34px", textAlign: "center" } }, fit ? "fit" : String(Math.round(Number(zoom) * 100)) + "%"),
|
|
5261
|
+
control("+", () => {
|
|
5262
|
+
setFit(false);
|
|
5263
|
+
setZoom(Math.min(3, Math.round((Number(zoom) + 0.25) * 100) / 100));
|
|
5264
|
+
}, "\u653E\u5927"),
|
|
5265
|
+
control("100%", () => {
|
|
5266
|
+
setFit(false);
|
|
5267
|
+
setZoom(1);
|
|
5268
|
+
}, "\u5B9E\u9645\u5927\u5C0F"),
|
|
5269
|
+
control(fit ? t("fitOn") : t("fitOff"), () => setFit(!fit), t("fitOff")),
|
|
5270
|
+
folded.length === 0 ? null : control(t("unfold") + " " + String(folded.length), () => setFolded([]), "\u5C55\u5F00\u6240\u6709\u5206\u7EC4"),
|
|
5271
|
+
h(
|
|
5272
|
+
"select",
|
|
5273
|
+
{
|
|
5274
|
+
value: scheme,
|
|
5275
|
+
title: t("scheme"),
|
|
5276
|
+
style: { marginLeft: "auto", fontSize: "11px", padding: "1px 4px", borderRadius: "6px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text },
|
|
5277
|
+
onChange: (event) => setScheme(String(event?.target?.value ?? "default"))
|
|
5278
|
+
},
|
|
5279
|
+
...schemeNames().map((name) => h("option", { value: name, selected: name === scheme }, name))
|
|
5280
|
+
)
|
|
5281
|
+
),
|
|
5282
|
+
h(
|
|
5283
|
+
"div",
|
|
5284
|
+
{ style: { display: "flex", gap: "6px", alignItems: "center" } },
|
|
5285
|
+
control(annotate ? t("noteOn") : t("note"), () => {
|
|
5286
|
+
setAnnotate(!annotate);
|
|
5287
|
+
setStatus("");
|
|
5288
|
+
if (annotate) {
|
|
5289
|
+
setPicked(null);
|
|
5290
|
+
setNote("");
|
|
5291
|
+
}
|
|
5292
|
+
}, t("noteHint")),
|
|
5293
|
+
h("span", { style: { fontSize: "11px", color: TOKEN.dim } }, annotate ? picked === null ? t("noteHint") : t("node") + " " + picked.id : t("noteHint"))
|
|
3398
5294
|
)
|
|
3399
5295
|
);
|
|
3400
5296
|
return h(
|
|
@@ -3402,21 +5298,76 @@ window.__ModuleLoader__.load({
|
|
|
3402
5298
|
{ style: { display: "flex", flexDirection: "column", gap: "4px", height: "100%", boxSizing: "border-box", padding: "8px" } },
|
|
3403
5299
|
textarea,
|
|
3404
5300
|
controls,
|
|
3405
|
-
// The drawing takes the rest of the pane and scrolls inside it
|
|
5301
|
+
// The drawing takes the rest of the pane and scrolls inside it; the annotation surface floats above it,
|
|
5302
|
+
// so the picture keeps its size while the reader writes about it.
|
|
3406
5303
|
h(
|
|
3407
5304
|
"div",
|
|
3408
5305
|
{
|
|
3409
|
-
style: { flex: "1 1 auto", minHeight: "0",
|
|
3410
|
-
// A boundary carries its id in the markup, so one listener on the container is enough: no per-rect
|
|
3411
|
-
// handler, and the handling survives a re-render that replaced the whole SVG.
|
|
3412
|
-
onClick: (event) => {
|
|
3413
|
-
const hit = event?.target?.closest?.("[data-group-id]");
|
|
3414
|
-
const id = hit?.getAttribute?.("data-group-id");
|
|
3415
|
-
if (typeof id !== "string" || id === "") return;
|
|
3416
|
-
setFolded(folded.includes(id) ? folded.filter((entry) => entry !== id) : [...folded, id]);
|
|
3417
|
-
}
|
|
5306
|
+
style: { position: "relative", flex: "1 1 auto", minHeight: "0", display: "flex", flexDirection: "column", gap: "2px" }
|
|
3418
5307
|
},
|
|
3419
|
-
|
|
5308
|
+
h(
|
|
5309
|
+
"div",
|
|
5310
|
+
{
|
|
5311
|
+
style: { flex: "1 1 auto", minHeight: "0", overflow: "auto" },
|
|
5312
|
+
// A boundary carries its id in the markup, so one listener on the container is enough: no per-rect
|
|
5313
|
+
// handler, and the handling survives a re-render that replaced the whole SVG.
|
|
5314
|
+
onClick: (event) => {
|
|
5315
|
+
if (annotate) {
|
|
5316
|
+
const node = event?.target?.closest?.("[data-node-id]");
|
|
5317
|
+
const marked = node?.getAttribute?.("data-node-id");
|
|
5318
|
+
if (typeof marked === "string" && marked !== "") {
|
|
5319
|
+
const pointer = event;
|
|
5320
|
+
const frame = pointer.currentTarget?.getBoundingClientRect?.();
|
|
5321
|
+
if (frame !== void 0 && typeof pointer.clientX === "number" && typeof pointer.clientY === "number") {
|
|
5322
|
+
setAnchor({
|
|
5323
|
+
left: Math.max(4, Math.min(pointer.clientX - frame.left + 12, frame.width - 270)),
|
|
5324
|
+
top: Math.max(4, Math.min(pointer.clientY - frame.top + 12, Math.max(4, frame.height - 160)))
|
|
5325
|
+
});
|
|
5326
|
+
} else {
|
|
5327
|
+
setAnchor({ left: 10, top: 10 });
|
|
5328
|
+
}
|
|
5329
|
+
setPicked({ id: marked });
|
|
5330
|
+
setStatus("");
|
|
5331
|
+
return;
|
|
5332
|
+
}
|
|
5333
|
+
if (note.trim() === "") {
|
|
5334
|
+
setPicked(null);
|
|
5335
|
+
setAnchor(void 0);
|
|
5336
|
+
}
|
|
5337
|
+
return;
|
|
5338
|
+
}
|
|
5339
|
+
const hit = event?.target?.closest?.("[data-group-id]");
|
|
5340
|
+
const id = hit?.getAttribute?.("data-group-id");
|
|
5341
|
+
if (typeof id !== "string" || id === "") return;
|
|
5342
|
+
setFolded(folded.includes(id) ? folded.filter((entry) => entry !== id) : [...folded, id]);
|
|
5343
|
+
}
|
|
5344
|
+
},
|
|
5345
|
+
drawn
|
|
5346
|
+
),
|
|
5347
|
+
annotate ? annotationPanel(
|
|
5348
|
+
h,
|
|
5349
|
+
{ picked, note, pending, status, anchor: anchor2, open },
|
|
5350
|
+
{
|
|
5351
|
+
onNote: setNote,
|
|
5352
|
+
onAdd: () => {
|
|
5353
|
+
addDraft();
|
|
5354
|
+
},
|
|
5355
|
+
onAddAndSend: addAndSend,
|
|
5356
|
+
onSend: () => sendAll(pending, true),
|
|
5357
|
+
onClear: () => {
|
|
5358
|
+
setPending([]);
|
|
5359
|
+
setStatus("");
|
|
5360
|
+
},
|
|
5361
|
+
onCancel: () => {
|
|
5362
|
+
setPicked(null);
|
|
5363
|
+
setNote("");
|
|
5364
|
+
setStatus("");
|
|
5365
|
+
},
|
|
5366
|
+
onToggle: () => setOpen(!open),
|
|
5367
|
+
onRemove: (index) => setPending(pending.filter((_, at) => at !== index))
|
|
5368
|
+
},
|
|
5369
|
+
{ document }
|
|
5370
|
+
) : null
|
|
3420
5371
|
)
|
|
3421
5372
|
);
|
|
3422
5373
|
};
|
|
@@ -3480,10 +5431,13 @@ window.__ModuleLoader__.load({
|
|
|
3480
5431
|
inject: ["slots"],
|
|
3481
5432
|
mount: (ctx, options = {}) => mountWhiteboard(React, ctx, options),
|
|
3482
5433
|
apply(ctx) {
|
|
5434
|
+
services = ctx;
|
|
5435
|
+
void startTranscriptDecoration;
|
|
3483
5436
|
for (const name of TOOL_NAMES) {
|
|
3484
5437
|
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({ name: "tool.call.toolview", key: name }, viewFor(React)));
|
|
3485
5438
|
}
|
|
3486
5439
|
ctx.slots.inject("sidebar.right.pane.tab", () => mountWhiteboard(React, ctx));
|
|
5440
|
+
void dockSlotView;
|
|
3487
5441
|
}
|
|
3488
5442
|
};
|
|
3489
5443
|
}
|