@artooi/ag-ui-web-component 0.33.1 → 0.35.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/CHANGELOG.md +563 -1
- package/README.md +279 -16
- package/dist/ag-ui-web-component.bundle.js +676 -101
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +80 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +37 -2
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +14 -0
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/highlight_overlay.d.ts +47 -0
- package/dist/dom/highlight_overlay.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2288 -388
- package/dist/index.js.map +4 -4
- package/dist/tools/chat_surface_tools.d.ts +96 -0
- package/dist/tools/chat_surface_tools.d.ts.map +1 -0
- package/dist/tools/page_action_tools.d.ts +2 -0
- package/dist/tools/page_action_tools.d.ts.map +1 -1
- package/dist/ui/chart_block.d.ts +18 -0
- package/dist/ui/chart_block.d.ts.map +1 -1
- package/dist/ui/clamp_launcher.d.ts +10 -5
- package/dist/ui/clamp_launcher.d.ts.map +1 -1
- package/dist/ui/clamp_panel.d.ts +13 -0
- package/dist/ui/clamp_panel.d.ts.map +1 -0
- package/dist/ui/launcher_drag.d.ts +2 -2
- package/dist/ui/launcher_drag.d.ts.map +1 -1
- package/dist/ui/launcher_placement.d.ts +14 -1
- package/dist/ui/launcher_placement.d.ts.map +1 -1
- package/dist/ui/panel_drag.d.ts +40 -0
- package/dist/ui/panel_drag.d.ts.map +1 -0
- package/dist/ui/place_widget.d.ts +31 -0
- package/dist/ui/place_widget.d.ts.map +1 -0
- package/dist/ui/run_notice.d.ts +14 -3
- package/dist/ui/run_notice.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +21 -0
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +16 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +87 -0
- package/src/core/ag_ui_chat.ts +1146 -38
- package/src/dom/animations.ts +30 -0
- package/src/dom/highlight_overlay.ts +256 -0
- package/src/index.ts +12 -0
- package/src/tools/chat_surface_tools.ts +207 -0
- package/src/tools/page_action_tools.ts +2 -0
- package/src/ui/chart_block.ts +222 -57
- package/src/ui/clamp_launcher.ts +25 -7
- package/src/ui/clamp_panel.ts +30 -0
- package/src/ui/launcher_drag.ts +11 -2
- package/src/ui/launcher_placement.ts +50 -60
- package/src/ui/panel_drag.ts +138 -0
- package/src/ui/place_widget.ts +64 -0
- package/src/ui/run_notice.ts +32 -3
- package/src/ui/styles.ts +622 -47
- package/src/ui/thread_drawer.ts +138 -8
- package/src/ui/ui_strings.ts +24 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,8 @@ var ICON_SEND = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path
|
|
|
59
59
|
var ICON_STOP = `<svg class="glyph glyph--solid" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2.5"/></svg>`;
|
|
60
60
|
var ICON_ATTACH = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8.5V15a5 5 0 0 1-10 0V7a3 3 0 0 1 6 0v7.5a1 1 0 0 1-2 0V8.5"/></svg>`;
|
|
61
61
|
var ICON_VOICE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V7a3 3 0 0 1 3-3z"/><path d="M5 11v1a7 7 0 0 0 14 0v-1"/><path d="M12 19v3"/></svg>`;
|
|
62
|
+
var ICON_MOON = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5z"/></svg>`;
|
|
63
|
+
var ICON_SUN = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>`;
|
|
62
64
|
var ICON_LAUNCHER = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-5 4z"/></svg>`;
|
|
63
65
|
var ICON_COPY = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 0 1 2-2h8"/></svg>`;
|
|
64
66
|
var ICON_RETRY = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12a8 8 0 1 1-2.5-5.8"/><path d="M20 4v4h-4"/></svg>`;
|
|
@@ -69,7 +71,12 @@ var ICON_FILE_IMAGE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"
|
|
|
69
71
|
var ICON_FILE_PDF = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><rect class="glyph--solid" x="7.5" y="13.5" width="9" height="4.5" rx="1"/></svg>`;
|
|
70
72
|
var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 13.5h7M8.5 17h4.5"/></svg>`;
|
|
71
73
|
var CHART_ACTIVITY_TYPE = "chart";
|
|
74
|
+
var EDGE_MARGIN = 24;
|
|
72
75
|
var ANNOUNCE_CLEAR_MS = 150;
|
|
76
|
+
var HIGHLIGHT_OVERLAY_Z_INDEX = 2147483001;
|
|
77
|
+
var THREADS_DOCK_MIN_WIDTH = 900;
|
|
78
|
+
var PASTE_ATTACH_CHARS = 5e3;
|
|
79
|
+
var SCREEN_EDGE_MARGIN = 8;
|
|
73
80
|
|
|
74
81
|
// src/core/ag_ui_chat.ts
|
|
75
82
|
import { randomUUID as randomUUID5 } from "@ag-ui/client";
|
|
@@ -106,6 +113,95 @@ function parseSkills(value) {
|
|
|
106
113
|
return value.filter(isSkill);
|
|
107
114
|
}
|
|
108
115
|
|
|
116
|
+
// src/tools/chat_surface_tools.ts
|
|
117
|
+
var CHAT_CORNERS = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
118
|
+
function isChatCorner(value) {
|
|
119
|
+
return CHAT_CORNERS.includes(value);
|
|
120
|
+
}
|
|
121
|
+
function createChatSurfaceTools(surface) {
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
name: "read_chat_surface",
|
|
125
|
+
description: "Describe the chat panel you are speaking from: its placement, whether it is collapsed, whether it can be moved, and the box it occupies. Read this before moving or minimising yourself, since a full-screen panel has nowhere to move to. Read-only.",
|
|
126
|
+
parameters: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {},
|
|
129
|
+
required: [],
|
|
130
|
+
[X_SUMMARY_KEY]: "Read the chat's own position"
|
|
131
|
+
},
|
|
132
|
+
handler: () => surface.describeSurface()
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "move_chat",
|
|
136
|
+
description: "Move your own panel to a corner, to uncover something the user needs to see. `corner` is top-left, top-right, bottom-left or bottom-right. Answers `moved: false` with a reason when the placement owns its position or the panel fills the screen; check `read_chat_surface` first, and prefer minimise_chat when there is nowhere to move to.",
|
|
137
|
+
parameters: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: {
|
|
140
|
+
corner: {
|
|
141
|
+
type: "string",
|
|
142
|
+
enum: [...CHAT_CORNERS]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ["corner"],
|
|
146
|
+
[X_SUMMARY_KEY]: "Move the chat out of the way"
|
|
147
|
+
},
|
|
148
|
+
handler: (args) => {
|
|
149
|
+
const asked = String(args["corner"] ?? "");
|
|
150
|
+
const report = surface.describeSurface();
|
|
151
|
+
if (!isChatCorner(asked)) {
|
|
152
|
+
return {
|
|
153
|
+
moved: false,
|
|
154
|
+
reason: `"${asked}" is not a corner; use one of ${CHAT_CORNERS.join(", ")}`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (!report.movable) {
|
|
158
|
+
return {
|
|
159
|
+
moved: false,
|
|
160
|
+
reason: report.fullBleed ? "the panel fills the screen, so there is nowhere to move it to" : report.draggable === false ? "this page has turned off moving the panel" : `the "${report.placement ?? ""}" placement owns the panel's position`,
|
|
161
|
+
suggestion: report.collapsible ? "minimise_chat" : null
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { moved: surface.moveTo(asked, { announce: true }), corner: asked };
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "minimise_chat",
|
|
169
|
+
description: "Collapse your own panel to its launcher, so the user can see the whole page. The launcher stays visible and reopens it. Answers `minimised: false` when the placement has no collapsed state, which is the case for a full-page chat.",
|
|
170
|
+
parameters: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {},
|
|
173
|
+
required: [],
|
|
174
|
+
[X_SUMMARY_KEY]: "Minimise the chat"
|
|
175
|
+
},
|
|
176
|
+
handler: () => {
|
|
177
|
+
const report = surface.describeSurface();
|
|
178
|
+
if (!report.collapsible) {
|
|
179
|
+
return {
|
|
180
|
+
minimised: false,
|
|
181
|
+
reason: "this placement has no collapsed state, so there is no launcher to return to"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
surface.setCollapsed(true, { announce: true });
|
|
185
|
+
return { minimised: true };
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "restore_chat",
|
|
190
|
+
description: "Open your own panel again after minimising it.",
|
|
191
|
+
parameters: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {},
|
|
194
|
+
required: [],
|
|
195
|
+
[X_SUMMARY_KEY]: "Restore the chat"
|
|
196
|
+
},
|
|
197
|
+
handler: () => {
|
|
198
|
+
surface.setCollapsed(false);
|
|
199
|
+
return { restored: true };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
|
|
109
205
|
// src/tools/client_tool_registry.ts
|
|
110
206
|
var ClientToolRegistry = class {
|
|
111
207
|
#tools = /* @__PURE__ */ new Map();
|
|
@@ -149,6 +245,133 @@ function isNavigates(parameters) {
|
|
|
149
245
|
return parameters[X_NAVIGATES_KEY] === true;
|
|
150
246
|
}
|
|
151
247
|
|
|
248
|
+
// src/dom/highlight_overlay.ts
|
|
249
|
+
var ACCENT = "#4f46e5";
|
|
250
|
+
function tokenOn(target, name, fallback) {
|
|
251
|
+
const value = window.getComputedStyle(target).getPropertyValue(name).trim();
|
|
252
|
+
return value === "" ? fallback : value;
|
|
253
|
+
}
|
|
254
|
+
var DEFAULT_PADDING_PX = 4;
|
|
255
|
+
function showHighlightOverlay(target, options = {}) {
|
|
256
|
+
const root = document.createElement("div");
|
|
257
|
+
root.setAttribute("data-ag-ui-highlight", "");
|
|
258
|
+
root.setAttribute("aria-hidden", "true");
|
|
259
|
+
root.style.cssText = [
|
|
260
|
+
"position: fixed",
|
|
261
|
+
"inset: 0",
|
|
262
|
+
"pointer-events: none",
|
|
263
|
+
// Above the widget's own default, because the overlay's whole job is to
|
|
264
|
+
// point at something on the page the widget is sitting over. A host whose
|
|
265
|
+
// chrome stacks higher still can say so.
|
|
266
|
+
`z-index: ${tokenOn(target, "--ag-ui-highlight-z-index", String(HIGHLIGHT_OVERLAY_Z_INDEX))}`
|
|
267
|
+
].join(";");
|
|
268
|
+
const scrim = document.createElement("div");
|
|
269
|
+
const ring = document.createElement("div");
|
|
270
|
+
root.className = "ag-ui-highlight";
|
|
271
|
+
scrim.className = "ag-ui-highlight-scrim";
|
|
272
|
+
ring.className = "ag-ui-highlight-ring";
|
|
273
|
+
if (options.scrim === true) {
|
|
274
|
+
root.append(scrim);
|
|
275
|
+
}
|
|
276
|
+
root.append(ring);
|
|
277
|
+
const ringWidth = options.ringWidth ?? Number.parseFloat(tokenOn(target, "--ag-ui-highlight-ring-width", "3"));
|
|
278
|
+
const flowMs = options.flowMs ?? Number.parseFloat(tokenOn(target, "--ag-ui-highlight-flow-ms", "2400"));
|
|
279
|
+
const paint = () => {
|
|
280
|
+
const box = target.getBoundingClientRect();
|
|
281
|
+
const pad = options.padding ?? DEFAULT_PADDING_PX;
|
|
282
|
+
const radius = options.radius ?? Number.parseFloat(getComputedStyle(target).borderRadius);
|
|
283
|
+
const left = box.left - pad;
|
|
284
|
+
const top = box.top - pad;
|
|
285
|
+
const width = box.width + pad * 2;
|
|
286
|
+
const height = box.height + pad * 2;
|
|
287
|
+
if (options.scrim === true) {
|
|
288
|
+
scrim.style.cssText = [
|
|
289
|
+
"position: absolute",
|
|
290
|
+
"inset: 0",
|
|
291
|
+
`background: ${tokenOn(target, "--ag-ui-highlight-scrim", "rgba(15, 15, 25, 0.45)")}`,
|
|
292
|
+
`clip-path: path(evenodd, '${holePath(left, top, width, height, radius + pad)}')`
|
|
293
|
+
].join(";");
|
|
294
|
+
}
|
|
295
|
+
ring.style.cssText = [
|
|
296
|
+
"position: absolute",
|
|
297
|
+
`left: ${left}px`,
|
|
298
|
+
`top: ${top}px`,
|
|
299
|
+
`width: ${width}px`,
|
|
300
|
+
`height: ${height}px`,
|
|
301
|
+
`border-radius: ${radius + pad}px`,
|
|
302
|
+
`border: ${ringWidth}px solid transparent`,
|
|
303
|
+
"box-sizing: border-box",
|
|
304
|
+
ringPaint(target, options)
|
|
305
|
+
].join(";");
|
|
306
|
+
};
|
|
307
|
+
paint();
|
|
308
|
+
let flow = null;
|
|
309
|
+
if (options.gradient === true && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
310
|
+
flow = ring.animate([{ backgroundPosition: "100% 0" }, { backgroundPosition: "-100% 0" }], {
|
|
311
|
+
duration: flowMs,
|
|
312
|
+
iterations: Number.POSITIVE_INFINITY,
|
|
313
|
+
easing: "linear"
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const listen = { capture: true, passive: true };
|
|
317
|
+
window.addEventListener("scroll", paint, listen);
|
|
318
|
+
window.addEventListener("resize", paint, listen);
|
|
319
|
+
document.body.appendChild(root);
|
|
320
|
+
return () => {
|
|
321
|
+
window.removeEventListener("scroll", paint, listen);
|
|
322
|
+
window.removeEventListener("resize", paint, listen);
|
|
323
|
+
flow?.cancel();
|
|
324
|
+
root.remove();
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function safeColor(value) {
|
|
328
|
+
if (value === void 0) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
return CSS.supports("color", value) ? value : null;
|
|
332
|
+
}
|
|
333
|
+
function ringPaint(target, options) {
|
|
334
|
+
const color = safeColor(options.color) ?? tokenOn(target, "--ag-ui-accent", ACCENT);
|
|
335
|
+
if (options.gradient !== true) {
|
|
336
|
+
return `border-color: ${color}`;
|
|
337
|
+
}
|
|
338
|
+
const image = tokenOn(
|
|
339
|
+
target,
|
|
340
|
+
"--ag-ui-highlight-gradient",
|
|
341
|
+
`linear-gradient(115deg, transparent 20%, ${color} 50%, transparent 80%)`
|
|
342
|
+
);
|
|
343
|
+
const mask = "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)";
|
|
344
|
+
return [
|
|
345
|
+
`background-image: ${image}`,
|
|
346
|
+
"background-origin: border-box",
|
|
347
|
+
"background-size: 300% 100%",
|
|
348
|
+
`-webkit-mask: ${mask}`,
|
|
349
|
+
`mask: ${mask}`,
|
|
350
|
+
"-webkit-mask-composite: xor",
|
|
351
|
+
"mask-composite: exclude",
|
|
352
|
+
"background-position: 50% 0"
|
|
353
|
+
].join(";");
|
|
354
|
+
}
|
|
355
|
+
function holePath(x2, y2, width, height, radius) {
|
|
356
|
+
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
357
|
+
const right = x2 + width;
|
|
358
|
+
const bottom = y2 + height;
|
|
359
|
+
const outer = `M 0 0 H ${window.innerWidth} V ${window.innerHeight} H 0 Z`;
|
|
360
|
+
const hole = [
|
|
361
|
+
`M ${x2 + r} ${y2}`,
|
|
362
|
+
`H ${right - r}`,
|
|
363
|
+
`A ${r} ${r} 0 0 1 ${right} ${y2 + r}`,
|
|
364
|
+
`V ${bottom - r}`,
|
|
365
|
+
`A ${r} ${r} 0 0 1 ${right - r} ${bottom}`,
|
|
366
|
+
`H ${x2 + r}`,
|
|
367
|
+
`A ${r} ${r} 0 0 1 ${x2} ${bottom - r}`,
|
|
368
|
+
`V ${y2 + r}`,
|
|
369
|
+
`A ${r} ${r} 0 0 1 ${x2 + r} ${y2}`,
|
|
370
|
+
"Z"
|
|
371
|
+
].join(" ");
|
|
372
|
+
return `${outer} ${hole}`;
|
|
373
|
+
}
|
|
374
|
+
|
|
152
375
|
// src/dom/native_setter.ts
|
|
153
376
|
function prototypeSetter(proto, prop) {
|
|
154
377
|
const setter = Object.getOwnPropertyDescriptor(proto, prop).set;
|
|
@@ -172,7 +395,7 @@ function setNativeChecked(el2, checked) {
|
|
|
172
395
|
}
|
|
173
396
|
|
|
174
397
|
// src/dom/animations.ts
|
|
175
|
-
var
|
|
398
|
+
var ACCENT2 = "#4f46e5";
|
|
176
399
|
var ACCENT_HALO = "rgba(79, 70, 229, 0.4)";
|
|
177
400
|
var ACCENT_PROPERTY = "--ag-ui-accent";
|
|
178
401
|
function delay(ms) {
|
|
@@ -213,7 +436,7 @@ async function highlightThenClick(el2, options = {}) {
|
|
|
213
436
|
const highlightMs = options.highlightMs ?? 280;
|
|
214
437
|
const previousOutline = el2.style.outline;
|
|
215
438
|
const previousOffset = el2.style.outlineOffset;
|
|
216
|
-
el2.style.outline = `2px solid ${accentColor(el2,
|
|
439
|
+
el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
|
|
217
440
|
el2.style.outlineOffset = "2px";
|
|
218
441
|
await delay(highlightMs);
|
|
219
442
|
el2.style.outline = previousOutline;
|
|
@@ -260,10 +483,21 @@ async function flashRing(el2, options, focusByDefault) {
|
|
|
260
483
|
if (flashMs <= 0) {
|
|
261
484
|
return;
|
|
262
485
|
}
|
|
486
|
+
if (options.scrim === true || options.gradient === true) {
|
|
487
|
+
const dismiss = showHighlightOverlay(el2, {
|
|
488
|
+
scrim: options.scrim === true,
|
|
489
|
+
gradient: options.gradient === true,
|
|
490
|
+
...options.color === void 0 ? {} : { color: options.color },
|
|
491
|
+
...options.ringPadding === void 0 ? {} : { padding: options.ringPadding }
|
|
492
|
+
});
|
|
493
|
+
await delay(flashMs);
|
|
494
|
+
dismiss();
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
263
497
|
const previousOutline = el2.style.outline;
|
|
264
498
|
const previousOffset = el2.style.outlineOffset;
|
|
265
499
|
const previousTransition = el2.style.transition;
|
|
266
|
-
const color = options.color ?? accentColor(el2,
|
|
500
|
+
const color = options.color ?? accentColor(el2, ACCENT2);
|
|
267
501
|
el2.style.outline = `3px solid ${color}`;
|
|
268
502
|
el2.style.outlineOffset = "2px";
|
|
269
503
|
const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
|
|
@@ -313,7 +547,7 @@ async function selectOption(el2, value, options = {}) {
|
|
|
313
547
|
const highlightMs = options.highlightMs ?? 220;
|
|
314
548
|
const previousOutline = el2.style.outline;
|
|
315
549
|
const previousOffset = el2.style.outlineOffset;
|
|
316
|
-
el2.style.outline = `2px solid ${accentColor(el2,
|
|
550
|
+
el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
|
|
317
551
|
el2.style.outlineOffset = "2px";
|
|
318
552
|
await motionDelay(highlightMs);
|
|
319
553
|
setNativeValue(el2, option.value);
|
|
@@ -336,7 +570,9 @@ async function toggleControl(el2, checked, options = {}) {
|
|
|
336
570
|
// src/tools/page_action_tools.ts
|
|
337
571
|
var PAGE_ACTIONS = {
|
|
338
572
|
SCROLL: "scroll",
|
|
339
|
-
DRAG: "drag"
|
|
573
|
+
DRAG: "drag",
|
|
574
|
+
/** The widget's own position: see `createChatSurfaceTools`. */
|
|
575
|
+
CHAT: "chat"
|
|
340
576
|
};
|
|
341
577
|
function createPageActionTools(enabled, resolveTarget) {
|
|
342
578
|
const tools = [];
|
|
@@ -546,6 +782,14 @@ function createRouteTools(getRouteMap, getNavigate) {
|
|
|
546
782
|
var DEFAULT_UI_STRINGS = {
|
|
547
783
|
title: "Assistant",
|
|
548
784
|
chatHistory: "Chat history",
|
|
785
|
+
closeHistory: "Close history",
|
|
786
|
+
searchConversations: "Search conversations",
|
|
787
|
+
queued: "Waiting to send",
|
|
788
|
+
removeQueued: 'Do not send "{text}"',
|
|
789
|
+
noMatches: "No conversations match that.",
|
|
790
|
+
chatMoved: "Moved this panel out of the way",
|
|
791
|
+
chatMinimised: "Minimised this panel",
|
|
792
|
+
undo: "Undo",
|
|
549
793
|
newChat: "New chat",
|
|
550
794
|
collapse: "Collapse",
|
|
551
795
|
expand: "Expand",
|
|
@@ -1093,11 +1337,14 @@ function accepts(accept, file) {
|
|
|
1093
1337
|
|
|
1094
1338
|
// src/ui/chart_block.ts
|
|
1095
1339
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
1096
|
-
var WIDTH = 480;
|
|
1097
|
-
var HEIGHT = 220;
|
|
1098
1340
|
var PAD = { top: 20, right: 12, bottom: 30, left: 44 };
|
|
1099
|
-
var
|
|
1100
|
-
var
|
|
1341
|
+
var DEFAULT_WIDTH = 480;
|
|
1342
|
+
var MIN_WIDTH = 220;
|
|
1343
|
+
var HEIGHT_RATIO = 220 / 480;
|
|
1344
|
+
var MIN_HEIGHT = 160;
|
|
1345
|
+
var MAX_HEIGHT = 320;
|
|
1346
|
+
var WIDTH_STEP = 8;
|
|
1347
|
+
var AXIS_CHAR_WIDTH = 5.6;
|
|
1101
1348
|
var SERIES_COLORS = [
|
|
1102
1349
|
"var(--ag-ui-chart-1, #4f7cff)",
|
|
1103
1350
|
"var(--ag-ui-chart-2, #21b573)",
|
|
@@ -1109,6 +1356,16 @@ var SERIES_COLORS = [
|
|
|
1109
1356
|
function seriesColor(index) {
|
|
1110
1357
|
return SERIES_COLORS[index % SERIES_COLORS.length];
|
|
1111
1358
|
}
|
|
1359
|
+
function geometryFor(width) {
|
|
1360
|
+
const w = Math.max(MIN_WIDTH, width);
|
|
1361
|
+
const h = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, Math.round(w * HEIGHT_RATIO)));
|
|
1362
|
+
return {
|
|
1363
|
+
width: w,
|
|
1364
|
+
height: h,
|
|
1365
|
+
plotW: w - PAD.left - PAD.right,
|
|
1366
|
+
plotH: h - PAD.top - PAD.bottom
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1112
1369
|
function el(name, attrs) {
|
|
1113
1370
|
const node = document.createElementNS(SVG_NS, name);
|
|
1114
1371
|
for (const [key, value] of Object.entries(attrs)) {
|
|
@@ -1143,21 +1400,26 @@ function extent(spec) {
|
|
|
1143
1400
|
const min = Math.min(0, ...values);
|
|
1144
1401
|
return max === min ? { min, max: max + 1 } : { min, max };
|
|
1145
1402
|
}
|
|
1146
|
-
function scaleY(value, min, max) {
|
|
1147
|
-
return PAD.top +
|
|
1403
|
+
function scaleY(value, min, max, geo) {
|
|
1404
|
+
return PAD.top + geo.plotH - (value - min) / (max - min) * geo.plotH;
|
|
1148
1405
|
}
|
|
1149
|
-
function bandCentre(index, count) {
|
|
1150
|
-
const step =
|
|
1406
|
+
function bandCentre(index, count, geo) {
|
|
1407
|
+
const step = geo.plotW / count;
|
|
1151
1408
|
return PAD.left + step * index + step / 2;
|
|
1152
1409
|
}
|
|
1153
|
-
function
|
|
1410
|
+
function labelStride(labels, geo) {
|
|
1411
|
+
const band = geo.plotW / labels.length;
|
|
1412
|
+
const widest = Math.max(...labels.map((label) => label.length)) * AXIS_CHAR_WIDTH;
|
|
1413
|
+
return Math.max(1, Math.ceil(widest / band));
|
|
1414
|
+
}
|
|
1415
|
+
function drawAxes(svg2, spec, geo, min, max) {
|
|
1154
1416
|
for (const value of [min, max]) {
|
|
1155
|
-
const y2 = scaleY(value, min, max);
|
|
1417
|
+
const y2 = scaleY(value, min, max, geo);
|
|
1156
1418
|
svg2.appendChild(
|
|
1157
1419
|
el("line", {
|
|
1158
1420
|
x1: PAD.left,
|
|
1159
1421
|
y1: y2,
|
|
1160
|
-
x2:
|
|
1422
|
+
x2: geo.width - PAD.right,
|
|
1161
1423
|
y2,
|
|
1162
1424
|
stroke: "currentColor",
|
|
1163
1425
|
"stroke-opacity": value === min ? 0.35 : 0.12
|
|
@@ -1167,23 +1429,27 @@ function drawAxes(svg2, spec, min, max) {
|
|
|
1167
1429
|
text(String(Math.round(value)), { x: PAD.left - 6, y: y2 + 4, "text-anchor": "end" })
|
|
1168
1430
|
);
|
|
1169
1431
|
}
|
|
1432
|
+
const stride = labelStride(spec.labels, geo);
|
|
1170
1433
|
spec.labels.forEach((label, i) => {
|
|
1434
|
+
if (i % stride !== 0) {
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1171
1437
|
svg2.appendChild(
|
|
1172
1438
|
text(label, {
|
|
1173
|
-
x: bandCentre(i, spec.labels.length),
|
|
1174
|
-
y:
|
|
1439
|
+
x: bandCentre(i, spec.labels.length, geo),
|
|
1440
|
+
y: geo.height - PAD.bottom + 16,
|
|
1175
1441
|
"text-anchor": "middle"
|
|
1176
1442
|
})
|
|
1177
1443
|
);
|
|
1178
1444
|
});
|
|
1179
1445
|
}
|
|
1180
|
-
function drawBars(svg2, spec, min, max) {
|
|
1181
|
-
const step =
|
|
1446
|
+
function drawBars(svg2, spec, geo, min, max) {
|
|
1447
|
+
const step = geo.plotW / spec.labels.length;
|
|
1182
1448
|
const width = step * 0.7 / spec.series.length;
|
|
1183
|
-
const base = scaleY(min, min, max);
|
|
1449
|
+
const base = scaleY(min, min, max, geo);
|
|
1184
1450
|
spec.series.forEach((series, s) => {
|
|
1185
1451
|
series.points.forEach((value, i) => {
|
|
1186
|
-
const y2 = scaleY(value, min, max);
|
|
1452
|
+
const y2 = scaleY(value, min, max, geo);
|
|
1187
1453
|
svg2.appendChild(
|
|
1188
1454
|
el("rect", {
|
|
1189
1455
|
x: PAD.left + step * i + step * 0.15 + width * s,
|
|
@@ -1197,8 +1463,8 @@ function drawBars(svg2, spec, min, max) {
|
|
|
1197
1463
|
});
|
|
1198
1464
|
});
|
|
1199
1465
|
}
|
|
1200
|
-
function drawStacked(svg2, spec, min, max) {
|
|
1201
|
-
const step =
|
|
1466
|
+
function drawStacked(svg2, spec, geo, min, max) {
|
|
1467
|
+
const step = geo.plotW / spec.labels.length;
|
|
1202
1468
|
const width = step * 0.7;
|
|
1203
1469
|
const running = spec.labels.map(() => 0);
|
|
1204
1470
|
spec.series.forEach((series, s) => {
|
|
@@ -1206,22 +1472,24 @@ function drawStacked(svg2, spec, min, max) {
|
|
|
1206
1472
|
const from = running[i] ?? 0;
|
|
1207
1473
|
const to = from + value;
|
|
1208
1474
|
running[i] = to;
|
|
1209
|
-
const y2 = scaleY(to, min, max);
|
|
1475
|
+
const y2 = scaleY(to, min, max, geo);
|
|
1210
1476
|
svg2.appendChild(
|
|
1211
1477
|
el("rect", {
|
|
1212
1478
|
x: PAD.left + step * i + step * 0.15,
|
|
1213
1479
|
y: y2,
|
|
1214
1480
|
width,
|
|
1215
|
-
height: Math.max(1, scaleY(from, min, max) - y2),
|
|
1481
|
+
height: Math.max(1, scaleY(from, min, max, geo) - y2),
|
|
1216
1482
|
fill: seriesColor(s)
|
|
1217
1483
|
})
|
|
1218
1484
|
);
|
|
1219
1485
|
});
|
|
1220
1486
|
});
|
|
1221
1487
|
}
|
|
1222
|
-
function drawLines(svg2, spec, min, max) {
|
|
1488
|
+
function drawLines(svg2, spec, geo, min, max) {
|
|
1223
1489
|
spec.series.forEach((series, s) => {
|
|
1224
|
-
const points = series.points.map(
|
|
1490
|
+
const points = series.points.map(
|
|
1491
|
+
(value, i) => `${bandCentre(i, spec.labels.length, geo)},${scaleY(value, min, max, geo)}`
|
|
1492
|
+
).join(" ");
|
|
1225
1493
|
svg2.appendChild(
|
|
1226
1494
|
el("polyline", {
|
|
1227
1495
|
points,
|
|
@@ -1233,13 +1501,13 @@ function drawLines(svg2, spec, min, max) {
|
|
|
1233
1501
|
);
|
|
1234
1502
|
});
|
|
1235
1503
|
}
|
|
1236
|
-
function drawScatter(svg2, spec, min, max) {
|
|
1504
|
+
function drawScatter(svg2, spec, geo, min, max) {
|
|
1237
1505
|
spec.series.forEach((series, s) => {
|
|
1238
1506
|
series.points.forEach((value, i) => {
|
|
1239
1507
|
svg2.appendChild(
|
|
1240
1508
|
el("circle", {
|
|
1241
|
-
cx: bandCentre(i, spec.labels.length),
|
|
1242
|
-
cy: scaleY(value, min, max),
|
|
1509
|
+
cx: bandCentre(i, spec.labels.length, geo),
|
|
1510
|
+
cy: scaleY(value, min, max, geo),
|
|
1243
1511
|
r: 4,
|
|
1244
1512
|
fill: seriesColor(s),
|
|
1245
1513
|
"fill-opacity": 0.85
|
|
@@ -1248,11 +1516,11 @@ function drawScatter(svg2, spec, min, max) {
|
|
|
1248
1516
|
});
|
|
1249
1517
|
});
|
|
1250
1518
|
}
|
|
1251
|
-
function drawPie(svg2, points) {
|
|
1519
|
+
function drawPie(svg2, points, geo) {
|
|
1252
1520
|
const total = points.reduce((sum, value) => sum + value, 0);
|
|
1253
|
-
const cx =
|
|
1254
|
-
const cy = PAD.top +
|
|
1255
|
-
const r = Math.min(
|
|
1521
|
+
const cx = geo.width / 2;
|
|
1522
|
+
const cy = PAD.top + geo.plotH / 2;
|
|
1523
|
+
const r = Math.min(geo.plotW, geo.plotH) / 2;
|
|
1256
1524
|
if (total === 0) {
|
|
1257
1525
|
svg2.appendChild(
|
|
1258
1526
|
el("circle", { cx, cy, r, fill: "none", stroke: "currentColor", "stroke-opacity": 0.3 })
|
|
@@ -1281,6 +1549,35 @@ function drawPie(svg2, points) {
|
|
|
1281
1549
|
angle = end;
|
|
1282
1550
|
});
|
|
1283
1551
|
}
|
|
1552
|
+
function drawSvg(spec, geo) {
|
|
1553
|
+
const svg2 = el("svg", {
|
|
1554
|
+
viewBox: `0 0 ${geo.width} ${geo.height}`,
|
|
1555
|
+
width: "100%",
|
|
1556
|
+
role: "img"
|
|
1557
|
+
});
|
|
1558
|
+
svg2.setAttribute("aria-label", spec.title ?? `${spec.kind} chart`);
|
|
1559
|
+
if (spec.kind === "pie") {
|
|
1560
|
+
const first = spec.series[0];
|
|
1561
|
+
drawPie(
|
|
1562
|
+
svg2,
|
|
1563
|
+
first.points.map((value) => Math.max(0, value)),
|
|
1564
|
+
geo
|
|
1565
|
+
);
|
|
1566
|
+
return svg2;
|
|
1567
|
+
}
|
|
1568
|
+
const { min, max } = extent(spec);
|
|
1569
|
+
drawAxes(svg2, spec, geo, min, max);
|
|
1570
|
+
if (spec.kind === "bar") {
|
|
1571
|
+
drawBars(svg2, spec, geo, min, max);
|
|
1572
|
+
} else if (spec.kind === "stacked") {
|
|
1573
|
+
drawStacked(svg2, spec, geo, min, max);
|
|
1574
|
+
} else if (spec.kind === "line") {
|
|
1575
|
+
drawLines(svg2, spec, geo, min, max);
|
|
1576
|
+
} else {
|
|
1577
|
+
drawScatter(svg2, spec, geo, min, max);
|
|
1578
|
+
}
|
|
1579
|
+
return svg2;
|
|
1580
|
+
}
|
|
1284
1581
|
function buildLegend(entries2) {
|
|
1285
1582
|
if (entries2.length < 2) {
|
|
1286
1583
|
return null;
|
|
@@ -1299,6 +1596,12 @@ function buildLegend(entries2) {
|
|
|
1299
1596
|
});
|
|
1300
1597
|
return row;
|
|
1301
1598
|
}
|
|
1599
|
+
function fitToWidth(block, redraw) {
|
|
1600
|
+
const observer = new ResizeObserver(() => {
|
|
1601
|
+
redraw(Math.round(block.clientWidth / WIDTH_STEP) * WIDTH_STEP);
|
|
1602
|
+
});
|
|
1603
|
+
observer.observe(block);
|
|
1604
|
+
}
|
|
1302
1605
|
function renderChart(spec) {
|
|
1303
1606
|
if (spec.labels.length === 0 || spec.series.length === 0) {
|
|
1304
1607
|
return null;
|
|
@@ -1313,27 +1616,8 @@ function renderChart(spec) {
|
|
|
1313
1616
|
heading.textContent = spec.title;
|
|
1314
1617
|
block.appendChild(heading);
|
|
1315
1618
|
}
|
|
1316
|
-
|
|
1317
|
-
svg2
|
|
1318
|
-
if (spec.kind === "pie") {
|
|
1319
|
-
const first = spec.series[0];
|
|
1320
|
-
drawPie(
|
|
1321
|
-
svg2,
|
|
1322
|
-
first.points.map((value) => Math.max(0, value))
|
|
1323
|
-
);
|
|
1324
|
-
} else {
|
|
1325
|
-
const { min, max } = extent(spec);
|
|
1326
|
-
drawAxes(svg2, spec, min, max);
|
|
1327
|
-
if (spec.kind === "bar") {
|
|
1328
|
-
drawBars(svg2, spec, min, max);
|
|
1329
|
-
} else if (spec.kind === "stacked") {
|
|
1330
|
-
drawStacked(svg2, spec, min, max);
|
|
1331
|
-
} else if (spec.kind === "line") {
|
|
1332
|
-
drawLines(svg2, spec, min, max);
|
|
1333
|
-
} else {
|
|
1334
|
-
drawScatter(svg2, spec, min, max);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1619
|
+
let width = DEFAULT_WIDTH;
|
|
1620
|
+
let svg2 = drawSvg(spec, geometryFor(width));
|
|
1337
1621
|
block.appendChild(svg2);
|
|
1338
1622
|
const legend = buildLegend(
|
|
1339
1623
|
spec.kind === "pie" ? spec.labels : spec.series.map((series) => series.label)
|
|
@@ -1341,6 +1625,15 @@ function renderChart(spec) {
|
|
|
1341
1625
|
if (legend !== null) {
|
|
1342
1626
|
block.appendChild(legend);
|
|
1343
1627
|
}
|
|
1628
|
+
fitToWidth(block, (measured) => {
|
|
1629
|
+
if (measured === width) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
width = measured;
|
|
1633
|
+
const next = drawSvg(spec, geometryFor(width));
|
|
1634
|
+
svg2.replaceWith(next);
|
|
1635
|
+
svg2 = next;
|
|
1636
|
+
});
|
|
1344
1637
|
return block;
|
|
1345
1638
|
}
|
|
1346
1639
|
|
|
@@ -1700,13 +1993,36 @@ var CheckpointMenu = class {
|
|
|
1700
1993
|
};
|
|
1701
1994
|
|
|
1702
1995
|
// src/ui/clamp_launcher.ts
|
|
1703
|
-
function clampLauncher(launcher, viewport) {
|
|
1996
|
+
function clampLauncher(launcher, viewport, margin = SCREEN_EDGE_MARGIN) {
|
|
1997
|
+
const room = Math.min(margin, Math.max(0, (viewport.width - launcher.width) / 2));
|
|
1998
|
+
const vertical = Math.min(margin, Math.max(0, (viewport.height - launcher.height) / 2));
|
|
1704
1999
|
return {
|
|
1705
|
-
left: Math.max(
|
|
1706
|
-
|
|
2000
|
+
left: Math.max(
|
|
2001
|
+
viewport.left + room,
|
|
2002
|
+
Math.min(launcher.left, viewport.left + viewport.width - launcher.width - room)
|
|
2003
|
+
),
|
|
2004
|
+
top: Math.max(
|
|
2005
|
+
viewport.top + vertical,
|
|
2006
|
+
Math.min(launcher.top, viewport.top + viewport.height - launcher.height - vertical)
|
|
2007
|
+
)
|
|
1707
2008
|
};
|
|
1708
2009
|
}
|
|
1709
2010
|
|
|
2011
|
+
// src/ui/clamp_panel.ts
|
|
2012
|
+
function clampPanel(host, viewport, margin = EDGE_MARGIN) {
|
|
2013
|
+
const width = host.right - host.left;
|
|
2014
|
+
const height = host.bottom - host.top;
|
|
2015
|
+
const left = Math.max(
|
|
2016
|
+
viewport.left + margin,
|
|
2017
|
+
Math.min(host.left, viewport.left + viewport.width - margin - width)
|
|
2018
|
+
);
|
|
2019
|
+
const top = Math.max(
|
|
2020
|
+
viewport.top + margin,
|
|
2021
|
+
Math.min(host.top, viewport.top + viewport.height - margin - height)
|
|
2022
|
+
);
|
|
2023
|
+
return { left, top, right: left + width, bottom: top + height };
|
|
2024
|
+
}
|
|
2025
|
+
|
|
1710
2026
|
// src/ui/confirmation_card.ts
|
|
1711
2027
|
function actionButton2(modifier, label) {
|
|
1712
2028
|
const button2 = document.createElement("button");
|
|
@@ -1908,6 +2224,7 @@ function enableLauncherDrag(launcher, options) {
|
|
|
1908
2224
|
const onUp = (up) => {
|
|
1909
2225
|
window.removeEventListener("pointermove", onMove);
|
|
1910
2226
|
window.removeEventListener("pointerup", onUp);
|
|
2227
|
+
window.removeEventListener("pointercancel", onUp);
|
|
1911
2228
|
if (!dragging) {
|
|
1912
2229
|
return;
|
|
1913
2230
|
}
|
|
@@ -1925,6 +2242,7 @@ function enableLauncherDrag(launcher, options) {
|
|
|
1925
2242
|
};
|
|
1926
2243
|
window.addEventListener("pointermove", onMove);
|
|
1927
2244
|
window.addEventListener("pointerup", onUp);
|
|
2245
|
+
window.addEventListener("pointercancel", onUp);
|
|
1928
2246
|
});
|
|
1929
2247
|
let pending = null;
|
|
1930
2248
|
const settle = () => {
|
|
@@ -1963,49 +2281,53 @@ function enableLauncherDrag(launcher, options) {
|
|
|
1963
2281
|
launcher.addEventListener("blur", settle);
|
|
1964
2282
|
}
|
|
1965
2283
|
|
|
1966
|
-
// src/ui/
|
|
1967
|
-
|
|
1968
|
-
function launcherPlacement(launcher, panel, viewport, margin = EDGE_MARGIN) {
|
|
1969
|
-
const roomRunningRight = viewport.width - launcher.left;
|
|
1970
|
-
const roomRunningLeft = launcher.left + launcher.width;
|
|
1971
|
-
const roomRunningDown = viewport.height - launcher.top;
|
|
1972
|
-
const roomRunningUp = launcher.top + launcher.height;
|
|
1973
|
-
const corner = {
|
|
1974
|
-
x: roomRunningRight >= roomRunningLeft ? "left" : "right",
|
|
1975
|
-
y: roomRunningDown >= roomRunningUp ? "top" : "bottom"
|
|
1976
|
-
};
|
|
1977
|
-
const wantedLeft = corner.x === "left" ? launcher.left : launcher.left + launcher.width - panel.width;
|
|
1978
|
-
const wantedTop = corner.y === "top" ? launcher.top : launcher.top + launcher.height - panel.height;
|
|
1979
|
-
const hostLeft = clamp(wantedLeft, margin, viewport.width - margin - panel.width);
|
|
1980
|
-
const hostTop = clamp(wantedTop, margin, viewport.height - margin - panel.height);
|
|
1981
|
-
const hostRight = hostLeft + panel.width;
|
|
1982
|
-
const hostBottom = hostTop + panel.height;
|
|
2284
|
+
// src/ui/place_widget.ts
|
|
2285
|
+
function placeWidget(host, launcher, corner, screen) {
|
|
1983
2286
|
return {
|
|
1984
|
-
corner,
|
|
1985
2287
|
hostInset: inset({
|
|
1986
|
-
top: corner.y === "top" ?
|
|
1987
|
-
right: corner.x === "right" ?
|
|
1988
|
-
bottom: corner.y === "bottom" ?
|
|
1989
|
-
left: corner.x === "left" ?
|
|
2288
|
+
top: corner.y === "top" ? host.top : null,
|
|
2289
|
+
right: corner.x === "right" ? screen.width - host.right : null,
|
|
2290
|
+
bottom: corner.y === "bottom" ? screen.height - host.bottom : null,
|
|
2291
|
+
left: corner.x === "left" ? host.left : null
|
|
1990
2292
|
}),
|
|
1991
|
-
// Measured from the host box's pinned corner to the launcher's matching
|
|
1992
|
-
// corner, so the launcher lands exactly where it was dropped.
|
|
1993
2293
|
launcherInset: inset({
|
|
1994
|
-
top: corner.y === "top" ? launcher.top -
|
|
1995
|
-
right: corner.x === "right" ?
|
|
1996
|
-
bottom: corner.y === "bottom" ?
|
|
1997
|
-
left: corner.x === "left" ? launcher.left -
|
|
2294
|
+
top: corner.y === "top" ? launcher.top - host.top : null,
|
|
2295
|
+
right: corner.x === "right" ? host.right - (launcher.left + launcher.width) : null,
|
|
2296
|
+
bottom: corner.y === "bottom" ? host.bottom - (launcher.top + launcher.height) : null,
|
|
2297
|
+
left: corner.x === "left" ? launcher.left - host.left : null
|
|
1998
2298
|
})
|
|
1999
2299
|
};
|
|
2000
2300
|
}
|
|
2001
|
-
function clamp(value, low, high) {
|
|
2002
|
-
return Math.max(low, Math.min(value, high));
|
|
2003
|
-
}
|
|
2004
2301
|
function inset(sides) {
|
|
2005
2302
|
const side = (value) => value === null ? "auto" : `${Math.round(value)}px`;
|
|
2006
2303
|
return `${side(sides.top)} ${side(sides.right)} ${side(sides.bottom)} ${side(sides.left)}`;
|
|
2007
2304
|
}
|
|
2008
2305
|
|
|
2306
|
+
// src/ui/launcher_placement.ts
|
|
2307
|
+
function launcherPlacement(launcher, panel, viewport, screen, margin = SCREEN_EDGE_MARGIN) {
|
|
2308
|
+
const roomRunningRight = viewport.left + viewport.width - launcher.left;
|
|
2309
|
+
const roomRunningLeft = launcher.left + launcher.width - viewport.left;
|
|
2310
|
+
const roomRunningDown = viewport.top + viewport.height - launcher.top;
|
|
2311
|
+
const roomRunningUp = launcher.top + launcher.height - viewport.top;
|
|
2312
|
+
const corner = {
|
|
2313
|
+
x: roomRunningRight >= roomRunningLeft ? "left" : "right",
|
|
2314
|
+
y: roomRunningDown >= roomRunningUp ? "top" : "bottom"
|
|
2315
|
+
};
|
|
2316
|
+
const wantedLeft = corner.x === "left" ? launcher.left : launcher.left + launcher.width - panel.width;
|
|
2317
|
+
const wantedTop = corner.y === "top" ? launcher.top : launcher.top + launcher.height - panel.height;
|
|
2318
|
+
const host = clampPanel(
|
|
2319
|
+
{
|
|
2320
|
+
left: wantedLeft,
|
|
2321
|
+
top: wantedTop,
|
|
2322
|
+
right: wantedLeft + panel.width,
|
|
2323
|
+
bottom: wantedTop + panel.height
|
|
2324
|
+
},
|
|
2325
|
+
viewport,
|
|
2326
|
+
margin
|
|
2327
|
+
);
|
|
2328
|
+
return { corner, ...placeWidget(host, launcher, corner, screen) };
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2009
2331
|
// src/ui/message_actions.ts
|
|
2010
2332
|
var CONFIRM_MS2 = 1500;
|
|
2011
2333
|
function attachMessageActions(bubble, options) {
|
|
@@ -2374,6 +2696,57 @@ function place(button2, rect) {
|
|
|
2374
2696
|
button2.style.left = `${Math.min(Math.max(centre, half), width - half)}px`;
|
|
2375
2697
|
}
|
|
2376
2698
|
|
|
2699
|
+
// src/ui/panel_drag.ts
|
|
2700
|
+
var DRAG_THRESHOLD2 = 4;
|
|
2701
|
+
var CONTROLS = "button, a[href], input, select, textarea, [contenteditable]";
|
|
2702
|
+
function enablePanelDrag(handle, options) {
|
|
2703
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
2704
|
+
if (event.button !== 0 || !options.enabled() || onControl(event, handle)) {
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
const start = options.rect();
|
|
2708
|
+
const originX = event.clientX;
|
|
2709
|
+
const originY = event.clientY;
|
|
2710
|
+
let dragging = false;
|
|
2711
|
+
const boxAt2 = (x2, y2) => {
|
|
2712
|
+
const dx = x2 - originX;
|
|
2713
|
+
const dy = y2 - originY;
|
|
2714
|
+
return {
|
|
2715
|
+
left: start.left + dx,
|
|
2716
|
+
top: start.top + dy,
|
|
2717
|
+
right: start.right + dx,
|
|
2718
|
+
bottom: start.bottom + dy
|
|
2719
|
+
};
|
|
2720
|
+
};
|
|
2721
|
+
const onMove = (move) => {
|
|
2722
|
+
if (!dragging && Math.hypot(move.clientX - originX, move.clientY - originY) < DRAG_THRESHOLD2) {
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
dragging = true;
|
|
2726
|
+
handle.setAttribute("data-dragging", "true");
|
|
2727
|
+
options.apply(boxAt2(move.clientX, move.clientY), start);
|
|
2728
|
+
};
|
|
2729
|
+
const onUp = (up) => {
|
|
2730
|
+
window.removeEventListener("pointermove", onMove);
|
|
2731
|
+
window.removeEventListener("pointerup", onUp);
|
|
2732
|
+
window.removeEventListener("pointercancel", onUp);
|
|
2733
|
+
if (!dragging) {
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
handle.removeAttribute("data-dragging");
|
|
2737
|
+
options.commit(boxAt2(up.clientX, up.clientY), start);
|
|
2738
|
+
};
|
|
2739
|
+
event.preventDefault();
|
|
2740
|
+
window.addEventListener("pointermove", onMove);
|
|
2741
|
+
window.addEventListener("pointerup", onUp);
|
|
2742
|
+
window.addEventListener("pointercancel", onUp);
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
function onControl(event, handle) {
|
|
2746
|
+
const path = event.composedPath();
|
|
2747
|
+
return path.slice(0, path.indexOf(handle)).some((node) => node instanceof Element && node.matches(CONTROLS));
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2377
2750
|
// src/ui/prettify_tool_name.ts
|
|
2378
2751
|
function prettifyToolName(name) {
|
|
2379
2752
|
const spaced = name.replace(/[._-]+/g, " ").trim();
|
|
@@ -2876,11 +3249,11 @@ function createDOMPurify() {
|
|
|
2876
3249
|
const originalDocument = document2;
|
|
2877
3250
|
const currentScript = originalDocument.currentScript;
|
|
2878
3251
|
window2.DocumentFragment;
|
|
2879
|
-
const HTMLTemplateElement = window2.HTMLTemplateElement, Node2 = window2.Node,
|
|
3252
|
+
const HTMLTemplateElement = window2.HTMLTemplateElement, Node2 = window2.Node, Element2 = window2.Element, NodeFilter2 = window2.NodeFilter, _window$NamedNodeMap = window2.NamedNodeMap;
|
|
2880
3253
|
_window$NamedNodeMap === void 0 ? window2.NamedNodeMap || window2.MozNamedAttrMap : _window$NamedNodeMap;
|
|
2881
3254
|
window2.HTMLFormElement;
|
|
2882
3255
|
const DOMParser = window2.DOMParser, trustedTypes = window2.trustedTypes;
|
|
2883
|
-
const ElementPrototype =
|
|
3256
|
+
const ElementPrototype = Element2.prototype;
|
|
2884
3257
|
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
2885
3258
|
const remove = lookupGetter(ElementPrototype, "remove");
|
|
2886
3259
|
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
|
|
@@ -4110,111 +4483,111 @@ function createDOMPurify() {
|
|
|
4110
4483
|
}
|
|
4111
4484
|
var purify = createDOMPurify();
|
|
4112
4485
|
|
|
4113
|
-
// node_modules/.pnpm/marked@18.0.
|
|
4114
|
-
function
|
|
4486
|
+
// node_modules/.pnpm/marked@18.0.11/node_modules/marked/lib/marked.esm.js
|
|
4487
|
+
function A() {
|
|
4115
4488
|
return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
|
|
4116
4489
|
}
|
|
4117
|
-
var R =
|
|
4490
|
+
var R = A();
|
|
4118
4491
|
function j(l3) {
|
|
4119
4492
|
R = l3;
|
|
4120
4493
|
}
|
|
4121
4494
|
var z = { exec: () => null };
|
|
4122
|
-
function
|
|
4495
|
+
function I(l3) {
|
|
4123
4496
|
let e = [];
|
|
4124
4497
|
return (t) => {
|
|
4125
4498
|
let n = Math.max(0, Math.min(3, t - 1)), s = e[n];
|
|
4126
4499
|
return s || (s = l3(n), e[n] = s), s;
|
|
4127
4500
|
};
|
|
4128
4501
|
}
|
|
4129
|
-
function
|
|
4502
|
+
function k(l3, e = "") {
|
|
4130
4503
|
let t = typeof l3 == "string" ? l3 : l3.source, n = { replace: (s, r) => {
|
|
4131
4504
|
let i = typeof r == "string" ? r : r.source;
|
|
4132
4505
|
return i = i.replace(m.caret, "$1"), t = t.replace(s, i), n;
|
|
4133
4506
|
}, getRegex: () => new RegExp(t, e) };
|
|
4134
4507
|
return n;
|
|
4135
4508
|
}
|
|
4136
|
-
var
|
|
4509
|
+
var Oe = ((l3 = "") => {
|
|
4137
4510
|
try {
|
|
4138
4511
|
return !!new RegExp("(?<=1)(?<!1)" + l3);
|
|
4139
4512
|
} catch {
|
|
4140
4513
|
return false;
|
|
4141
4514
|
}
|
|
4142
4515
|
})();
|
|
4143
|
-
var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex:
|
|
4144
|
-
var
|
|
4516
|
+
var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: I((l3) => new RegExp(`^ {0,${l3}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`)), hrRegex: I((l3) => new RegExp(`^ {0,${l3}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`)), fencesBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}(?:\`\`\`|~~~)`)), headingBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}#`)), htmlBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}<(?:[a-z].*>|!--)`, "i")), blockquoteBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}>`)) };
|
|
4517
|
+
var Te = /^(?:[ \t]*(?:\n|$))+/;
|
|
4145
4518
|
var we = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
|
|
4146
4519
|
var ye = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
|
|
4147
4520
|
var q = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
|
|
4148
4521
|
var Pe = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
|
|
4149
4522
|
var U = / {0,3}(?:[*+-]|\d{1,9}[.)])/;
|
|
4150
4523
|
var oe = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/;
|
|
4151
|
-
var ae =
|
|
4152
|
-
var Se =
|
|
4524
|
+
var ae = k(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex();
|
|
4525
|
+
var Se = k(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex();
|
|
4153
4526
|
var K = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|[ \t]+\n)[^\n]+)*)/;
|
|
4154
4527
|
var _e = /^[^\n]+/;
|
|
4155
4528
|
var W = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
|
|
4156
|
-
var $e =
|
|
4157
|
-
var Le =
|
|
4529
|
+
var $e = k(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", W).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
|
|
4530
|
+
var Le = k(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, U).getRegex();
|
|
4158
4531
|
var Q = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
|
|
4159
4532
|
var X = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
|
4160
|
-
var
|
|
4161
|
-
var le = (l3) =>
|
|
4533
|
+
var Ee = k("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n*|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>[^\\n]*\\n*|$)|<![A-Z][\\s\\S]*?(?:>[^\\n]*\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>[^\\n]*\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", X).replace("tag", Q).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
|
|
4534
|
+
var le = (l3) => k(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", l3).replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
|
|
4162
4535
|
var ze = le(/ {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]/);
|
|
4163
|
-
var
|
|
4164
|
-
var
|
|
4165
|
-
var J = { blockquote:
|
|
4166
|
-
var se =
|
|
4167
|
-
var
|
|
4168
|
-
var
|
|
4536
|
+
var Me = le(/ {0,3}(?:[*+-]|\d{1,9}[.)])(?:[ \t]|\n|$)/);
|
|
4537
|
+
var Ae = k(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", Me).getRegex();
|
|
4538
|
+
var J = { blockquote: Ae, code: we, def: $e, fences: ye, heading: Pe, hr: q, html: Ee, lheading: ae, list: Le, newline: Te, paragraph: ze, table: z, text: _e };
|
|
4539
|
+
var se = k("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
|
|
4540
|
+
var Ie = { ...J, lheading: Se, table: se, paragraph: k(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex() };
|
|
4541
|
+
var Ce = { ...J, html: k(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", X).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: z, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: k(K).replace("hr", q).replace("heading", ` *#{1,6} *[^
|
|
4169
4542
|
]`).replace("lheading", ae).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() };
|
|
4170
4543
|
var Be = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
|
|
4171
4544
|
var De = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
|
|
4172
|
-
var
|
|
4545
|
+
var ue = /^( {2,}|\\)\n(?!\s*$)/;
|
|
4173
4546
|
var qe = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
|
|
4174
4547
|
var _ = /[\p{P}\p{S}]/u;
|
|
4175
|
-
var
|
|
4548
|
+
var C = /[\s\p{P}\p{S}]/u;
|
|
4176
4549
|
var v = /[^\s\p{P}\p{S}]/u;
|
|
4177
|
-
var ve =
|
|
4550
|
+
var ve = k(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, C).getRegex();
|
|
4178
4551
|
var He = /[\p{Pi}\p{Ps}"']/u;
|
|
4179
|
-
var
|
|
4552
|
+
var pe = /(?!~)[\p{P}\p{S}]/u;
|
|
4180
4553
|
var Ze = /(?!~)[\s\p{P}\p{S}]/u;
|
|
4181
4554
|
var Ge = /(?:[^\s\p{P}\p{S}]|~)/u;
|
|
4182
|
-
var Qe =
|
|
4555
|
+
var Qe = k(/link|precode-code|html/, "g").replace("link", /\[(?:[^\[\]`]|(?<a>`+)[^`]+\k<a>(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", Oe ? "(?<!`)()" : "(^^|[^`])").replace("code", /(?<b>`+)[^`]+\k<b>(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex();
|
|
4183
4556
|
var ce = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/;
|
|
4184
|
-
var Ne =
|
|
4185
|
-
var je =
|
|
4557
|
+
var Ne = k(ce, "u").replace(/punct/g, _).getRegex();
|
|
4558
|
+
var je = k(ce, "u").replace(/punct/g, pe).getRegex();
|
|
4186
4559
|
var Fe = /^(?:\*+(?:((?!\*)(?!openQuote)punct)|([^\s*]))?)|^_+(?:((?!_)(?!openQuote)punct)|([^\s_]))?/;
|
|
4187
|
-
var Ue =
|
|
4560
|
+
var Ue = k(Fe, "u").replace(/openQuote/g, He).replace(/punct/g, _).getRegex();
|
|
4188
4561
|
var he = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)";
|
|
4189
|
-
var Ke =
|
|
4190
|
-
var We =
|
|
4562
|
+
var Ke = k(he, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4563
|
+
var We = k(he, "gu").replace(/notPunctSpace/g, Ge).replace(/punctSpace/g, Ze).replace(/punct/g, pe).getRegex();
|
|
4191
4564
|
var Xe = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)[\\s](\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|(?:(?!\\*)punct|notPunctSpace)(\\*+)(?!\\*)(?=notPunctSpace)";
|
|
4192
|
-
var Je =
|
|
4193
|
-
var Ve =
|
|
4565
|
+
var Je = k(Xe, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4566
|
+
var Ve = k("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4194
4567
|
var Ye = "^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)[\\s](_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)|(?:(?!_)punct|notPunctSpace)(_+)(?!_)(?=notPunctSpace)";
|
|
4195
|
-
var et =
|
|
4196
|
-
var tt =
|
|
4568
|
+
var et = k(Ye, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4569
|
+
var tt = k(/^~~?(?:((?!~)punct)|[^\s~])/, "u").replace(/punct/g, _).getRegex();
|
|
4197
4570
|
var nt = "^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)";
|
|
4198
|
-
var rt =
|
|
4199
|
-
var st =
|
|
4200
|
-
var it =
|
|
4201
|
-
var ot =
|
|
4202
|
-
var at =
|
|
4571
|
+
var rt = k(nt, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4572
|
+
var st = k(/\\(punct)/, "gu").replace(/punct/g, _).getRegex();
|
|
4573
|
+
var it = k(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
|
|
4574
|
+
var ot = k(X).replace("(?:-->|$)", "-->").getRegex();
|
|
4575
|
+
var at = k("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", ot).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
|
|
4203
4576
|
var G = /(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/;
|
|
4204
|
-
var lt =
|
|
4205
|
-
var
|
|
4206
|
-
var
|
|
4207
|
-
var
|
|
4577
|
+
var lt = k(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label", G).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]+|(?=\))/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
|
|
4578
|
+
var ke = k(/^!?\[(label)\]\[(ref)\]/).replace("label", G).replace("ref", W).getRegex();
|
|
4579
|
+
var de = k(/^!?\[(ref)\](?:\[\])?/).replace("ref", W).getRegex();
|
|
4580
|
+
var ut = k("reflink|nolink(?!\\()", "g").replace("reflink", ke).replace("nolink", de).getRegex();
|
|
4208
4581
|
var ie = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/;
|
|
4209
|
-
var V = { _backpedal: z, anyPunctuation: st, autolink: it, blockSkip: Qe, br:
|
|
4210
|
-
var
|
|
4211
|
-
var F = { ...V, emStrongRDelimAst: We, emStrongLDelim: je, delLDelim: tt, delRDelim: rt, url:
|
|
4212
|
-
var ct = { ...F, br:
|
|
4213
|
-
var H = { normal: J, gfm:
|
|
4214
|
-
var B = { normal: V, gfm: F, breaks: ct, pedantic:
|
|
4582
|
+
var V = { _backpedal: z, anyPunctuation: st, autolink: it, blockSkip: Qe, br: ue, code: De, del: z, delLDelim: z, delRDelim: z, emStrongLDelim: Ne, emStrongRDelimAst: Ke, emStrongRDelimUnd: Ve, escape: Be, link: lt, nolink: de, punctuation: ve, reflink: ke, reflinkSearch: ut, tag: at, text: qe, url: z };
|
|
4583
|
+
var pt = { ...V, emStrongLDelim: Ue, emStrongRDelimAst: Je, emStrongRDelimUnd: et, link: k(/^!?\[(label)\]\((.*?)\)/).replace("label", G).getRegex(), reflink: k(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", G).getRegex() };
|
|
4584
|
+
var F = { ...V, emStrongRDelimAst: We, emStrongLDelim: je, delLDelim: tt, delRDelim: rt, url: k(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", ie).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: k(/^(`+|~+|[^`~])(?:(?=[`~])|(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|protocol:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/).replace("protocol", ie).getRegex() };
|
|
4585
|
+
var ct = { ...F, br: k(ue).replace("{2,}", "*").getRegex(), text: k(F.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex() };
|
|
4586
|
+
var H = { normal: J, gfm: Ie, pedantic: Ce };
|
|
4587
|
+
var B = { normal: V, gfm: F, breaks: ct, pedantic: pt };
|
|
4215
4588
|
var ht = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
4216
4589
|
var ge = (l3) => ht[l3];
|
|
4217
|
-
function
|
|
4590
|
+
function T(l3, e) {
|
|
4218
4591
|
if (e) {
|
|
4219
4592
|
if (m.escapeTest.test(l3)) return l3.replace(m.escapeReplace, ge);
|
|
4220
4593
|
} else if (m.escapeTestNoEncode.test(l3)) return l3.replace(m.escapeReplaceNoEncode, ge);
|
|
@@ -4230,9 +4603,9 @@ function Y(l3) {
|
|
|
4230
4603
|
}
|
|
4231
4604
|
function ee(l3, e) {
|
|
4232
4605
|
let t = l3.replace(m.findPipe, (r, i, o) => {
|
|
4233
|
-
let
|
|
4234
|
-
for (; --a >= 0 && o[a] === "\\"; )
|
|
4235
|
-
return
|
|
4606
|
+
let u = false, a = i;
|
|
4607
|
+
for (; --a >= 0 && o[a] === "\\"; ) u = !u;
|
|
4608
|
+
return u ? "|" : " |";
|
|
4236
4609
|
}), n = t.split(m.splitPipe), s = 0;
|
|
4237
4610
|
if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e);
|
|
4238
4611
|
else for (; n.length < e; ) n.push("");
|
|
@@ -4275,12 +4648,21 @@ function me(l3, e = 0) {
|
|
|
4275
4648
|
return n;
|
|
4276
4649
|
}
|
|
4277
4650
|
function xe(l3, e, t, n, s) {
|
|
4278
|
-
let r = e.href, i = e.title || null, o = l3[1].replace(s.other.outputLinkReplace, "$1");
|
|
4651
|
+
let r = e.href, i = e.title || null, o = l3[1].replace(s.other.outputLinkReplace, "$1"), u = l3[0].charAt(0) === "!";
|
|
4279
4652
|
n.state.inLink = true;
|
|
4280
|
-
let
|
|
4281
|
-
|
|
4653
|
+
let a = n.state.linkEmitted, p = n.state.inRawBlock;
|
|
4654
|
+
n.state.linkEmitted = false;
|
|
4655
|
+
let c = n.inlineTokens(o), h = n.state.linkEmitted;
|
|
4656
|
+
if (n.state.linkEmitted = a, n.state.inLink = false, !u) {
|
|
4657
|
+
if (h) {
|
|
4658
|
+
n.state.inRawBlock = p;
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
n.state.linkEmitted = true;
|
|
4662
|
+
}
|
|
4663
|
+
return { type: u ? "image" : "link", raw: t, href: r, title: i, text: o, tokens: c };
|
|
4282
4664
|
}
|
|
4283
|
-
function
|
|
4665
|
+
function kt(l3, e, t) {
|
|
4284
4666
|
let n = l3.match(t.other.indentCodeCompensation);
|
|
4285
4667
|
if (n === null) return e;
|
|
4286
4668
|
let s = n[1];
|
|
@@ -4314,7 +4696,7 @@ var y = class {
|
|
|
4314
4696
|
fences(e) {
|
|
4315
4697
|
let t = this.rules.block.fences.exec(e);
|
|
4316
4698
|
if (t) {
|
|
4317
|
-
let n = t[0], s =
|
|
4699
|
+
let n = t[0], s = kt(n, t[3] || "", this.rules);
|
|
4318
4700
|
return { type: "code", raw: n, lang: t[2] ? t[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t[2], text: s };
|
|
4319
4701
|
}
|
|
4320
4702
|
}
|
|
@@ -4342,33 +4724,33 @@ var y = class {
|
|
|
4342
4724
|
`).split(`
|
|
4343
4725
|
`), s = "", r = "", i = [];
|
|
4344
4726
|
for (; n.length > 0; ) {
|
|
4345
|
-
let o = false,
|
|
4346
|
-
for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a]))
|
|
4347
|
-
else if (!o)
|
|
4727
|
+
let o = false, u = [], a;
|
|
4728
|
+
for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) u.push(n[a]), o = true;
|
|
4729
|
+
else if (!o) u.push(n[a]);
|
|
4348
4730
|
else break;
|
|
4349
4731
|
n = n.slice(a);
|
|
4350
|
-
let
|
|
4351
|
-
`), c =
|
|
4732
|
+
let p = u.join(`
|
|
4733
|
+
`), c = p.replace(this.rules.other.blockquoteSetextReplace, `
|
|
4352
4734
|
$1`).replace(this.rules.other.blockquoteSetextReplace2, "");
|
|
4353
4735
|
s = s ? `${s}
|
|
4354
|
-
${
|
|
4736
|
+
${p}` : p, r = r ? `${r}
|
|
4355
4737
|
${c}` : c;
|
|
4356
4738
|
let h = this.lexer.state.top;
|
|
4357
4739
|
if (this.lexer.state.top = true, this.lexer.blockTokens(c, i, true), this.lexer.state.top = h, n.length === 0) break;
|
|
4358
|
-
let
|
|
4359
|
-
if (
|
|
4360
|
-
if (
|
|
4361
|
-
let
|
|
4362
|
-
`), w =
|
|
4363
|
-
` + g.replace(this.rules.other.blockquoteSetextReplace2, ""),
|
|
4364
|
-
i[i.length - 1] =
|
|
4365
|
-
${g}`, r = r.substring(0, r.length -
|
|
4740
|
+
let d = i.at(-1);
|
|
4741
|
+
if (d?.type === "code") break;
|
|
4742
|
+
if (d?.type === "blockquote") {
|
|
4743
|
+
let O = d, g = n.join(`
|
|
4744
|
+
`), w = O.raw + `
|
|
4745
|
+
` + g.replace(this.rules.other.blockquoteSetextReplace2, ""), E = this.blockquote(w);
|
|
4746
|
+
i[i.length - 1] = E, s = `${s}
|
|
4747
|
+
${g}`, r = r.substring(0, r.length - O.text.length) + E.text;
|
|
4366
4748
|
break;
|
|
4367
|
-
} else if (
|
|
4368
|
-
let
|
|
4749
|
+
} else if (d?.type === "list") {
|
|
4750
|
+
let O = d, g = O.raw + `
|
|
4369
4751
|
` + n.join(`
|
|
4370
4752
|
`), w = this.list(g);
|
|
4371
|
-
i[i.length - 1] = w, s = s.substring(0, s.length -
|
|
4753
|
+
i[i.length - 1] = w, s = s.substring(0, s.length - d.raw.length) + w.raw, r = r.substring(0, r.length - O.raw.length) + w.raw, n = g.substring(i.at(-1).raw.length).split(`
|
|
4372
4754
|
`);
|
|
4373
4755
|
continue;
|
|
4374
4756
|
}
|
|
@@ -4383,44 +4765,44 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4383
4765
|
n = s ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = s ? n : "[*+-]");
|
|
4384
4766
|
let i = this.rules.other.listItemRegex(n), o = false;
|
|
4385
4767
|
for (; e; ) {
|
|
4386
|
-
let a = false,
|
|
4768
|
+
let a = false, p = "", c = "";
|
|
4387
4769
|
if (!(t = i.exec(e)) || this.rules.block.hr.test(e)) break;
|
|
4388
|
-
|
|
4770
|
+
p = t[0], e = e.substring(p.length);
|
|
4389
4771
|
let h = me(t[2].split(`
|
|
4390
|
-
`, 1)[0], t[1].length),
|
|
4391
|
-
`, 1)[0],
|
|
4392
|
-
if (this.options.pedantic ? (g = 2, c = h.trimStart()) :
|
|
4393
|
-
`, e = e.substring(
|
|
4394
|
-
let w = this.rules.other.nextBulletRegex(g),
|
|
4772
|
+
`, 1)[0], t[1].length), d = e.split(`
|
|
4773
|
+
`, 1)[0], O = !h.trim(), g = 0;
|
|
4774
|
+
if (this.options.pedantic ? (g = 2, c = h.trimStart()) : O ? g = t[1].length + 1 : (g = h.search(this.rules.other.nonSpaceChar), g = g > 4 ? 1 : g, c = h.slice(g), g += t[1].length), O && this.rules.other.blankLine.test(d) && (p += d + `
|
|
4775
|
+
`, e = e.substring(d.length + 1), a = true), !a) {
|
|
4776
|
+
let w = this.rules.other.nextBulletRegex(g), E = this.rules.other.hrRegex(g), ne = this.rules.other.fencesBeginRegex(g), re = this.rules.other.headingBeginRegex(g), be = this.rules.other.htmlBeginRegex(g), Re = this.rules.other.blockquoteBeginRegex(g);
|
|
4395
4777
|
for (; e; ) {
|
|
4396
4778
|
let N = e.split(`
|
|
4397
4779
|
`, 1)[0], D;
|
|
4398
|
-
if (
|
|
4399
|
-
if (D.search(this.rules.other.nonSpaceChar) >= g || !
|
|
4780
|
+
if (d = N, this.options.pedantic ? (d = d.replace(this.rules.other.listReplaceNesting, " "), D = d) : D = d.replace(this.rules.other.tabCharGlobal, " "), ne.test(d) || re.test(d) || be.test(d) || Re.test(d) || w.test(d) || E.test(d)) break;
|
|
4781
|
+
if (D.search(this.rules.other.nonSpaceChar) >= g || !d.trim()) c += `
|
|
4400
4782
|
` + D.slice(g);
|
|
4401
4783
|
else {
|
|
4402
|
-
if (
|
|
4784
|
+
if (O || h.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || ne.test(h) || re.test(h) || E.test(h)) break;
|
|
4403
4785
|
c += `
|
|
4404
|
-
` +
|
|
4786
|
+
` + d;
|
|
4405
4787
|
}
|
|
4406
|
-
|
|
4788
|
+
O = !d.trim(), p += N + `
|
|
4407
4789
|
`, e = e.substring(N.length + 1), h = D.slice(g);
|
|
4408
4790
|
}
|
|
4409
4791
|
}
|
|
4410
|
-
r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(
|
|
4792
|
+
r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(p) && (o = true)), r.items.push({ type: "list_item", raw: p, task: !!this.options.gfm && this.rules.other.listIsTask.test(c), loose: false, text: c, tokens: [] }), r.raw += p;
|
|
4411
4793
|
}
|
|
4412
|
-
let
|
|
4413
|
-
if (
|
|
4794
|
+
let u = r.items.at(-1);
|
|
4795
|
+
if (u) u.raw = u.raw.trimEnd(), u.text = u.text.trimEnd();
|
|
4414
4796
|
else return;
|
|
4415
4797
|
r.raw = r.raw.trimEnd();
|
|
4416
4798
|
for (let a of r.items) if (this.lexer.state.top = false, a.tokens = this.lexer.blockTokens(a.text, []), !r.loose) {
|
|
4417
|
-
let
|
|
4799
|
+
let p = a.tokens.filter((h) => h.type === "space"), c = p.length > 0 && p.some((h) => this.rules.other.anyLine.test(h.raw));
|
|
4418
4800
|
r.loose = c;
|
|
4419
4801
|
}
|
|
4420
4802
|
for (let a of r.items) {
|
|
4421
|
-
let
|
|
4422
|
-
if (a.task && (
|
|
4423
|
-
a.text = a.text.replace(this.rules.other.listReplaceTask, ""),
|
|
4803
|
+
let p = a.tokens[0];
|
|
4804
|
+
if (a.task && (p?.type === "text" || p?.type === "paragraph")) {
|
|
4805
|
+
a.text = a.text.replace(this.rules.other.listReplaceTask, ""), p.raw = p.raw.replace(this.rules.other.listReplaceTask, ""), p.text = p.text.replace(this.rules.other.listReplaceTask, "");
|
|
4424
4806
|
for (let h = this.lexer.inlineQueue.length - 1; h >= 0; h--) if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[h].src)) {
|
|
4425
4807
|
this.lexer.inlineQueue[h].src = this.lexer.inlineQueue[h].src.replace(this.rules.other.listReplaceTask, "");
|
|
4426
4808
|
break;
|
|
@@ -4434,7 +4816,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4434
4816
|
}
|
|
4435
4817
|
if (r.loose) for (let a of r.items) {
|
|
4436
4818
|
a.loose = true;
|
|
4437
|
-
for (let
|
|
4819
|
+
for (let p of a.tokens) p.type === "text" && (p.type = "paragraph");
|
|
4438
4820
|
}
|
|
4439
4821
|
return r;
|
|
4440
4822
|
}
|
|
@@ -4463,7 +4845,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4463
4845
|
if (n.length === s.length) {
|
|
4464
4846
|
for (let o of s) this.rules.other.tableAlignRight.test(o) ? i.align.push("right") : this.rules.other.tableAlignCenter.test(o) ? i.align.push("center") : this.rules.other.tableAlignLeft.test(o) ? i.align.push("left") : i.align.push(null);
|
|
4465
4847
|
for (let o = 0; o < n.length; o++) i.header.push({ text: n[o], tokens: this.lexer.inline(n[o]), header: true, align: i.align[o] });
|
|
4466
|
-
for (let o of r) i.rows.push(ee(o, i.header.length).map((
|
|
4848
|
+
for (let o of r) i.rows.push(ee(o, i.header.length).map((u, a) => ({ text: u, tokens: this.lexer.inline(u), header: false, align: i.align[a] })));
|
|
4467
4849
|
return i;
|
|
4468
4850
|
}
|
|
4469
4851
|
}
|
|
@@ -4507,8 +4889,8 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4507
4889
|
let i = fe(t[2], "()");
|
|
4508
4890
|
if (i === -2) return;
|
|
4509
4891
|
if (i > -1) {
|
|
4510
|
-
let
|
|
4511
|
-
t[2] = t[2].substring(0, i), t[0] = t[0].substring(0,
|
|
4892
|
+
let u = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + i;
|
|
4893
|
+
t[2] = t[2].substring(0, i), t[0] = t[0].substring(0, u).trim(), t[3] = "";
|
|
4512
4894
|
}
|
|
4513
4895
|
}
|
|
4514
4896
|
let s = t[2], r = "";
|
|
@@ -4534,25 +4916,25 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4534
4916
|
let s = this.rules.inline.emStrongLDelim.exec(e);
|
|
4535
4917
|
if (!s || !s[1] && !s[2] && !s[3] && !s[4] || s[4] && n.match(this.rules.other.unicodeAlphaNumeric)) return;
|
|
4536
4918
|
if (!(s[1] || s[3] || "") || !n || this.rules.inline.punctuation.exec(n)) {
|
|
4537
|
-
let i = [...s[0]].length - 1, o,
|
|
4538
|
-
for (
|
|
4919
|
+
let i = [...s[0]].length - 1, o, u, a = i, p = 0, c = s[0][0], h = n === c, d = c === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
|
|
4920
|
+
for (d.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = d.exec(t)) !== null; ) {
|
|
4539
4921
|
if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o) continue;
|
|
4540
|
-
if (
|
|
4541
|
-
a +=
|
|
4922
|
+
if (u = [...o].length, s[3] || s[4]) {
|
|
4923
|
+
a += u;
|
|
4542
4924
|
continue;
|
|
4543
4925
|
} else if (s[5] || s[6]) {
|
|
4544
|
-
if (i % 3 && !((i +
|
|
4545
|
-
|
|
4926
|
+
if (i % 3 && !((i + u) % 3)) {
|
|
4927
|
+
p += u;
|
|
4546
4928
|
continue;
|
|
4547
4929
|
}
|
|
4548
4930
|
if (h) break;
|
|
4549
4931
|
}
|
|
4550
|
-
if (a -=
|
|
4551
|
-
|
|
4552
|
-
let
|
|
4553
|
-
if (Math.min(i,
|
|
4554
|
-
let
|
|
4555
|
-
return { type: "em", raw: g, text:
|
|
4932
|
+
if (a -= u, a > 0) continue;
|
|
4933
|
+
u = Math.min(u, u + a + p);
|
|
4934
|
+
let O = [...s[0]][0].length, g = e.slice(0, i + s.index + O + u);
|
|
4935
|
+
if (Math.min(i, u) % 2) {
|
|
4936
|
+
let E = g.slice(1, -1);
|
|
4937
|
+
return { type: "em", raw: g, text: E, tokens: this.lexer.inlineTokens(E) };
|
|
4556
4938
|
}
|
|
4557
4939
|
let w = g.slice(2, -2);
|
|
4558
4940
|
return { type: "strong", raw: g, text: w, tokens: this.lexer.inlineTokens(w) };
|
|
@@ -4574,17 +4956,17 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4574
4956
|
let s = this.rules.inline.delLDelim.exec(e);
|
|
4575
4957
|
if (!s) return;
|
|
4576
4958
|
if (!(s[1] || "") || !n || this.rules.inline.punctuation.exec(n)) {
|
|
4577
|
-
let i = [...s[0]].length - 1, o,
|
|
4578
|
-
for (
|
|
4579
|
-
if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o || (
|
|
4959
|
+
let i = [...s[0]].length - 1, o, u, a = i, p = this.rules.inline.delRDelim;
|
|
4960
|
+
for (p.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = p.exec(t)) !== null; ) {
|
|
4961
|
+
if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o || (u = [...o].length, u !== i)) continue;
|
|
4580
4962
|
if (s[3] || s[4]) {
|
|
4581
|
-
a +=
|
|
4963
|
+
a += u;
|
|
4582
4964
|
continue;
|
|
4583
4965
|
}
|
|
4584
|
-
if (a -=
|
|
4585
|
-
|
|
4586
|
-
let c = [...s[0]][0].length, h = e.slice(0, i + s.index + c +
|
|
4587
|
-
return { type: "del", raw: h, text:
|
|
4966
|
+
if (a -= u, a > 0) continue;
|
|
4967
|
+
u = Math.min(u, u + a);
|
|
4968
|
+
let c = [...s[0]][0].length, h = e.slice(0, i + s.index + c + u), d = h.slice(i, -i);
|
|
4969
|
+
return { type: "del", raw: h, text: d, tokens: this.lexer.inlineTokens(d) };
|
|
4588
4970
|
}
|
|
4589
4971
|
}
|
|
4590
4972
|
}
|
|
@@ -4625,7 +5007,7 @@ var x = class l {
|
|
|
4625
5007
|
inlineQueue;
|
|
4626
5008
|
tokenizer;
|
|
4627
5009
|
constructor(e) {
|
|
4628
|
-
this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || R, this.options.tokenizer = this.options.tokenizer || new y(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, top: true };
|
|
5010
|
+
this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || R, this.options.tokenizer = this.options.tokenizer || new y(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, linkEmitted: false, top: true };
|
|
4629
5011
|
let t = { other: m, block: H.normal, inline: B.normal };
|
|
4630
5012
|
this.options.pedantic ? (t.block = H.pedantic, t.inline = B.pedantic) : this.options.gfm && (t.block = H.gfm, this.options.breaks ? t.inline = B.breaks : t.inline = B.gfm), this.tokenizer.rules = t;
|
|
4631
5013
|
}
|
|
@@ -4717,9 +5099,9 @@ var x = class l {
|
|
|
4717
5099
|
}
|
|
4718
5100
|
let i = e;
|
|
4719
5101
|
if (this.options.extensions?.startBlock) {
|
|
4720
|
-
let o = 1 / 0,
|
|
4721
|
-
this.options.extensions.startBlock.forEach((
|
|
4722
|
-
a =
|
|
5102
|
+
let o = 1 / 0, u = e.slice(1), a;
|
|
5103
|
+
this.options.extensions.startBlock.forEach((p) => {
|
|
5104
|
+
a = p.call({ lexer: this }, u), typeof a == "number" && a >= 0 && (o = Math.min(o, a));
|
|
4723
5105
|
}), o < 1 / 0 && o >= 0 && (i = e.substring(0, o + 1));
|
|
4724
5106
|
}
|
|
4725
5107
|
if (this.state.top && (r = this.tokenizer.paragraph(i))) {
|
|
@@ -4749,16 +5131,34 @@ var x = class l {
|
|
|
4749
5131
|
inline(e, t = []) {
|
|
4750
5132
|
return this.inlineQueue.push({ src: e, tokens: t }), t;
|
|
4751
5133
|
}
|
|
5134
|
+
linkInText(e) {
|
|
5135
|
+
if (!e.includes("[")) return false;
|
|
5136
|
+
let t = this.tokenizer.rules.inline.link;
|
|
5137
|
+
for (let n of e.matchAll(this.tokenizer.rules.inline.blockSkip)) if (t.test(n[0]) && e.charAt(n.index - 1) !== "!") return true;
|
|
5138
|
+
for (let n of e.matchAll(this.tokenizer.rules.inline.reflinkSearch)) {
|
|
5139
|
+
let s = n[0], r = s.lastIndexOf("[");
|
|
5140
|
+
if (!(s.charAt(0) === "!" || !Object.hasOwn(this.tokens.links, s.slice(r + 1, -1))) && !(r > 1 && this.linkInText(s.slice(1, r - 1)))) return true;
|
|
5141
|
+
}
|
|
5142
|
+
return false;
|
|
5143
|
+
}
|
|
4752
5144
|
inlineTokens(e, t = []) {
|
|
4753
5145
|
this.tokenizer.lexer = this;
|
|
4754
5146
|
let n = e;
|
|
4755
|
-
if (this.tokens.links) {
|
|
4756
|
-
let o =
|
|
4757
|
-
|
|
5147
|
+
if (this.tokens.links && e.includes("[")) {
|
|
5148
|
+
let o = this.tokenizer.rules.inline.reflinkSearch, u = (a) => {
|
|
5149
|
+
let p = a.lastIndexOf("[");
|
|
5150
|
+
if (!Object.hasOwn(this.tokens.links, a.slice(p + 1, -1))) return a;
|
|
5151
|
+
if (p > 1 && a.charAt(0) !== "!") {
|
|
5152
|
+
let c = a.slice(1, p - 1);
|
|
5153
|
+
if (this.linkInText(c)) return "[" + c.replace(o, u) + "][" + "a".repeat(a.length - p - 2) + "]";
|
|
5154
|
+
}
|
|
5155
|
+
return "[" + "a".repeat(a.length - 2) + "]";
|
|
5156
|
+
};
|
|
5157
|
+
n = n.replace(o, u);
|
|
4758
5158
|
}
|
|
4759
|
-
n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (o) => "+".repeat(o.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (o,
|
|
4760
|
-
let
|
|
4761
|
-
return o.slice(0,
|
|
5159
|
+
n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (o) => "+".repeat(o.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (o, u, a) => {
|
|
5160
|
+
let p = a ? a.length : 0;
|
|
5161
|
+
return o.slice(0, p) + "[" + "a".repeat(o.length - p - 2) + "]";
|
|
4762
5162
|
}), n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n;
|
|
4763
5163
|
let s = false, r = "", i = 1 / 0;
|
|
4764
5164
|
for (; e; ) {
|
|
@@ -4812,14 +5212,14 @@ var x = class l {
|
|
|
4812
5212
|
e = e.substring(o.raw.length), t.push(o);
|
|
4813
5213
|
continue;
|
|
4814
5214
|
}
|
|
4815
|
-
let
|
|
5215
|
+
let u = e;
|
|
4816
5216
|
if (this.options.extensions?.startInline) {
|
|
4817
|
-
let a = 1 / 0,
|
|
5217
|
+
let a = 1 / 0, p = e.slice(1), c;
|
|
4818
5218
|
this.options.extensions.startInline.forEach((h) => {
|
|
4819
|
-
c = h.call({ lexer: this },
|
|
4820
|
-
}), a < 1 / 0 && a >= 0 && (
|
|
5219
|
+
c = h.call({ lexer: this }, p), typeof c == "number" && c >= 0 && (a = Math.min(a, c));
|
|
5220
|
+
}), a < 1 / 0 && a >= 0 && (u = e.substring(0, a + 1));
|
|
4821
5221
|
}
|
|
4822
|
-
if (o = this.tokenizer.inlineText(
|
|
5222
|
+
if (o = this.tokenizer.inlineText(u)) {
|
|
4823
5223
|
e = e.substring(o.raw.length), o.raw.slice(-1) !== "_" && (r = o.raw.slice(-1)), s = true;
|
|
4824
5224
|
let a = t.at(-1);
|
|
4825
5225
|
a?.type === "text" ? (a.raw += o.raw, a.text += o.text) : t.push(o);
|
|
@@ -4850,8 +5250,8 @@ var P = class {
|
|
|
4850
5250
|
code({ text: e, lang: t, escaped: n }) {
|
|
4851
5251
|
let s = (t || "").match(m.notSpaceStart)?.[0], r = e.replace(m.endingNewline, "") + `
|
|
4852
5252
|
`;
|
|
4853
|
-
return s ? '<pre><code class="language-' +
|
|
4854
|
-
` : "<pre><code>" + (n ? r :
|
|
5253
|
+
return s ? '<pre><code class="language-' + T(s) + '">' + (n ? r : T(r, true)) + `</code></pre>
|
|
5254
|
+
` : "<pre><code>" + (n ? r : T(r, true)) + `</code></pre>
|
|
4855
5255
|
`;
|
|
4856
5256
|
}
|
|
4857
5257
|
blockquote({ tokens: e }) {
|
|
@@ -4876,8 +5276,8 @@ ${this.parser.parse(e)}</blockquote>
|
|
|
4876
5276
|
list(e) {
|
|
4877
5277
|
let t = e.ordered, n = e.start, s = "";
|
|
4878
5278
|
for (let o = 0; o < e.items.length; o++) {
|
|
4879
|
-
let
|
|
4880
|
-
s += this.listitem(
|
|
5279
|
+
let u = e.items[o];
|
|
5280
|
+
s += this.listitem(u);
|
|
4881
5281
|
}
|
|
4882
5282
|
let r = t ? "ol" : "ul", i = t && n !== 1 ? ' start="' + n + '"' : "";
|
|
4883
5283
|
return "<" + r + i + `>
|
|
@@ -4929,7 +5329,7 @@ ${e}</tr>
|
|
|
4929
5329
|
return `<em>${this.parser.parseInline(e)}</em>`;
|
|
4930
5330
|
}
|
|
4931
5331
|
codespan({ text: e }) {
|
|
4932
|
-
return `<code>${
|
|
5332
|
+
return `<code>${T(e, true)}</code>`;
|
|
4933
5333
|
}
|
|
4934
5334
|
br(e) {
|
|
4935
5335
|
return "<br>";
|
|
@@ -4942,18 +5342,18 @@ ${e}</tr>
|
|
|
4942
5342
|
if (r === null) return s;
|
|
4943
5343
|
e = r;
|
|
4944
5344
|
let i = '<a href="' + e + '"';
|
|
4945
|
-
return t && (i += ' title="' +
|
|
5345
|
+
return t && (i += ' title="' + T(t) + '"'), i += ">" + s + "</a>", i;
|
|
4946
5346
|
}
|
|
4947
5347
|
image({ href: e, title: t, text: n, tokens: s }) {
|
|
4948
5348
|
s && (n = this.parser.parseInline(s, this.parser.textRenderer));
|
|
4949
5349
|
let r = Y(e);
|
|
4950
|
-
if (r === null) return
|
|
5350
|
+
if (r === null) return T(n);
|
|
4951
5351
|
e = r;
|
|
4952
|
-
let i = `<img src="${e}" alt="${
|
|
4953
|
-
return t && (i += ` title="${
|
|
5352
|
+
let i = `<img src="${e}" alt="${T(n)}"`;
|
|
5353
|
+
return t && (i += ` title="${T(t)}"`), i += ">", i;
|
|
4954
5354
|
}
|
|
4955
5355
|
text(e) {
|
|
4956
|
-
return "tokens" in e && e.tokens ? this.parser.parseInline(e.tokens) : "escaped" in e && e.escaped ? e.text :
|
|
5356
|
+
return "tokens" in e && e.tokens ? this.parser.parseInline(e.tokens) : "escaped" in e && e.escaped ? e.text : T(e.text);
|
|
4957
5357
|
}
|
|
4958
5358
|
};
|
|
4959
5359
|
var L = class {
|
|
@@ -5168,7 +5568,7 @@ var S = class {
|
|
|
5168
5568
|
}
|
|
5169
5569
|
};
|
|
5170
5570
|
var Z = class {
|
|
5171
|
-
defaults =
|
|
5571
|
+
defaults = A();
|
|
5172
5572
|
options = this.setOptions;
|
|
5173
5573
|
parse = this.parseMarkdown(true);
|
|
5174
5574
|
parseInline = this.parseMarkdown(false);
|
|
@@ -5214,8 +5614,8 @@ var Z = class {
|
|
|
5214
5614
|
if ("renderer" in r) {
|
|
5215
5615
|
let i = t.renderers[r.name];
|
|
5216
5616
|
i ? t.renderers[r.name] = function(...o) {
|
|
5217
|
-
let
|
|
5218
|
-
return
|
|
5617
|
+
let u = r.renderer.apply(this, o);
|
|
5618
|
+
return u === false && (u = i.apply(this, o)), u;
|
|
5219
5619
|
} : t.renderers[r.name] = r.renderer;
|
|
5220
5620
|
}
|
|
5221
5621
|
if ("tokenizer" in r) {
|
|
@@ -5229,10 +5629,10 @@ var Z = class {
|
|
|
5229
5629
|
for (let i in n.renderer) {
|
|
5230
5630
|
if (!(i in r)) throw new Error(`renderer '${i}' does not exist`);
|
|
5231
5631
|
if (["options", "parser"].includes(i)) continue;
|
|
5232
|
-
let o = i,
|
|
5233
|
-
r[o] = (...
|
|
5234
|
-
let c =
|
|
5235
|
-
return c === false && (c = a.apply(r,
|
|
5632
|
+
let o = i, u = n.renderer[o], a = r[o];
|
|
5633
|
+
r[o] = (...p) => {
|
|
5634
|
+
let c = u.apply(r, p);
|
|
5635
|
+
return c === false && (c = a.apply(r, p)), c || "";
|
|
5236
5636
|
};
|
|
5237
5637
|
}
|
|
5238
5638
|
s.renderer = r;
|
|
@@ -5242,10 +5642,10 @@ var Z = class {
|
|
|
5242
5642
|
for (let i in n.tokenizer) {
|
|
5243
5643
|
if (!(i in r)) throw new Error(`tokenizer '${i}' does not exist`);
|
|
5244
5644
|
if (["options", "rules", "lexer"].includes(i)) continue;
|
|
5245
|
-
let o = i,
|
|
5246
|
-
r[o] = (...
|
|
5247
|
-
let c =
|
|
5248
|
-
return c === false && (c = a.apply(r,
|
|
5645
|
+
let o = i, u = n.tokenizer[o], a = r[o];
|
|
5646
|
+
r[o] = (...p) => {
|
|
5647
|
+
let c = u.apply(r, p);
|
|
5648
|
+
return c === false && (c = a.apply(r, p)), c;
|
|
5249
5649
|
};
|
|
5250
5650
|
}
|
|
5251
5651
|
s.tokenizer = r;
|
|
@@ -5255,21 +5655,21 @@ var Z = class {
|
|
|
5255
5655
|
for (let i in n.hooks) {
|
|
5256
5656
|
if (!(i in r)) throw new Error(`hook '${i}' does not exist`);
|
|
5257
5657
|
if (["options", "block"].includes(i)) continue;
|
|
5258
|
-
let o = i,
|
|
5259
|
-
S.passThroughHooks.has(i) ? r[o] = (
|
|
5658
|
+
let o = i, u = n.hooks[o], a = r[o];
|
|
5659
|
+
S.passThroughHooks.has(i) ? r[o] = (p) => {
|
|
5260
5660
|
if (this.defaults.async && S.passThroughHooksRespectAsync.has(i)) return (async () => {
|
|
5261
|
-
let h = await
|
|
5661
|
+
let h = await u.call(r, p);
|
|
5262
5662
|
return a.call(r, h);
|
|
5263
5663
|
})();
|
|
5264
|
-
let c =
|
|
5664
|
+
let c = u.call(r, p);
|
|
5265
5665
|
return a.call(r, c);
|
|
5266
|
-
} : r[o] = (...
|
|
5666
|
+
} : r[o] = (...p) => {
|
|
5267
5667
|
if (this.defaults.async) return (async () => {
|
|
5268
|
-
let h = await
|
|
5269
|
-
return h === false && (h = await a.apply(r,
|
|
5668
|
+
let h = await u.apply(r, p);
|
|
5669
|
+
return h === false && (h = await a.apply(r, p)), h;
|
|
5270
5670
|
})();
|
|
5271
|
-
let c =
|
|
5272
|
-
return c === false && (c = a.apply(r,
|
|
5671
|
+
let c = u.apply(r, p);
|
|
5672
|
+
return c === false && (c = a.apply(r, p)), c;
|
|
5273
5673
|
};
|
|
5274
5674
|
}
|
|
5275
5675
|
s.hooks = r;
|
|
@@ -5277,8 +5677,8 @@ var Z = class {
|
|
|
5277
5677
|
if (n.walkTokens) {
|
|
5278
5678
|
let r = this.defaults.walkTokens, i = n.walkTokens;
|
|
5279
5679
|
s.walkTokens = function(o) {
|
|
5280
|
-
let
|
|
5281
|
-
return
|
|
5680
|
+
let u = [];
|
|
5681
|
+
return u.push(i.call(this, o)), r && (u = u.concat(r.call(this, o))), u;
|
|
5282
5682
|
};
|
|
5283
5683
|
}
|
|
5284
5684
|
this.defaults = { ...this.defaults, ...s };
|
|
@@ -5300,10 +5700,10 @@ var Z = class {
|
|
|
5300
5700
|
if (typeof n > "u" || n === null) return o(new Error("marked(): input parameter is undefined or null"));
|
|
5301
5701
|
if (typeof n != "string") return o(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected"));
|
|
5302
5702
|
if (i.hooks && (i.hooks.options = i, i.hooks.block = e), i.async) return (async () => {
|
|
5303
|
-
let
|
|
5703
|
+
let u = i.hooks ? await i.hooks.preprocess(n) : n, p = await (i.hooks ? await i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(u, i), c = i.hooks ? await i.hooks.processAllTokens(p) : p;
|
|
5304
5704
|
i.walkTokens && await Promise.all(this.walkTokens(c, i.walkTokens));
|
|
5305
|
-
let
|
|
5306
|
-
return i.hooks ? await i.hooks.postprocess(
|
|
5705
|
+
let d = await (i.hooks ? await i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(c, i);
|
|
5706
|
+
return i.hooks ? await i.hooks.postprocess(d) : d;
|
|
5307
5707
|
})().catch(o);
|
|
5308
5708
|
try {
|
|
5309
5709
|
i.hooks && (n = i.hooks.preprocess(n));
|
|
@@ -5311,8 +5711,8 @@ var Z = class {
|
|
|
5311
5711
|
i.hooks && (a = i.hooks.processAllTokens(a)), i.walkTokens && this.walkTokens(a, i.walkTokens);
|
|
5312
5712
|
let c = (i.hooks ? i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(a, i);
|
|
5313
5713
|
return i.hooks && (c = i.hooks.postprocess(c)), c;
|
|
5314
|
-
} catch (
|
|
5315
|
-
return o(
|
|
5714
|
+
} catch (u) {
|
|
5715
|
+
return o(u);
|
|
5316
5716
|
}
|
|
5317
5717
|
};
|
|
5318
5718
|
}
|
|
@@ -5320,7 +5720,7 @@ var Z = class {
|
|
|
5320
5720
|
return (n) => {
|
|
5321
5721
|
if (n.message += `
|
|
5322
5722
|
Please report this to https://github.com/markedjs/marked.`, e) {
|
|
5323
|
-
let s = "<p>An error occurred:</p><pre>" +
|
|
5723
|
+
let s = "<p>An error occurred:</p><pre>" + T(n.message + "", true) + "</pre>";
|
|
5324
5724
|
return t ? Promise.resolve(s) : s;
|
|
5325
5725
|
}
|
|
5326
5726
|
if (t) return Promise.reject(n);
|
|
@@ -5328,23 +5728,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
5328
5728
|
};
|
|
5329
5729
|
}
|
|
5330
5730
|
};
|
|
5331
|
-
var
|
|
5731
|
+
var M = new Z();
|
|
5332
5732
|
function f(l3, e) {
|
|
5333
|
-
return
|
|
5733
|
+
return M.parse(l3, e);
|
|
5334
5734
|
}
|
|
5335
5735
|
f.options = f.setOptions = function(l3) {
|
|
5336
|
-
return
|
|
5736
|
+
return M.setOptions(l3), f.defaults = M.defaults, j(f.defaults), f;
|
|
5337
5737
|
};
|
|
5338
|
-
f.getDefaults =
|
|
5738
|
+
f.getDefaults = A;
|
|
5339
5739
|
f.defaults = R;
|
|
5340
|
-
function
|
|
5341
|
-
return
|
|
5740
|
+
function dt(...l3) {
|
|
5741
|
+
return M.use(...l3), f.defaults = M.defaults, j(f.defaults), f;
|
|
5342
5742
|
}
|
|
5343
|
-
f.use =
|
|
5743
|
+
f.use = dt;
|
|
5344
5744
|
f.walkTokens = function(l3, e) {
|
|
5345
|
-
return
|
|
5745
|
+
return M.walkTokens(l3, e);
|
|
5346
5746
|
};
|
|
5347
|
-
f.parseInline =
|
|
5747
|
+
f.parseInline = M.parseInline;
|
|
5348
5748
|
f.Parser = b;
|
|
5349
5749
|
f.parser = b.parse;
|
|
5350
5750
|
f.Renderer = P;
|
|
@@ -5359,7 +5759,7 @@ var rn = f.setOptions;
|
|
|
5359
5759
|
var sn = f.walkTokens;
|
|
5360
5760
|
var on = f.parseInline;
|
|
5361
5761
|
var ln = b.parse;
|
|
5362
|
-
var
|
|
5762
|
+
var un = x.lex;
|
|
5363
5763
|
|
|
5364
5764
|
// src/ui/render_markdown.ts
|
|
5365
5765
|
var parser = new Z({ gfm: true, breaks: true });
|
|
@@ -5443,8 +5843,8 @@ function renderMarkdown(text4, options) {
|
|
|
5443
5843
|
}
|
|
5444
5844
|
|
|
5445
5845
|
// src/ui/resize_handle.ts
|
|
5446
|
-
var
|
|
5447
|
-
var
|
|
5846
|
+
var MIN_WIDTH2 = 280;
|
|
5847
|
+
var MIN_HEIGHT2 = 240;
|
|
5448
5848
|
var STEP2 = 16;
|
|
5449
5849
|
var COARSE_STEP2 = 64;
|
|
5450
5850
|
function createResizeHandle(grip, options) {
|
|
@@ -5526,11 +5926,11 @@ function movable(grip, axis) {
|
|
|
5526
5926
|
return axis === "both" || grip.x !== void 0;
|
|
5527
5927
|
}
|
|
5528
5928
|
function boxAt(grip, axis, rect, x2, y2) {
|
|
5529
|
-
const left = grip.x === "left" ? Math.min(x2, rect.right -
|
|
5530
|
-
const right = grip.x === "right" ? Math.max(x2, rect.left +
|
|
5929
|
+
const left = grip.x === "left" ? Math.min(x2, rect.right - MIN_WIDTH2) : rect.left;
|
|
5930
|
+
const right = grip.x === "right" ? Math.max(x2, rect.left + MIN_WIDTH2) : rect.right;
|
|
5531
5931
|
const vertical = axis === "both";
|
|
5532
|
-
const top = vertical && grip.y === "top" ? Math.min(y2, rect.bottom -
|
|
5533
|
-
const bottom = vertical && grip.y === "bottom" ? Math.max(y2, rect.top +
|
|
5932
|
+
const top = vertical && grip.y === "top" ? Math.min(y2, rect.bottom - MIN_HEIGHT2) : rect.top;
|
|
5933
|
+
const bottom = vertical && grip.y === "bottom" ? Math.max(y2, rect.top + MIN_HEIGHT2) : rect.bottom;
|
|
5534
5934
|
return { left, top, right, bottom };
|
|
5535
5935
|
}
|
|
5536
5936
|
|
|
@@ -5570,7 +5970,7 @@ function wrapWords(root) {
|
|
|
5570
5970
|
}
|
|
5571
5971
|
|
|
5572
5972
|
// src/ui/run_notice.ts
|
|
5573
|
-
function renderRunNotice(icon, text4, kind) {
|
|
5973
|
+
function renderRunNotice(icon, text4, kind, undo) {
|
|
5574
5974
|
const notice = document.createElement("div");
|
|
5575
5975
|
notice.className = `run-notice run-notice--${kind}`;
|
|
5576
5976
|
notice.setAttribute("part", `run-notice run-notice-${kind}`);
|
|
@@ -5585,6 +5985,18 @@ function renderRunNotice(icon, text4, kind) {
|
|
|
5585
5985
|
label.setAttribute("part", "run-notice-text");
|
|
5586
5986
|
label.textContent = text4;
|
|
5587
5987
|
notice.append(glyph, label);
|
|
5988
|
+
if (undo !== void 0) {
|
|
5989
|
+
const button2 = document.createElement("button");
|
|
5990
|
+
button2.type = "button";
|
|
5991
|
+
button2.className = "run-notice-undo";
|
|
5992
|
+
button2.setAttribute("part", "run-notice-undo");
|
|
5993
|
+
button2.textContent = undo.label;
|
|
5994
|
+
button2.addEventListener("click", () => {
|
|
5995
|
+
button2.disabled = true;
|
|
5996
|
+
undo.onActivate();
|
|
5997
|
+
});
|
|
5998
|
+
notice.append(button2);
|
|
5999
|
+
}
|
|
5588
6000
|
return notice;
|
|
5589
6001
|
}
|
|
5590
6002
|
|
|
@@ -5842,6 +6254,11 @@ var STYLES = `
|
|
|
5842
6254
|
/* Message action row: the control box and the mark inside it. The box has a
|
|
5843
6255
|
floor of 24px so it stays a reliable target at every density. */
|
|
5844
6256
|
--_action-size: var(--ag-ui-action-size, 28px);
|
|
6257
|
+
/* The header's own controls. Sized rather than left to their contents: a row
|
|
6258
|
+
of buttons each as wide as the glyph inside it comes out uneven, and uneven
|
|
6259
|
+
buttons two pixels apart read as one smudge rather than five controls. */
|
|
6260
|
+
--_header-btn-size: var(--ag-ui-header-btn-size, 30px);
|
|
6261
|
+
--_header-gap: var(--ag-ui-header-gap, 4px);
|
|
5845
6262
|
--_action-icon-size: var(--ag-ui-action-icon-size, 15px);
|
|
5846
6263
|
--_tooltip-bg: var(--ag-ui-tooltip-bg, #1f2430);
|
|
5847
6264
|
--_tooltip-fg: var(--ag-ui-tooltip-fg, #f5f6fa);
|
|
@@ -5903,6 +6320,11 @@ var STYLES = `
|
|
|
5903
6320
|
--_glyph-size: var(--ag-ui-glyph-size, 18px);
|
|
5904
6321
|
--_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);
|
|
5905
6322
|
|
|
6323
|
+
/* How wide a chart is allowed to get. A cap rather than a width: below it
|
|
6324
|
+
the chart fills its column, and above it a wider panel is just a wider
|
|
6325
|
+
panel. */
|
|
6326
|
+
--_chart-max-width: var(--ag-ui-chart-max-width, 480px);
|
|
6327
|
+
|
|
5906
6328
|
/* Unread badge on the launcher. */
|
|
5907
6329
|
--_badge-bg: var(--ag-ui-badge-bg, var(--_danger));
|
|
5908
6330
|
--_badge-fg: var(--ag-ui-badge-fg, #ffffff);
|
|
@@ -5931,6 +6353,68 @@ var STYLES = `
|
|
|
5931
6353
|
--_msg-pad: var(--ag-ui-msg-pad, 8px 12px);
|
|
5932
6354
|
--_msg-radius: var(--ag-ui-msg-radius, 14px);
|
|
5933
6355
|
|
|
6356
|
+
/* Edges of the viewport the host has already spent, and that a fixed
|
|
6357
|
+
placement must therefore stay out of: a sticky nav bar, a docked toolbar,
|
|
6358
|
+
a device's safe area. Four longhands rather than one shorthand because a
|
|
6359
|
+
custom property is a token stream and CSS cannot index one -- the height
|
|
6360
|
+
arithmetic below needs the vertical pair on its own.
|
|
6361
|
+
|
|
6362
|
+
Every fixed placement derives from these, and the heights subtract them in
|
|
6363
|
+
one place. A host that reserved its chrome by restating --ag-ui-inset per
|
|
6364
|
+
placement had to keep --ag-ui-height in step by hand, and forgetting it
|
|
6365
|
+
overflowed the panel past the bottom of the screen with nothing to say so.
|
|
6366
|
+
|
|
6367
|
+
These take env(safe-area-inset-*) verbatim, which is what a full-bleed
|
|
6368
|
+
placement wants on a device with a notch. */
|
|
6369
|
+
--_viewport-inset-top: var(--ag-ui-viewport-inset-top, 0px);
|
|
6370
|
+
--_viewport-inset-right: var(--ag-ui-viewport-inset-right, 0px);
|
|
6371
|
+
--_viewport-inset-bottom: var(--ag-ui-viewport-inset-bottom, 0px);
|
|
6372
|
+
--_viewport-inset-left: var(--ag-ui-viewport-inset-left, 0px);
|
|
6373
|
+
/* What is left of the viewport once the host's chrome is taken out, on both
|
|
6374
|
+
axes, so a placement never composes this itself and no host can subtract
|
|
6375
|
+
an edge from the position and forget it in the height.
|
|
6376
|
+
|
|
6377
|
+
Settable in their own right, and that is not only convention: an on-screen
|
|
6378
|
+
keyboard changes no viewport-percentage length -- not vh, not dvh, not svh
|
|
6379
|
+
-- so a full-bleed panel on a phone has to be told the height rather than
|
|
6380
|
+
deriving it. The value to publish there is the visual viewport's. */
|
|
6381
|
+
--_viewport-height: var(--ag-ui-viewport-height, var(--_visual-viewport-height));
|
|
6382
|
+
/* The measured height of the part of the screen the user can actually see,
|
|
6383
|
+
written by the element from the visual viewport and falling back to the
|
|
6384
|
+
layout viewport where nothing has measured yet.
|
|
6385
|
+
|
|
6386
|
+
This is the on-screen keyboard, and it needs measuring because no CSS
|
|
6387
|
+
length describes it: an on-screen keyboard has no effect on any
|
|
6388
|
+
viewport-percentage unit, so 100vh, 100dvh and 100svh are all the same
|
|
6389
|
+
number with the keyboard up as without it. A full-bleed panel sized from
|
|
6390
|
+
any of them puts its composer behind the keyboard the user is typing into.
|
|
6391
|
+
|
|
6392
|
+
Separate from the token above so a host that states the usable height
|
|
6393
|
+
outright still wins: the element writes this one inline, and an inline
|
|
6394
|
+
value would otherwise outrank the host's own rule. */
|
|
6395
|
+
--_visual-viewport-height: var(
|
|
6396
|
+
--ag-ui-visual-viewport-height,
|
|
6397
|
+
calc(100vh - var(--_viewport-inset-top) - var(--_viewport-inset-bottom))
|
|
6398
|
+
);
|
|
6399
|
+
/* How much of the layout viewport is hidden below the visible one, measured
|
|
6400
|
+
and written by the element alongside the height above.
|
|
6401
|
+
|
|
6402
|
+
A shorter panel is not enough on its own for anything anchored to the
|
|
6403
|
+
bottom. A floating widget is positioned against the layout viewport, so
|
|
6404
|
+
with a keyboard up its bottom edge -- and the launcher that lives at that
|
|
6405
|
+
corner -- sits behind the keyboard however tall the panel is. This is what
|
|
6406
|
+
lifts it clear. */
|
|
6407
|
+
/* Two tokens, for the same reason the height above has two: the element
|
|
6408
|
+
writes the measurement inline, and a host that states its own value needs
|
|
6409
|
+
a knob that outranks that write rather than one the next write replaces.
|
|
6410
|
+
A host wanting no keyboard lift at all sets --ag-ui-keyboard-inset: 0px. */
|
|
6411
|
+
--_keyboard-inset: var(--ag-ui-keyboard-inset, var(--_visual-viewport-inset-bottom));
|
|
6412
|
+
--_visual-viewport-inset-bottom: var(--ag-ui-visual-viewport-inset-bottom, 0px);
|
|
6413
|
+
--_viewport-width: var(
|
|
6414
|
+
--ag-ui-viewport-width,
|
|
6415
|
+
calc(100vw - var(--_viewport-inset-left) - var(--_viewport-inset-right))
|
|
6416
|
+
);
|
|
6417
|
+
|
|
5934
6418
|
/* Layout \u2014 override from outside to dock the widget anywhere.
|
|
5935
6419
|
Set --ag-ui-position: static (and place this element in your own
|
|
5936
6420
|
grid/flex layout) to embed it in the page flow instead of floating. */
|
|
@@ -5938,14 +6422,49 @@ var STYLES = `
|
|
|
5938
6422
|
--_z-index: var(--ag-ui-z-index, 2147483000);
|
|
5939
6423
|
--_width: var(--ag-ui-width, 380px);
|
|
5940
6424
|
--_height: var(--ag-ui-height, 560px);
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
6425
|
+
/* The gutter a resting floating panel keeps between itself and the edge of
|
|
6426
|
+
the box the host left free. One number, because the inset below spends it
|
|
6427
|
+
and the cap beneath has to know it was spent. */
|
|
6428
|
+
--_edge-gutter: var(--ag-ui-edge-gutter, 24px);
|
|
6429
|
+
--_inset: var(
|
|
6430
|
+
--ag-ui-inset,
|
|
6431
|
+
auto calc(var(--_edge-gutter) + var(--_viewport-inset-right))
|
|
6432
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset)) auto
|
|
6433
|
+
);
|
|
6434
|
+
/* The cap is what the host left free, less the *one* gutter the inset above
|
|
6435
|
+
spends on the anchored edge -- not two, and not none.
|
|
6436
|
+
|
|
6437
|
+
Two was the first answer and it was felt on one axis only. The arithmetic
|
|
6438
|
+
says why: the default panel is 560 tall against a cap of the viewport
|
|
6439
|
+
minus 48, which on an 800px screen with a header reserved is 72px of
|
|
6440
|
+
headroom, so the height reaches its cap almost immediately; the default
|
|
6441
|
+
width is 380 against a cap near 1230, which is 850px nothing ever reaches.
|
|
6442
|
+
Once the size is capped a grip cannot grow the panel, so a pull on the
|
|
6443
|
+
anchored edge is written as position instead and the panel travels, and a
|
|
6444
|
+
pull on the free edge stops a whole gutter short of an edge a drag can
|
|
6445
|
+
reach.
|
|
6446
|
+
|
|
6447
|
+
None was the correction, and it overshot: the panel is anchored bottom-
|
|
6448
|
+
right with a gutter already spent there, so a cap of the full usable
|
|
6449
|
+
height puts the far edge exactly one gutter outside it. On an 800px screen
|
|
6450
|
+
with nothing reserved that is a top of -24 -- the header, and every
|
|
6451
|
+
control in it, off the top of the window. Reserve a 120px header and it
|
|
6452
|
+
sits inside that instead, which is the one thing the reservation exists to
|
|
6453
|
+
prevent.
|
|
6454
|
+
|
|
6455
|
+
One is the fixed point of both complaints. A resting panel grown to the
|
|
6456
|
+
cap runs from the usable near edge to its gutter on the far one, so the
|
|
6457
|
+
near edge is reachable by a resize exactly as it is by a drag, and neither
|
|
6458
|
+
can put any part of the panel outside the box. */
|
|
6459
|
+
--_max-width: var(--ag-ui-max-width, calc(var(--_viewport-width) - var(--_edge-gutter)));
|
|
6460
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_edge-gutter)));
|
|
5944
6461
|
/* Reading-column width for placement="page" (full-bleed, centred content). */
|
|
5945
6462
|
--_content-max-width: var(--ag-ui-content-max-width, 820px);
|
|
5946
6463
|
/* Slim rail the sidebar placement collapses to. Only that placement reads
|
|
5947
6464
|
it, but it is declared here so every alias has a default in one place. */
|
|
5948
6465
|
--_rail-width: var(--ag-ui-rail-width, 52px);
|
|
6466
|
+
/* Width of the docked conversation list on a full-page chat. */
|
|
6467
|
+
--_threads-rail-width: var(--ag-ui-threads-rail-width, 280px);
|
|
5949
6468
|
|
|
5950
6469
|
position: var(--_position);
|
|
5951
6470
|
inset: var(--_inset);
|
|
@@ -6016,23 +6535,30 @@ var STYLES = `
|
|
|
6016
6535
|
|
|
6017
6536
|
/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
6018
6537
|
:host([placement="bottom-left"]) {
|
|
6019
|
-
--_inset: var(
|
|
6538
|
+
--_inset: var(
|
|
6539
|
+
--ag-ui-inset,
|
|
6540
|
+
auto auto calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset))
|
|
6541
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-left))
|
|
6542
|
+
);
|
|
6020
6543
|
}
|
|
6021
6544
|
|
|
6022
6545
|
:host([placement="side"]) {
|
|
6023
|
-
--_inset: var(--ag-ui-inset,
|
|
6546
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
6024
6547
|
--_width: var(--ag-ui-width, 420px);
|
|
6025
|
-
--_height: var(--ag-ui-height,
|
|
6026
|
-
--_max-height: var(--ag-ui-max-height,
|
|
6548
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6549
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6027
6550
|
--_radius: var(--ag-ui-radius, 0);
|
|
6028
6551
|
}
|
|
6029
6552
|
|
|
6030
6553
|
:host([placement="full"]) {
|
|
6031
|
-
--_inset: var(
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
--
|
|
6554
|
+
--_inset: var(
|
|
6555
|
+
--ag-ui-inset,
|
|
6556
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
|
|
6557
|
+
);
|
|
6558
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
6559
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6560
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
6561
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6036
6562
|
--_radius: var(--ag-ui-radius, 0);
|
|
6037
6563
|
}
|
|
6038
6564
|
|
|
@@ -6042,11 +6568,14 @@ var STYLES = `
|
|
|
6042
6568
|
composer rather than a per-row wrapper, so user pills still right-align and
|
|
6043
6569
|
the assistant well spans the column. */
|
|
6044
6570
|
:host([placement="page"]) {
|
|
6045
|
-
--_inset: var(
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
--
|
|
6571
|
+
--_inset: var(
|
|
6572
|
+
--ag-ui-inset,
|
|
6573
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
|
|
6574
|
+
);
|
|
6575
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
6576
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6577
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
6578
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6050
6579
|
--_radius: var(--ag-ui-radius, 0);
|
|
6051
6580
|
}
|
|
6052
6581
|
|
|
@@ -6077,28 +6606,100 @@ var STYLES = `
|
|
|
6077
6606
|
max-width: 100%;
|
|
6078
6607
|
}
|
|
6079
6608
|
|
|
6609
|
+
/* Small viewports: one shape, reached from whichever placement the host chose.
|
|
6610
|
+
|
|
6611
|
+
A phone is not an eighth placement, it is an override that collapses the
|
|
6612
|
+
others onto one of them. The host picked a placement for the desktop it was
|
|
6613
|
+
designing; a 380x560 panel with a 24px margin is not a smaller version of
|
|
6614
|
+
that decision, it is most of the screen with a frame drawn round it.
|
|
6615
|
+
|
|
6616
|
+
The breakpoint is a literal because a custom property cannot be read in a
|
|
6617
|
+
media query -- which is exactly why there is an opt-out. Everything the block
|
|
6618
|
+
sets is a token a host can re-state, but the *trigger* is unreachable, so a
|
|
6619
|
+
host whose layout wants a different threshold, or none, sets
|
|
6620
|
+
data-small-viewport="off" and keeps its desktop shape at every width. 600px is above every common phone in portrait and below every
|
|
6621
|
+
tablet in landscape, and it is a width rather than a pointer test on purpose:
|
|
6622
|
+
a touch laptop is coarse-pointered and wide, a narrow desktop window is
|
|
6623
|
+
fine-pointered and small, and conflating the two gets both wrong. What the
|
|
6624
|
+
pointer decides is which controls make sense, further down.
|
|
6625
|
+
|
|
6626
|
+
Two placements are left alone. "page" is already this shape. "embedded" sits
|
|
6627
|
+
in a box the host sized and placed, and taking that over would break the app
|
|
6628
|
+
shell it was embedded into -- the host is the only party that knows whether
|
|
6629
|
+
its column should become the whole screen. */
|
|
6630
|
+
@media (max-width: 600px) {
|
|
6631
|
+
:host([placement="floating"]:not([data-small-viewport="off"])),
|
|
6632
|
+
:host([placement="bottom-left"]:not([data-small-viewport="off"])),
|
|
6633
|
+
:host([placement="sidebar"]:not([data-small-viewport="off"])),
|
|
6634
|
+
:host([placement="side"]:not([data-small-viewport="off"])),
|
|
6635
|
+
:host(:not([placement]):not([data-small-viewport="off"])),
|
|
6636
|
+
:host([placement=""]:not([data-small-viewport="off"])) {
|
|
6637
|
+
--_inset: var(
|
|
6638
|
+
--ag-ui-inset,
|
|
6639
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right)
|
|
6640
|
+
calc(var(--_viewport-inset-bottom) + var(--_keyboard-inset))
|
|
6641
|
+
var(--_viewport-inset-left)
|
|
6642
|
+
);
|
|
6643
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
6644
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6645
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
6646
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6647
|
+
--_radius: var(--ag-ui-radius, 0);
|
|
6648
|
+
--_shadow: var(--ag-ui-shadow, none);
|
|
6649
|
+
}
|
|
6650
|
+
|
|
6651
|
+
/* Nothing to resize once the panel is the screen, and the grips would sit
|
|
6652
|
+
under the thumbs holding the phone. */
|
|
6653
|
+
:host([placement="floating"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6654
|
+
:host([placement="bottom-left"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6655
|
+
:host([placement="sidebar"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6656
|
+
:host([placement="side"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6657
|
+
:host(:not([placement]):not([data-small-viewport="off"])) .resize-handle,
|
|
6658
|
+
:host([placement=""]:not([data-small-viewport="off"])) .resize-handle {
|
|
6659
|
+
display: none;
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6080
6663
|
/* Sidebar: a full-height docked panel that slides open/closed and
|
|
6081
6664
|
collapses to a slim icon rail (not the floating launcher). Docked right by
|
|
6082
6665
|
default; data-side="left" docks it left. Overlay by default \u2014 set
|
|
6083
6666
|
--ag-ui-position: static (and place this element in your own layout) for a
|
|
6084
6667
|
host-managed push instead. */
|
|
6085
6668
|
:host([placement="sidebar"]) {
|
|
6086
|
-
--_inset: var(--ag-ui-inset,
|
|
6669
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
6087
6670
|
--_width: var(--ag-ui-width, 420px);
|
|
6088
|
-
--_height: var(--ag-ui-height,
|
|
6089
|
-
--_max-height: var(--ag-ui-max-height,
|
|
6671
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6672
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6090
6673
|
--_radius: var(--ag-ui-radius, 0);
|
|
6091
6674
|
transition: width var(--_motion) var(--_ease);
|
|
6092
6675
|
}
|
|
6093
6676
|
|
|
6094
6677
|
:host([placement="sidebar"][data-side="left"]) {
|
|
6095
|
-
--_inset: var(--ag-ui-inset,
|
|
6678
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) auto var(--_viewport-inset-bottom) var(--_viewport-inset-left));
|
|
6096
6679
|
}
|
|
6097
6680
|
|
|
6098
6681
|
/* The docked panel is pinned to the edge it docks against rather than filling
|
|
6099
6682
|
the host as a flex child. Collapsing shrinks the host to the rail width, and
|
|
6100
6683
|
a flex child would be squashed to 52px on the way out instead of sliding out
|
|
6101
6684
|
at full width. */
|
|
6685
|
+
/* The panel is taken out of flow so the collapse can slide it out at full
|
|
6686
|
+
width; see the note above. That makes the host its containing block, and the
|
|
6687
|
+
host is only one by accident: it is position: fixed by default. A host that
|
|
6688
|
+
takes the documented route to a pushed layout instead of an overlay sets
|
|
6689
|
+
--ag-ui-position: static, and a static element establishes nothing -- so the
|
|
6690
|
+
panel resolved against the initial containing block, landed at the document
|
|
6691
|
+
origin, and scrolled away with the page. Docked left it pinned to the
|
|
6692
|
+
document's left edge rather than the host's.
|
|
6693
|
+
|
|
6694
|
+
It looked correct wherever it was first tried, because a full-height column
|
|
6695
|
+
at the top of an unscrolled document is exactly where those two answers
|
|
6696
|
+
coincide. Containment makes the host a containing block whatever its
|
|
6697
|
+
position, so the panel stays in the box the host was given. It is a no-op in
|
|
6698
|
+
the default case, which is already positioned. */
|
|
6699
|
+
:host([placement="sidebar"]) {
|
|
6700
|
+
contain: layout;
|
|
6701
|
+
}
|
|
6702
|
+
|
|
6102
6703
|
:host([placement="sidebar"]) .chat {
|
|
6103
6704
|
position: absolute;
|
|
6104
6705
|
inset: 0 0 0 auto;
|
|
@@ -6114,11 +6715,15 @@ var STYLES = `
|
|
|
6114
6715
|
/* Collapsed sidebar: shrink the host to the rail width and slide the panel out
|
|
6115
6716
|
through the edge it docks against. Higher specificity than the generic
|
|
6116
6717
|
collapse rules, so it wins regardless of source order. */
|
|
6718
|
+
/* The rail is the full height of what the host left, not of the screen. It
|
|
6719
|
+
said 100vh and pinned its own bottom, which put it under any chrome the host
|
|
6720
|
+
had reserved -- and the icon lives at the top of the rail, so the one control
|
|
6721
|
+
that reopens the panel was the first thing to disappear behind a sticky
|
|
6722
|
+
header. */
|
|
6117
6723
|
:host([placement="sidebar"][collapsed]) {
|
|
6118
6724
|
width: var(--_rail-width);
|
|
6119
|
-
height:
|
|
6120
|
-
max-height:
|
|
6121
|
-
bottom: 0;
|
|
6725
|
+
height: var(--_viewport-height);
|
|
6726
|
+
max-height: var(--_viewport-height);
|
|
6122
6727
|
pointer-events: auto;
|
|
6123
6728
|
}
|
|
6124
6729
|
|
|
@@ -6234,17 +6839,83 @@ var STYLES = `
|
|
|
6234
6839
|
|
|
6235
6840
|
/* The sidebar collapses to an edge rail instead: full height, square, flush
|
|
6236
6841
|
against the dock. It slides in with the panel rather than popping. */
|
|
6842
|
+
/* The edge rail. It reads as the docked edge of a panel rather than a coloured
|
|
6843
|
+
stripe: the surface the panel is made of, a border on the side it docks
|
|
6844
|
+
against, and the accent kept for the icon -- a full-height slab of accent is
|
|
6845
|
+
the loudest thing on the page and says the least about itself.
|
|
6846
|
+
|
|
6847
|
+
Content sits at the top rather than centred, because a control floating in
|
|
6848
|
+
the middle of a screen-high column has nothing to belong to. */
|
|
6237
6849
|
:host([placement="sidebar"][collapsed]) .launcher {
|
|
6238
6850
|
inset: 0;
|
|
6239
6851
|
width: auto;
|
|
6240
6852
|
height: auto;
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6853
|
+
flex-direction: column;
|
|
6854
|
+
align-items: center;
|
|
6855
|
+
justify-content: flex-start;
|
|
6856
|
+
gap: 12px;
|
|
6857
|
+
padding-top: 14px;
|
|
6858
|
+
border: 0;
|
|
6859
|
+
border-inline-start: 1px solid var(--_border);
|
|
6244
6860
|
border-radius: 0;
|
|
6861
|
+
background: var(--_bg);
|
|
6862
|
+
color: var(--_fg);
|
|
6863
|
+
box-shadow: none;
|
|
6864
|
+
transform: none;
|
|
6865
|
+
}
|
|
6866
|
+
|
|
6867
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .launcher {
|
|
6868
|
+
border-inline-start: 0;
|
|
6869
|
+
border-inline-end: 1px solid var(--_border);
|
|
6870
|
+
}
|
|
6871
|
+
|
|
6872
|
+
/* The icon keeps the accent, so there is one obvious thing to press.
|
|
6873
|
+
|
|
6874
|
+
The glyph is sized here rather than left to fill the holder. A glyph in an
|
|
6875
|
+
icon holder takes the holder's whole box by default, which is right where the
|
|
6876
|
+
holder is only a box and wrong the moment it becomes a filled circle: the
|
|
6877
|
+
mark then runs edge to edge and reads as a square crammed into a disc. The
|
|
6878
|
+
proportion is the floating launcher's own -- a 26px glyph in a 56px bubble --
|
|
6879
|
+
so the two collapsed states look like the same widget. */
|
|
6880
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder {
|
|
6881
|
+
display: inline-flex;
|
|
6882
|
+
align-items: center;
|
|
6883
|
+
justify-content: center;
|
|
6884
|
+
width: 34px;
|
|
6885
|
+
height: 34px;
|
|
6886
|
+
border-radius: 50%;
|
|
6245
6887
|
background: var(--_header-bg);
|
|
6246
6888
|
color: var(--_header-fg);
|
|
6247
|
-
|
|
6889
|
+
}
|
|
6890
|
+
|
|
6891
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder .glyph,
|
|
6892
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder .icon-img {
|
|
6893
|
+
width: 16px;
|
|
6894
|
+
height: 16px;
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6897
|
+
/* Written down the rail, which is the only direction it fits. Reading upward
|
|
6898
|
+
is the convention for a right-hand edge and matches how a docked panel's
|
|
6899
|
+
label is set everywhere it appears. */
|
|
6900
|
+
.rail-label {
|
|
6901
|
+
display: none;
|
|
6902
|
+
}
|
|
6903
|
+
|
|
6904
|
+
:host([placement="sidebar"][collapsed]) .rail-label {
|
|
6905
|
+
display: block;
|
|
6906
|
+
writing-mode: vertical-rl;
|
|
6907
|
+
transform: rotate(180deg);
|
|
6908
|
+
max-height: calc(100% - 96px);
|
|
6909
|
+
overflow: hidden;
|
|
6910
|
+
font-size: 0.9em;
|
|
6911
|
+
font-weight: 600;
|
|
6912
|
+
letter-spacing: 0.02em;
|
|
6913
|
+
white-space: nowrap;
|
|
6914
|
+
text-overflow: ellipsis;
|
|
6915
|
+
}
|
|
6916
|
+
|
|
6917
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .rail-label {
|
|
6918
|
+
writing-mode: vertical-rl;
|
|
6248
6919
|
transform: none;
|
|
6249
6920
|
}
|
|
6250
6921
|
|
|
@@ -6293,6 +6964,41 @@ var STYLES = `
|
|
|
6293
6964
|
color: var(--_header-fg);
|
|
6294
6965
|
}
|
|
6295
6966
|
|
|
6967
|
+
/* The header is the panel's title bar: on the placements that let the widget
|
|
6968
|
+
be moved it drags the whole thing, matching the launcher's own drag. The
|
|
6969
|
+
selector lists those placements rather than asking JS to stamp an attribute,
|
|
6970
|
+
because a cursor that appears one frame late reads as a control that only
|
|
6971
|
+
sometimes exists. touch-action keeps a finger drag here moving the panel
|
|
6972
|
+
instead of scrolling the page behind it. */
|
|
6973
|
+
:host(:not([placement])) .header,
|
|
6974
|
+
:host([placement=""]) .header,
|
|
6975
|
+
:host([placement="floating"]) .header,
|
|
6976
|
+
:host([placement="bottom-left"]) .header {
|
|
6977
|
+
cursor: move;
|
|
6978
|
+
touch-action: none;
|
|
6979
|
+
}
|
|
6980
|
+
|
|
6981
|
+
/* A host that turned the drag off keeps a header that says so. */
|
|
6982
|
+
:host([data-launcher-drag="false"]) .header {
|
|
6983
|
+
cursor: auto;
|
|
6984
|
+
touch-action: auto;
|
|
6985
|
+
}
|
|
6986
|
+
|
|
6987
|
+
/* :host is carried for the specificity, not the scope: the placement rules
|
|
6988
|
+
above are a host selector plus a class plus an attribute, and a bare
|
|
6989
|
+
.header[data-dragging] loses to them -- silently, because the drag still
|
|
6990
|
+
works and only the cursor is wrong. */
|
|
6991
|
+
:host .header[data-dragging] {
|
|
6992
|
+
cursor: grabbing;
|
|
6993
|
+
}
|
|
6994
|
+
|
|
6995
|
+
/* The cursor inherits, so a control a host slots into the header would show
|
|
6996
|
+
the drag cursor over something the drag deliberately ignores. Named to
|
|
6997
|
+
match the controls panel_drag steps aside for. */
|
|
6998
|
+
.header ::slotted(:is(button, a, input, select, textarea)) {
|
|
6999
|
+
cursor: pointer;
|
|
7000
|
+
}
|
|
7001
|
+
|
|
6296
7002
|
.header-title {
|
|
6297
7003
|
flex: 1;
|
|
6298
7004
|
min-width: 0;
|
|
@@ -6323,17 +7029,24 @@ var STYLES = `
|
|
|
6323
7029
|
|
|
6324
7030
|
.header-controls {
|
|
6325
7031
|
display: flex;
|
|
6326
|
-
gap:
|
|
7032
|
+
gap: var(--_header-gap);
|
|
6327
7033
|
flex: none;
|
|
6328
7034
|
}
|
|
6329
7035
|
|
|
6330
7036
|
.header-btn {
|
|
7037
|
+
display: inline-flex;
|
|
7038
|
+
flex: 0 0 auto;
|
|
7039
|
+
align-items: center;
|
|
7040
|
+
justify-content: center;
|
|
7041
|
+
box-sizing: border-box;
|
|
7042
|
+
width: var(--_header-btn-size);
|
|
7043
|
+
height: var(--_header-btn-size);
|
|
6331
7044
|
border: none;
|
|
6332
7045
|
background: transparent;
|
|
6333
7046
|
color: inherit;
|
|
6334
7047
|
font: inherit;
|
|
6335
7048
|
line-height: 1;
|
|
6336
|
-
padding:
|
|
7049
|
+
padding: 0;
|
|
6337
7050
|
border-radius: 6px;
|
|
6338
7051
|
cursor: pointer;
|
|
6339
7052
|
opacity: 0.85;
|
|
@@ -6355,9 +7068,9 @@ var STYLES = `
|
|
|
6355
7068
|
Nothing paints there once the panel is gone, so the box only has to stop
|
|
6356
7069
|
swallowing clicks: pointer events go to none and the launcher takes them.
|
|
6357
7070
|
|
|
6358
|
-
Two placements
|
|
6359
|
-
|
|
6360
|
-
|
|
7071
|
+
Two placements do not use it: "sidebar" slides to its rail (below), and
|
|
7072
|
+
"embedded" keeps its header bar, having no corner for a floating circle that
|
|
7073
|
+
would escape the host's layout. "page" has no collapsed state at all. */
|
|
6361
7074
|
:host([collapsed]) {
|
|
6362
7075
|
pointer-events: none;
|
|
6363
7076
|
/* A collapsed host has to be allowed to shrink, and in the layout hosts
|
|
@@ -6419,56 +7132,126 @@ var STYLES = `
|
|
|
6419
7132
|
transform-origin: bottom right;
|
|
6420
7133
|
}
|
|
6421
7134
|
|
|
6422
|
-
/* The
|
|
6423
|
-
|
|
6424
|
-
:host([collapsed]
|
|
7135
|
+
/* The in-flow placement keeps the original collapse: hide the body, let the
|
|
7136
|
+
host shrink to the header bar. */
|
|
7137
|
+
:host([collapsed][placement="embedded"]) {
|
|
6425
7138
|
height: auto;
|
|
6426
7139
|
max-height: none;
|
|
6427
7140
|
pointer-events: auto;
|
|
6428
7141
|
}
|
|
6429
7142
|
|
|
6430
|
-
:host([collapsed]
|
|
7143
|
+
:host([collapsed][placement="embedded"]) .chat {
|
|
6431
7144
|
opacity: 1;
|
|
6432
7145
|
transform: none;
|
|
6433
7146
|
visibility: visible;
|
|
6434
7147
|
}
|
|
6435
7148
|
|
|
6436
|
-
/*
|
|
7149
|
+
/* It keeps the header bar, so the launcher must stay out of the way: an
|
|
6437
7150
|
embedded host is position: static, which would let an absolutely-positioned
|
|
6438
7151
|
circle escape the layout and land against whatever the page positions. */
|
|
6439
|
-
:host([collapsed]
|
|
7152
|
+
:host([collapsed][placement="embedded"]) .launcher {
|
|
6440
7153
|
visibility: hidden;
|
|
6441
7154
|
opacity: 0;
|
|
6442
7155
|
}
|
|
6443
7156
|
|
|
6444
|
-
:host([collapsed]
|
|
6445
|
-
:host([collapsed]
|
|
6446
|
-
:host([collapsed]
|
|
6447
|
-
:host([collapsed]
|
|
6448
|
-
:host([collapsed]
|
|
6449
|
-
:host([collapsed]
|
|
7157
|
+
:host([collapsed][placement="embedded"]) .messages-wrap,
|
|
7158
|
+
:host([collapsed][placement="embedded"]) .messages,
|
|
7159
|
+
:host([collapsed][placement="embedded"]) .input-row,
|
|
7160
|
+
:host([collapsed][placement="embedded"]) .skill-chips,
|
|
7161
|
+
:host([collapsed][placement="embedded"]) .skill-palette,
|
|
7162
|
+
:host([collapsed][placement="embedded"]) .skill-hint {
|
|
6450
7163
|
display: none;
|
|
6451
7164
|
}
|
|
6452
7165
|
|
|
6453
|
-
/*
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
7166
|
+
/* A docked conversation list: beside the transcript rather than over it.
|
|
7167
|
+
|
|
7168
|
+
A full-page chat is the one surface with width to spare, and covering the
|
|
7169
|
+
conversation to show the list of conversations is the wrong trade there --
|
|
7170
|
+
it hides the thing you are trying to get back to. Everywhere else the panel
|
|
7171
|
+
is a few hundred pixels wide and a docked list would leave a column of
|
|
7172
|
+
transcript narrower than the messages in it, so this is the only placement
|
|
7173
|
+
that gets it.
|
|
7174
|
+
|
|
7175
|
+
The transcript is moved by padding on the shell rather than by making the
|
|
7176
|
+
list a flex sibling: the drawer is the last child of the panel, and no
|
|
7177
|
+
selector reaches backwards from it to the rows that have to shift. That is
|
|
7178
|
+
also why the state is stamped on the host. */
|
|
7179
|
+
:host([data-threads-docked]) .drawer {
|
|
7180
|
+
/* Not a scrim: the page behind it is still the user's to work in. */
|
|
7181
|
+
pointer-events: none;
|
|
6464
7182
|
}
|
|
6465
7183
|
|
|
6466
|
-
.
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
7184
|
+
:host([data-threads-docked]) .drawer-backdrop {
|
|
7185
|
+
display: none;
|
|
7186
|
+
}
|
|
7187
|
+
|
|
7188
|
+
:host([data-threads-docked]) .drawer-panel {
|
|
7189
|
+
width: var(--_threads-rail-width);
|
|
7190
|
+
pointer-events: auto;
|
|
7191
|
+
border-inline-end: 1px solid var(--_border);
|
|
7192
|
+
box-shadow: none;
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7195
|
+
:host([data-threads-docked]) .messages,
|
|
7196
|
+
:host([data-threads-docked]) .input-row,
|
|
7197
|
+
:host([data-threads-docked]) .skill-chips,
|
|
7198
|
+
:host([data-threads-docked]) .attachment-tray {
|
|
7199
|
+
padding-inline-start: calc(
|
|
7200
|
+
var(--_threads-rail-width) + max(var(--_pad), (100% - var(--_content-max-width)) / 2)
|
|
7201
|
+
);
|
|
7202
|
+
}
|
|
7203
|
+
|
|
7204
|
+
:host([data-threads-docked]) .header {
|
|
7205
|
+
padding-inline-start: calc(var(--_threads-rail-width) + var(--_pad));
|
|
7206
|
+
}
|
|
7207
|
+
|
|
7208
|
+
/* The page placement has no collapsed state, so it offers no control for one.
|
|
7209
|
+
A dedicated route has no "away" to go to: shrinking it left a strip of
|
|
7210
|
+
application chrome fixed over a route that no longer had an owner, and this
|
|
7211
|
+
is the one placement where the launcher that would bring it back is hidden.
|
|
7212
|
+
|
|
7213
|
+
Hiding the control is only half of it. The state has another way in -- the
|
|
7214
|
+
attribute can be written straight onto the element, and a value stored under
|
|
7215
|
+
a different placement is restored on connect -- and the JS guards that catch
|
|
7216
|
+
those cannot see an attribute set directly. So the placement neutralises the
|
|
7217
|
+
state here as well: with the collapse rules above no longer naming "page", an
|
|
7218
|
+
unguarded collapsed attribute would otherwise fall through to the generic
|
|
7219
|
+
rule that scales the panel away and drops pointer events, leaving nothing on
|
|
7220
|
+
screen and no launcher to press. */
|
|
7221
|
+
:host([placement="page"]) .header-btn--collapse {
|
|
7222
|
+
display: none;
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
:host([placement="page"][collapsed]) {
|
|
7226
|
+
pointer-events: auto;
|
|
7227
|
+
align-self: auto;
|
|
7228
|
+
}
|
|
7229
|
+
|
|
7230
|
+
:host([placement="page"][collapsed]) .chat {
|
|
7231
|
+
opacity: 1;
|
|
7232
|
+
transform: none;
|
|
7233
|
+
visibility: visible;
|
|
7234
|
+
}
|
|
7235
|
+
|
|
7236
|
+
/* Jump-to-latest: shown only once the reader has scrolled away *and* missed
|
|
7237
|
+
something. Anchored to the panel rather than the list so it does not scroll
|
|
7238
|
+
with the content it is offering to scroll to. */
|
|
7239
|
+
/* The transcript's own box, and the only one whose foot is the transcript's
|
|
7240
|
+
foot. The panel's foot is below the composer, the chips and the footer. */
|
|
7241
|
+
.messages-wrap {
|
|
7242
|
+
position: relative;
|
|
7243
|
+
flex: 1;
|
|
7244
|
+
min-height: 0;
|
|
7245
|
+
display: flex;
|
|
7246
|
+
flex-direction: column;
|
|
7247
|
+
}
|
|
7248
|
+
|
|
7249
|
+
.jump-latest {
|
|
7250
|
+
position: absolute;
|
|
7251
|
+
left: 50%;
|
|
7252
|
+
transform: translateX(-50%);
|
|
7253
|
+
bottom: var(--_pad);
|
|
7254
|
+
z-index: 2;
|
|
6472
7255
|
display: none;
|
|
6473
7256
|
align-items: center;
|
|
6474
7257
|
gap: 0.35em;
|
|
@@ -6537,6 +7320,28 @@ var STYLES = `
|
|
|
6537
7320
|
|
|
6538
7321
|
The 1px box with clip-path, rather than width/height 0, is the shape that
|
|
6539
7322
|
survives: a zero-sized element is dropped from the tree by some engines. */
|
|
7323
|
+
/* A used-value reader for the four viewport-inset tokens, and the only
|
|
7324
|
+
reliable one. getComputedStyle().getPropertyValue() on an unregistered
|
|
7325
|
+
custom property hands back the substituted token stream, not a length: it
|
|
7326
|
+
returns "4rem" verbatim, and "calc(56px + env(safe-area-inset-top))" as
|
|
7327
|
+
"calc(56px + 0px)" -- which parses as NaN and takes the whole inset with it.
|
|
7328
|
+
Padding is a real property, so the same tokens come back resolved to px.
|
|
7329
|
+
|
|
7330
|
+
visibility rather than display: none, because a box that generates no
|
|
7331
|
+
layout has no used values to read. Zero-sized, absolutely positioned and
|
|
7332
|
+
inert, so it costs nothing but the read. */
|
|
7333
|
+
.viewport-probe {
|
|
7334
|
+
position: absolute;
|
|
7335
|
+
top: 0;
|
|
7336
|
+
left: 0;
|
|
7337
|
+
width: 0;
|
|
7338
|
+
height: 0;
|
|
7339
|
+
visibility: hidden;
|
|
7340
|
+
pointer-events: none;
|
|
7341
|
+
padding: var(--_viewport-inset-top) var(--_viewport-inset-right)
|
|
7342
|
+
var(--_viewport-inset-bottom) var(--_viewport-inset-left);
|
|
7343
|
+
}
|
|
7344
|
+
|
|
6540
7345
|
.sr-only {
|
|
6541
7346
|
position: absolute;
|
|
6542
7347
|
width: 1px;
|
|
@@ -6552,6 +7357,8 @@ var STYLES = `
|
|
|
6552
7357
|
.messages {
|
|
6553
7358
|
flex: 1;
|
|
6554
7359
|
overflow-y: auto;
|
|
7360
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7361
|
+
overscroll-behavior: contain;
|
|
6555
7362
|
/* The browser's own scroll anchoring competes with the scroller for the same
|
|
6556
7363
|
job and wins unpredictably -- it can hold the view still exactly when we
|
|
6557
7364
|
want to follow. Turned off so following is decided in one place. Safari
|
|
@@ -6704,6 +7511,8 @@ var STYLES = `
|
|
|
6704
7511
|
.message--assistant pre {
|
|
6705
7512
|
padding: 8px 10px;
|
|
6706
7513
|
overflow: auto;
|
|
7514
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7515
|
+
overscroll-behavior: contain;
|
|
6707
7516
|
background: var(--_bg);
|
|
6708
7517
|
border: 1px solid var(--_border);
|
|
6709
7518
|
border-radius: 6px;
|
|
@@ -6882,6 +7691,8 @@ var STYLES = `
|
|
|
6882
7691
|
border-left: 2px solid var(--_border);
|
|
6883
7692
|
max-height: 220px;
|
|
6884
7693
|
overflow: auto;
|
|
7694
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7695
|
+
overscroll-behavior: contain;
|
|
6885
7696
|
white-space: pre-wrap;
|
|
6886
7697
|
word-break: break-word;
|
|
6887
7698
|
font-family: inherit;
|
|
@@ -7072,6 +7883,8 @@ var STYLES = `
|
|
|
7072
7883
|
padding: 6px 8px;
|
|
7073
7884
|
max-height: 160px;
|
|
7074
7885
|
overflow: auto;
|
|
7886
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7887
|
+
overscroll-behavior: contain;
|
|
7075
7888
|
background: var(--_bg);
|
|
7076
7889
|
border: 1px solid var(--_border);
|
|
7077
7890
|
border-radius: 6px;
|
|
@@ -7566,6 +8379,8 @@ var STYLES = `
|
|
|
7566
8379
|
resize: none;
|
|
7567
8380
|
max-height: var(--_composer-max-height);
|
|
7568
8381
|
overflow-y: auto;
|
|
8382
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
8383
|
+
overscroll-behavior: contain;
|
|
7569
8384
|
padding: 6px 4px 2px;
|
|
7570
8385
|
background: transparent;
|
|
7571
8386
|
border: none;
|
|
@@ -7776,13 +8591,30 @@ var STYLES = `
|
|
|
7776
8591
|
* values, and the axis furniture inherits currentColor at low opacity so it
|
|
7777
8592
|
* reads correctly in either theme without a second palette.
|
|
7778
8593
|
*/
|
|
8594
|
+
/* A chart is sized like a message, not like a panel: it takes the width it
|
|
8595
|
+
needs and stops, rather than stretching into whatever room the transcript
|
|
8596
|
+
has. A message bubble caps at 80% for the same reason -- widening the panel
|
|
8597
|
+
should not resize what is already in it. The cap is a token so a host with a
|
|
8598
|
+
wide panel and a real reason can raise it; the default is the width this
|
|
8599
|
+
renderer has always drawn at. Below the cap the block still fills its column,
|
|
8600
|
+
which is what a narrow panel needs. */
|
|
7779
8601
|
.chart-block {
|
|
7780
|
-
align-self:
|
|
7781
|
-
|
|
8602
|
+
align-self: flex-start;
|
|
8603
|
+
width: 100%;
|
|
8604
|
+
max-width: var(--_chart-max-width);
|
|
7782
8605
|
margin: 6px 0;
|
|
7783
8606
|
color: var(--_fg);
|
|
7784
8607
|
}
|
|
7785
8608
|
|
|
8609
|
+
/* The drawing fills the block's width and carries its own height in the
|
|
8610
|
+
viewBox, so it is laid out rather than magnified: block display keeps the
|
|
8611
|
+
inline baseline gap from adding a stripe under every chart. */
|
|
8612
|
+
.chart-block svg {
|
|
8613
|
+
display: block;
|
|
8614
|
+
width: 100%;
|
|
8615
|
+
height: auto;
|
|
8616
|
+
}
|
|
8617
|
+
|
|
7786
8618
|
.chart-title {
|
|
7787
8619
|
margin-bottom: 2px;
|
|
7788
8620
|
font-size: 0.85em;
|
|
@@ -7833,6 +8665,30 @@ var STYLES = `
|
|
|
7833
8665
|
opacity: 0.75;
|
|
7834
8666
|
}
|
|
7835
8667
|
|
|
8668
|
+
/* The one control a notice may carry. Quiet, because it reports something
|
|
8669
|
+
already done rather than asking for a decision. */
|
|
8670
|
+
.run-notice-undo {
|
|
8671
|
+
flex: 0 0 auto;
|
|
8672
|
+
margin-inline-start: auto;
|
|
8673
|
+
padding: 2px 8px;
|
|
8674
|
+
border: 1px solid var(--_border);
|
|
8675
|
+
border-radius: var(--_radius);
|
|
8676
|
+
background: var(--_bg);
|
|
8677
|
+
color: var(--_accent);
|
|
8678
|
+
font: inherit;
|
|
8679
|
+
font-size: 0.9em;
|
|
8680
|
+
cursor: pointer;
|
|
8681
|
+
}
|
|
8682
|
+
|
|
8683
|
+
.run-notice-undo:hover:not(:disabled) {
|
|
8684
|
+
background: var(--_hover);
|
|
8685
|
+
}
|
|
8686
|
+
|
|
8687
|
+
.run-notice-undo:disabled {
|
|
8688
|
+
opacity: 0.5;
|
|
8689
|
+
cursor: default;
|
|
8690
|
+
}
|
|
8691
|
+
|
|
7836
8692
|
.run-notice-text {
|
|
7837
8693
|
min-width: 0;
|
|
7838
8694
|
overflow-wrap: anywhere;
|
|
@@ -7977,6 +8833,8 @@ var STYLES = `
|
|
|
7977
8833
|
padding: 8px 10px;
|
|
7978
8834
|
max-height: 140px;
|
|
7979
8835
|
overflow: auto;
|
|
8836
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
8837
|
+
overscroll-behavior: contain;
|
|
7980
8838
|
font-size: 12px;
|
|
7981
8839
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
7982
8840
|
background: var(--_assistant-bg);
|
|
@@ -8165,6 +9023,43 @@ var STYLES = `
|
|
|
8165
9023
|
}
|
|
8166
9024
|
}
|
|
8167
9025
|
|
|
9026
|
+
/* Touch. Separate from the width breakpoint above on purpose: width decides
|
|
9027
|
+
the layout, the pointer decides which controls make sense, and a touch
|
|
9028
|
+
laptop is coarse-pointered and wide while a narrow desktop window is
|
|
9029
|
+
fine-pointered and small. */
|
|
9030
|
+
@media (pointer: coarse) {
|
|
9031
|
+
/* A 6px edge strip is not a control, it is a trap that eats a scroll. The
|
|
9032
|
+
corners are 14px, still under the 24px floor a target is meant to clear,
|
|
9033
|
+
and the panel has other ways to be resized on a device that has a pointer
|
|
9034
|
+
precise enough to grab one. */
|
|
9035
|
+
.resize-handle {
|
|
9036
|
+
display: none;
|
|
9037
|
+
}
|
|
9038
|
+
|
|
9039
|
+
/* Platform guidance is 44pt on iOS and 48dp on Android. These sit at 28-30px,
|
|
9040
|
+
which clears the WCAG minimum and misses both. */
|
|
9041
|
+
:host {
|
|
9042
|
+
--_action-size: var(--ag-ui-action-size, 44px);
|
|
9043
|
+
--_tool-btn-size: var(--ag-ui-tool-btn-size, 44px);
|
|
9044
|
+
--_send-size: var(--ag-ui-send-size, 44px);
|
|
9045
|
+
/* Missed the first time these were raised, which left the header -- the
|
|
9046
|
+
widget's primary controls, and the only way to reach history, a new chat
|
|
9047
|
+
or the collapse -- at half the size of everything beside it. */
|
|
9048
|
+
--_header-btn-size: var(--ag-ui-header-btn-size, 44px);
|
|
9049
|
+
--_header-gap: var(--ag-ui-header-gap, 6px);
|
|
9050
|
+
}
|
|
9051
|
+
|
|
9052
|
+
/* iOS Safari zooms the page when a control under 16px takes focus, which
|
|
9053
|
+
drags the whole fixed panel with it and leaves the user pinching back out
|
|
9054
|
+
of a chat they only wanted to type into. The composer inherits the widget
|
|
9055
|
+
font, which is 14px by default and 13px at compact density, so it has to
|
|
9056
|
+
say 16 outright -- and only ever upward, so a host that has deliberately
|
|
9057
|
+
set something larger keeps it. */
|
|
9058
|
+
.input {
|
|
9059
|
+
font-size: max(16px, var(--_font-size));
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
|
|
8168
9063
|
.message-action[aria-pressed="true"] {
|
|
8169
9064
|
opacity: 1;
|
|
8170
9065
|
color: var(--_accent);
|
|
@@ -8353,6 +9248,8 @@ var STYLES = `
|
|
|
8353
9248
|
flex-direction: column;
|
|
8354
9249
|
max-height: 220px;
|
|
8355
9250
|
overflow: auto;
|
|
9251
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
9252
|
+
overscroll-behavior: contain;
|
|
8356
9253
|
background: var(--_bg);
|
|
8357
9254
|
border: 1px solid var(--_border);
|
|
8358
9255
|
border-radius: 8px;
|
|
@@ -8441,6 +9338,8 @@ var STYLES = `
|
|
|
8441
9338
|
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
8442
9339
|
max-height: 60%;
|
|
8443
9340
|
overflow-y: auto;
|
|
9341
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
9342
|
+
overscroll-behavior: contain;
|
|
8444
9343
|
transform-origin: top center;
|
|
8445
9344
|
transition:
|
|
8446
9345
|
opacity var(--_motion) var(--_ease),
|
|
@@ -8616,10 +9515,20 @@ var STYLES = `
|
|
|
8616
9515
|
}
|
|
8617
9516
|
|
|
8618
9517
|
.drawer-title {
|
|
9518
|
+
/* Takes the slack so the two controls group at the trailing edge. With
|
|
9519
|
+
space-between and three items the middle one floats, which reads as the
|
|
9520
|
+
title and the close button being a pair with New chat wedged between
|
|
9521
|
+
them. Truncates rather than pushing them off the row. */
|
|
9522
|
+
flex: 1 1 auto;
|
|
9523
|
+
min-width: 0;
|
|
9524
|
+
overflow: hidden;
|
|
9525
|
+
text-overflow: ellipsis;
|
|
9526
|
+
white-space: nowrap;
|
|
8619
9527
|
font-weight: 600;
|
|
8620
9528
|
}
|
|
8621
9529
|
|
|
8622
9530
|
.drawer-new {
|
|
9531
|
+
flex: 0 0 auto;
|
|
8623
9532
|
border: 1px solid var(--_border);
|
|
8624
9533
|
border-radius: var(--_radius);
|
|
8625
9534
|
background: var(--_bg);
|
|
@@ -8630,10 +9539,63 @@ var STYLES = `
|
|
|
8630
9539
|
cursor: pointer;
|
|
8631
9540
|
}
|
|
8632
9541
|
|
|
9542
|
+
/* Narrows the list. Sits under the header rather than in it: the header's two
|
|
9543
|
+
controls act on the conversation, and a field that filters what is below it
|
|
9544
|
+
belongs with what it filters. */
|
|
9545
|
+
/* What is waiting for the run to finish. Above the composer, beside the
|
|
9546
|
+
attachment tray, because both are things already handed over and not yet
|
|
9547
|
+
sent. Each chip is a button: pressing it takes that message back. */
|
|
9548
|
+
.queued {
|
|
9549
|
+
display: flex;
|
|
9550
|
+
flex-wrap: wrap;
|
|
9551
|
+
gap: var(--_space);
|
|
9552
|
+
padding: 0 var(--_pad) var(--_space);
|
|
9553
|
+
}
|
|
9554
|
+
|
|
9555
|
+
.queued-chip {
|
|
9556
|
+
max-width: 100%;
|
|
9557
|
+
overflow: hidden;
|
|
9558
|
+
padding: 4px 10px;
|
|
9559
|
+
border: 1px dashed var(--_border);
|
|
9560
|
+
border-radius: var(--_msg-radius);
|
|
9561
|
+
background: var(--_bg);
|
|
9562
|
+
color: var(--_muted);
|
|
9563
|
+
font: inherit;
|
|
9564
|
+
font-size: 0.9em;
|
|
9565
|
+
text-overflow: ellipsis;
|
|
9566
|
+
white-space: nowrap;
|
|
9567
|
+
cursor: pointer;
|
|
9568
|
+
}
|
|
9569
|
+
|
|
9570
|
+
.queued-chip:hover {
|
|
9571
|
+
border-style: solid;
|
|
9572
|
+
color: var(--_fg);
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9575
|
+
.drawer-filter {
|
|
9576
|
+
flex: 0 0 auto;
|
|
9577
|
+
box-sizing: border-box;
|
|
9578
|
+
width: calc(100% - var(--_pad) * 2);
|
|
9579
|
+
margin: var(--_space) var(--_pad) 0;
|
|
9580
|
+
padding: 6px 10px;
|
|
9581
|
+
border: 1px solid var(--_border);
|
|
9582
|
+
border-radius: var(--_radius);
|
|
9583
|
+
background: var(--_input-bg);
|
|
9584
|
+
color: var(--_fg);
|
|
9585
|
+
font: inherit;
|
|
9586
|
+
font-size: 0.9em;
|
|
9587
|
+
}
|
|
9588
|
+
|
|
9589
|
+
.drawer-filter::placeholder {
|
|
9590
|
+
color: var(--_muted);
|
|
9591
|
+
}
|
|
9592
|
+
|
|
8633
9593
|
.drawer-list {
|
|
8634
9594
|
flex: 1;
|
|
8635
9595
|
min-height: 0;
|
|
8636
9596
|
overflow-y: auto;
|
|
9597
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
9598
|
+
overscroll-behavior: contain;
|
|
8637
9599
|
}
|
|
8638
9600
|
|
|
8639
9601
|
.drawer-empty {
|
|
@@ -8754,6 +9716,31 @@ var STYLES = `
|
|
|
8754
9716
|
background: none;
|
|
8755
9717
|
}
|
|
8756
9718
|
|
|
9719
|
+
/* The way back out of the list. It sits beside New chat, which is the control
|
|
9720
|
+
it must not be mistaken for: one returns you to the conversation you were
|
|
9721
|
+
reading, the other replaces it. */
|
|
9722
|
+
.drawer-close {
|
|
9723
|
+
flex: 0 0 auto;
|
|
9724
|
+
display: inline-flex;
|
|
9725
|
+
align-items: center;
|
|
9726
|
+
justify-content: center;
|
|
9727
|
+
width: 26px;
|
|
9728
|
+
height: 26px;
|
|
9729
|
+
padding: 0;
|
|
9730
|
+
border: 0;
|
|
9731
|
+
border-radius: 6px;
|
|
9732
|
+
background: none;
|
|
9733
|
+
color: inherit;
|
|
9734
|
+
font: inherit;
|
|
9735
|
+
font-size: 18px;
|
|
9736
|
+
line-height: 1;
|
|
9737
|
+
cursor: pointer;
|
|
9738
|
+
}
|
|
9739
|
+
|
|
9740
|
+
.drawer-close:hover {
|
|
9741
|
+
background: var(--_hover);
|
|
9742
|
+
}
|
|
9743
|
+
|
|
8757
9744
|
:host([placement="embedded"]) .drawer-panel {
|
|
8758
9745
|
width: 100%;
|
|
8759
9746
|
border-right: none;
|
|
@@ -9021,6 +10008,7 @@ var ThoughtsBlock = class {
|
|
|
9021
10008
|
};
|
|
9022
10009
|
|
|
9023
10010
|
// src/ui/thread_drawer.ts
|
|
10011
|
+
var FILTER_FROM = 8;
|
|
9024
10012
|
var ThreadDrawer = class {
|
|
9025
10013
|
/** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
|
|
9026
10014
|
element;
|
|
@@ -9033,6 +10021,23 @@ var ThreadDrawer = class {
|
|
|
9033
10021
|
#strings;
|
|
9034
10022
|
#threads = [];
|
|
9035
10023
|
#activeId = "";
|
|
10024
|
+
/** Dismisses the list without starting a new conversation. */
|
|
10025
|
+
#closeButton;
|
|
10026
|
+
/** Narrows the list. Only rendered once there is enough of a list to narrow. */
|
|
10027
|
+
#filter;
|
|
10028
|
+
/** The current filter text, lowercased once rather than per row. */
|
|
10029
|
+
#query = "";
|
|
10030
|
+
/**
|
|
10031
|
+
* Whether the list covers the conversation, or sits beside it.
|
|
10032
|
+
*
|
|
10033
|
+
* Everything a dialog implies follows from this and nothing else does: the
|
|
10034
|
+
* modal role, the focus trap, and taking focus on open. A list docked beside
|
|
10035
|
+
* a full-page conversation is a region of that page -- telling assistive
|
|
10036
|
+
* technology to ignore the rest of the document while it is showing would be
|
|
10037
|
+
* a lie, and stealing focus into it would interrupt someone mid-sentence to
|
|
10038
|
+
* announce a list they can already see.
|
|
10039
|
+
*/
|
|
10040
|
+
#modal = true;
|
|
9036
10041
|
/** The element focused before the drawer opened, restored on close. */
|
|
9037
10042
|
#lastFocused = null;
|
|
9038
10043
|
constructor(callbacks, strings = DEFAULT_UI_STRINGS) {
|
|
@@ -9069,11 +10074,29 @@ var ThreadDrawer = class {
|
|
|
9069
10074
|
this.close();
|
|
9070
10075
|
this.#callbacks.onNew();
|
|
9071
10076
|
});
|
|
9072
|
-
|
|
10077
|
+
this.#closeButton = document.createElement("button");
|
|
10078
|
+
this.#closeButton.type = "button";
|
|
10079
|
+
this.#closeButton.className = "drawer-close";
|
|
10080
|
+
this.#closeButton.setAttribute("part", "drawer-close");
|
|
10081
|
+
this.#closeButton.title = strings.closeHistory;
|
|
10082
|
+
this.#closeButton.setAttribute("aria-label", strings.closeHistory);
|
|
10083
|
+
this.#closeButton.append(document.createTextNode("\xD7"));
|
|
10084
|
+
this.#closeButton.addEventListener("click", () => this.close());
|
|
10085
|
+
header.append(this.#heading, this.#newButton, this.#closeButton);
|
|
10086
|
+
this.#filter = document.createElement("input");
|
|
10087
|
+
this.#filter.type = "search";
|
|
10088
|
+
this.#filter.className = "drawer-filter";
|
|
10089
|
+
this.#filter.setAttribute("part", "drawer-filter");
|
|
10090
|
+
this.#filter.placeholder = strings.searchConversations;
|
|
10091
|
+
this.#filter.setAttribute("aria-label", strings.searchConversations);
|
|
10092
|
+
this.#filter.addEventListener("input", () => {
|
|
10093
|
+
this.#query = this.#filter.value.trim().toLowerCase();
|
|
10094
|
+
this.#renderList();
|
|
10095
|
+
});
|
|
9073
10096
|
this.#list = document.createElement("div");
|
|
9074
10097
|
this.#list.className = "drawer-list";
|
|
9075
10098
|
this.#list.setAttribute("part", "drawer-list");
|
|
9076
|
-
this.#panel.append(header, this.#list);
|
|
10099
|
+
this.#panel.append(header, this.#filter, this.#list);
|
|
9077
10100
|
this.element.append(backdrop, this.#panel);
|
|
9078
10101
|
}
|
|
9079
10102
|
/** Re-localize the drawer's chrome and rows (the host calls this on connect). */
|
|
@@ -9097,8 +10120,26 @@ var ThreadDrawer = class {
|
|
|
9097
10120
|
this.#panel.setAttribute("aria-label", strings.chatHistory);
|
|
9098
10121
|
this.#heading.textContent = strings.chats;
|
|
9099
10122
|
this.#newButton.textContent = strings.newChat;
|
|
10123
|
+
this.#closeButton.title = strings.closeHistory;
|
|
10124
|
+
this.#closeButton.setAttribute("aria-label", strings.closeHistory);
|
|
10125
|
+
this.#filter.placeholder = strings.searchConversations;
|
|
10126
|
+
this.#filter.setAttribute("aria-label", strings.searchConversations);
|
|
9100
10127
|
this.#renderList();
|
|
9101
10128
|
}
|
|
10129
|
+
/**
|
|
10130
|
+
* Whether the list is a dialog over the conversation or a rail beside it.
|
|
10131
|
+
* The host decides, because only the placement knows whether there is room.
|
|
10132
|
+
*/
|
|
10133
|
+
setModal(modal) {
|
|
10134
|
+
this.#modal = modal;
|
|
10135
|
+
if (modal) {
|
|
10136
|
+
this.#panel.setAttribute("role", "dialog");
|
|
10137
|
+
this.#panel.setAttribute("aria-modal", "true");
|
|
10138
|
+
} else {
|
|
10139
|
+
this.#panel.setAttribute("role", "region");
|
|
10140
|
+
this.#panel.removeAttribute("aria-modal");
|
|
10141
|
+
}
|
|
10142
|
+
}
|
|
9102
10143
|
isOpen() {
|
|
9103
10144
|
return !this.element.hidden;
|
|
9104
10145
|
}
|
|
@@ -9108,15 +10149,21 @@ var ThreadDrawer = class {
|
|
|
9108
10149
|
}
|
|
9109
10150
|
this.#lastFocused = this.#activeElement();
|
|
9110
10151
|
this.element.hidden = false;
|
|
9111
|
-
this.#
|
|
10152
|
+
if (this.#modal) {
|
|
10153
|
+
this.#newButton.focus();
|
|
10154
|
+
}
|
|
10155
|
+
this.#callbacks.onVisibility?.(true);
|
|
9112
10156
|
}
|
|
9113
10157
|
close() {
|
|
9114
10158
|
if (!this.isOpen()) {
|
|
9115
10159
|
return;
|
|
9116
10160
|
}
|
|
9117
10161
|
this.element.hidden = true;
|
|
9118
|
-
this.#
|
|
10162
|
+
if (this.#modal) {
|
|
10163
|
+
this.#lastFocused?.focus();
|
|
10164
|
+
}
|
|
9119
10165
|
this.#lastFocused = null;
|
|
10166
|
+
this.#callbacks.onVisibility?.(false);
|
|
9120
10167
|
}
|
|
9121
10168
|
toggle() {
|
|
9122
10169
|
if (this.isOpen()) {
|
|
@@ -9136,7 +10183,7 @@ var ThreadDrawer = class {
|
|
|
9136
10183
|
this.close();
|
|
9137
10184
|
return;
|
|
9138
10185
|
}
|
|
9139
|
-
if (event.key !== "Tab") {
|
|
10186
|
+
if (event.key !== "Tab" || !this.#modal) {
|
|
9140
10187
|
return;
|
|
9141
10188
|
}
|
|
9142
10189
|
const focusables = Array.from(
|
|
@@ -9161,18 +10208,42 @@ var ThreadDrawer = class {
|
|
|
9161
10208
|
}
|
|
9162
10209
|
#renderList() {
|
|
9163
10210
|
this.#list.replaceChildren();
|
|
9164
|
-
|
|
10211
|
+
const hideFilter = this.#threads.length < FILTER_FROM;
|
|
10212
|
+
this.#filter.hidden = hideFilter;
|
|
10213
|
+
if (hideFilter && this.#query !== "") {
|
|
10214
|
+
this.#filter.value = "";
|
|
10215
|
+
this.#query = "";
|
|
10216
|
+
}
|
|
10217
|
+
const shown = this.#matching();
|
|
10218
|
+
if (shown.length === 0) {
|
|
9165
10219
|
const empty = document.createElement("div");
|
|
9166
10220
|
empty.className = "drawer-empty";
|
|
9167
10221
|
empty.setAttribute("part", "drawer-empty");
|
|
9168
|
-
empty.textContent = this.#strings.noConversations;
|
|
10222
|
+
empty.textContent = this.#threads.length === 0 ? this.#strings.noConversations : this.#strings.noMatches;
|
|
9169
10223
|
this.#list.appendChild(empty);
|
|
9170
10224
|
return;
|
|
9171
10225
|
}
|
|
9172
|
-
for (const meta of
|
|
10226
|
+
for (const meta of shown) {
|
|
9173
10227
|
this.#list.appendChild(this.#renderRow(meta));
|
|
9174
10228
|
}
|
|
9175
10229
|
}
|
|
10230
|
+
/**
|
|
10231
|
+
* The rows the filter leaves.
|
|
10232
|
+
*
|
|
10233
|
+
* Title and preview both, because the title is often the model's summary of
|
|
10234
|
+
* a conversation and the thing being looked for is as likely to be a phrase
|
|
10235
|
+
* from inside it. Client-side over what the drawer already holds: the server
|
|
10236
|
+
* index is fetched whole, so a query would be a round trip to filter a list
|
|
10237
|
+
* already in memory.
|
|
10238
|
+
*/
|
|
10239
|
+
#matching() {
|
|
10240
|
+
if (this.#query === "") {
|
|
10241
|
+
return this.#threads;
|
|
10242
|
+
}
|
|
10243
|
+
return this.#threads.filter(
|
|
10244
|
+
(meta) => meta.title.toLowerCase().includes(this.#query) || meta.preview.toLowerCase().includes(this.#query)
|
|
10245
|
+
);
|
|
10246
|
+
}
|
|
9176
10247
|
#renderRow(meta) {
|
|
9177
10248
|
const row = document.createElement("div");
|
|
9178
10249
|
row.className = "drawer-row";
|
|
@@ -10698,6 +11769,13 @@ var COLLAPSED_KEY = "ag-ui-chat:collapsed";
|
|
|
10698
11769
|
var SIZE_KEY = "ag-ui-chat:size";
|
|
10699
11770
|
var THEME_KEY = "ag-ui-chat:theme";
|
|
10700
11771
|
var LAUNCHER_KEY = "ag-ui-chat:launcher";
|
|
11772
|
+
function asPoint(value) {
|
|
11773
|
+
if (typeof value !== "object" || value === null) {
|
|
11774
|
+
return null;
|
|
11775
|
+
}
|
|
11776
|
+
const { left, top } = value;
|
|
11777
|
+
return typeof left === "number" && typeof top === "number" ? { left, top } : null;
|
|
11778
|
+
}
|
|
10701
11779
|
var DRAGGABLE_PLACEMENTS = /* @__PURE__ */ new Set([null, "", "floating", "bottom-left"]);
|
|
10702
11780
|
var RESIZE_GRIPS = [
|
|
10703
11781
|
{ y: "top" },
|
|
@@ -10937,7 +12015,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
10937
12015
|
* Resolve a `scroll_to` / `drag_and_drop` target string to a host-page
|
|
10938
12016
|
* element (or `null`). Defaults to a CSS-selector lookup; override to map
|
|
10939
12017
|
* page-map element ids. The page-action tools are opt-in via the
|
|
10940
|
-
* `data-page-actions` attribute (`"scroll"` / `"drag"`).
|
|
12018
|
+
* `data-page-actions` attribute (`"scroll"` / `"drag"` / `"chat"`).
|
|
10941
12019
|
*/
|
|
10942
12020
|
resolvePageTarget = (target) => document.querySelector(target);
|
|
10943
12021
|
/**
|
|
@@ -11053,6 +12131,12 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11053
12131
|
#root;
|
|
11054
12132
|
/** Screen-reader-only status region -- see {@link AgUiChat.#announce}. */
|
|
11055
12133
|
#announcer = document.createElement("div");
|
|
12134
|
+
/**
|
|
12135
|
+
* A zero-sized box carrying the host's viewport insets as padding, so they
|
|
12136
|
+
* can be read back as used pixel lengths. See the `.viewport-probe` rule for
|
|
12137
|
+
* why a custom property cannot be read directly.
|
|
12138
|
+
*/
|
|
12139
|
+
#viewportProbe = document.createElement("div");
|
|
11056
12140
|
/** Return-to-foot affordance, shown only once something has been missed. */
|
|
11057
12141
|
#jumpButton = document.createElement("button");
|
|
11058
12142
|
/**
|
|
@@ -11114,6 +12198,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11114
12198
|
#launcher;
|
|
11115
12199
|
/** The launcher's unread badge; hidden at zero, and when the host opts out. */
|
|
11116
12200
|
#badge;
|
|
12201
|
+
/** The rail's vertical caption. Rendered only by the sidebar's edge rail. */
|
|
12202
|
+
#railLabel = document.createElement("span");
|
|
11117
12203
|
// Answers that finished while the widget was collapsed. Expanding clears it.
|
|
11118
12204
|
#unread = 0;
|
|
11119
12205
|
/** Empty-state region at the top of the message list; hidden once anything renders. */
|
|
@@ -11126,6 +12212,26 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11126
12212
|
* position -- see #applyLauncherPlacement for what that costs the host.
|
|
11127
12213
|
*/
|
|
11128
12214
|
#launcherPos = null;
|
|
12215
|
+
/** What the user has sent this session, newest first, for arrow-key recall. */
|
|
12216
|
+
#sentDrafts = [];
|
|
12217
|
+
/** Typed while a run was in flight, oldest first; sent when it settles. */
|
|
12218
|
+
#queued = [];
|
|
12219
|
+
/** The row those show up in, above the composer. */
|
|
12220
|
+
#queuedRow = document.createElement("div");
|
|
12221
|
+
/** How far back the composer has been walked; null while the user is typing. */
|
|
12222
|
+
#recallIndex = null;
|
|
12223
|
+
/**
|
|
12224
|
+
* Where the user dragged the *panel*, in viewport coordinates, or null while
|
|
12225
|
+
* its position is still derived from the launcher's.
|
|
12226
|
+
*
|
|
12227
|
+
* The two gestures state different things and are restored differently. A
|
|
12228
|
+
* launcher drag says where the bubble goes and leaves the panel to open into
|
|
12229
|
+
* whatever space the viewport has, so it is re-derived every time -- which is
|
|
12230
|
+
* what lets a widget re-decide its direction when the window changes under
|
|
12231
|
+
* it. A header drag states the panel's own position, and re-deriving that
|
|
12232
|
+
* from the launcher would move the panel the user just placed.
|
|
12233
|
+
*/
|
|
12234
|
+
#panelPos = null;
|
|
11129
12235
|
/**
|
|
11130
12236
|
* The corner the panel opens away from, once this element is placing itself.
|
|
11131
12237
|
* Null means the host's layout still decides, and the anchor is measured.
|
|
@@ -11144,8 +12250,53 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11144
12250
|
* a field so `removeEventListener` on disconnect gets the same reference.
|
|
11145
12251
|
*/
|
|
11146
12252
|
#onViewportResize = () => {
|
|
12253
|
+
this.#publishVisualViewport();
|
|
12254
|
+
if (this.#dragging()) {
|
|
12255
|
+
return;
|
|
12256
|
+
}
|
|
11147
12257
|
this.#restoreLauncherPosition();
|
|
12258
|
+
if (this.#drawer.isOpen()) {
|
|
12259
|
+
this.#drawer.setModal(!this.#threadsDock());
|
|
12260
|
+
this.#syncThreadsState();
|
|
12261
|
+
}
|
|
11148
12262
|
};
|
|
12263
|
+
/**
|
|
12264
|
+
* Hold a resized box inside the part of the screen the host left free.
|
|
12265
|
+
*
|
|
12266
|
+
* Each edge on its own, unlike the drag's clamp: a drag moves a box of fixed
|
|
12267
|
+
* size, so pushing it back in is right, while a resize is anchored on the
|
|
12268
|
+
* opposite edge and pushing it back would move the edge the user is not
|
|
12269
|
+
* touching. Bounding each edge instead leaves the grip stopped at the limit
|
|
12270
|
+
* -- the gesture keeps going and the panel simply stops growing, which is
|
|
12271
|
+
* what dragging already does.
|
|
12272
|
+
*
|
|
12273
|
+
* The minimum size is the grip's own concern and is applied before this, so
|
|
12274
|
+
* a panel that cannot fit the space is left at its minimum and overflowing
|
|
12275
|
+
* rather than collapsed to nothing.
|
|
12276
|
+
*/
|
|
12277
|
+
#withinViewport(box) {
|
|
12278
|
+
const viewport = this.#viewport();
|
|
12279
|
+
const left = viewport.left + SCREEN_EDGE_MARGIN;
|
|
12280
|
+
const top = viewport.top + SCREEN_EDGE_MARGIN;
|
|
12281
|
+
const right = viewport.left + viewport.width - SCREEN_EDGE_MARGIN;
|
|
12282
|
+
const bottom = viewport.top + viewport.height - SCREEN_EDGE_MARGIN;
|
|
12283
|
+
return {
|
|
12284
|
+
left: Math.min(Math.max(box.left, left), box.right),
|
|
12285
|
+
top: Math.min(Math.max(box.top, top), box.bottom),
|
|
12286
|
+
right: Math.max(Math.min(box.right, right), box.left),
|
|
12287
|
+
bottom: Math.max(Math.min(box.bottom, bottom), box.top)
|
|
12288
|
+
};
|
|
12289
|
+
}
|
|
12290
|
+
/**
|
|
12291
|
+
* Whether a pointer or key gesture is currently placing the widget.
|
|
12292
|
+
*
|
|
12293
|
+
* Read from the stamp the drag helpers already set, rather than tracked
|
|
12294
|
+
* separately: one source of truth, and it clears on `pointercancel` as well
|
|
12295
|
+
* as `pointerup`, which is the end a touch gesture usually gets.
|
|
12296
|
+
*/
|
|
12297
|
+
#dragging() {
|
|
12298
|
+
return this.#launcher.hasAttribute("data-dragging") || this.#root.querySelector(".header[data-dragging]") !== null;
|
|
12299
|
+
}
|
|
11149
12300
|
/** Mic button mount point (input row); the control mounts on connect when enabled. */
|
|
11150
12301
|
#voiceSlot;
|
|
11151
12302
|
/** Voice-input control; created on connect when transcription is available. */
|
|
@@ -11267,6 +12418,9 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11267
12418
|
},
|
|
11268
12419
|
onDelete: (threadId) => {
|
|
11269
12420
|
this.#deleteThread(threadId);
|
|
12421
|
+
},
|
|
12422
|
+
onVisibility: () => {
|
|
12423
|
+
this.#syncThreadsState();
|
|
11270
12424
|
}
|
|
11271
12425
|
});
|
|
11272
12426
|
this.#checkpoints = new CheckpointMenu((runId, verb) => {
|
|
@@ -11355,11 +12509,15 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11355
12509
|
if (name === "placement") {
|
|
11356
12510
|
this.#releaseOwnedAxes();
|
|
11357
12511
|
this.#releaseLauncherPosition();
|
|
12512
|
+
if (!this.#collapsible() && this.collapsed) {
|
|
12513
|
+
this.setCollapsed(false);
|
|
12514
|
+
}
|
|
11358
12515
|
requestAnimationFrame(() => this.#syncResizeAnchor());
|
|
11359
12516
|
return;
|
|
11360
12517
|
}
|
|
11361
12518
|
if (name === "title-text") {
|
|
11362
12519
|
this.#title.textContent = value ?? this.#strings.title;
|
|
12520
|
+
this.#railLabel.textContent = this.#title.textContent;
|
|
11363
12521
|
return;
|
|
11364
12522
|
}
|
|
11365
12523
|
if (name === "user-key") {
|
|
@@ -11468,7 +12626,10 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11468
12626
|
const enabled = new Set(
|
|
11469
12627
|
attr.split(",").map((token) => token.trim()).filter((token) => token !== "")
|
|
11470
12628
|
);
|
|
11471
|
-
return
|
|
12629
|
+
return [
|
|
12630
|
+
...createPageActionTools(enabled, (target) => this.resolvePageTarget(target)),
|
|
12631
|
+
...enabled.has(PAGE_ACTIONS.CHAT) ? createChatSurfaceTools(this) : []
|
|
12632
|
+
];
|
|
11472
12633
|
}
|
|
11473
12634
|
/** All built-in (route + page + page-action + ask_user) frontend tools. */
|
|
11474
12635
|
#builtinTools() {
|
|
@@ -11708,7 +12869,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11708
12869
|
});
|
|
11709
12870
|
this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
|
|
11710
12871
|
if (this.getAttribute("data-theme-toggle") !== null) {
|
|
11711
|
-
const saved = this.#
|
|
12872
|
+
const saved = this.#readPreference(THEME_KEY);
|
|
11712
12873
|
if (saved !== null) {
|
|
11713
12874
|
this.setAttribute("theme", saved);
|
|
11714
12875
|
}
|
|
@@ -11716,7 +12877,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11716
12877
|
this.#render();
|
|
11717
12878
|
this.#drawer.setStrings(this.#strings);
|
|
11718
12879
|
this.#checkpoints.setStrings(this.#strings);
|
|
11719
|
-
if (this.#
|
|
12880
|
+
if (this.#collapsible() && this.#startsCollapsed()) {
|
|
11720
12881
|
this.setAttribute("collapsed", "");
|
|
11721
12882
|
}
|
|
11722
12883
|
this.#syncLauncher();
|
|
@@ -11727,6 +12888,9 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11727
12888
|
this.conversationStore = this.#builtinStore;
|
|
11728
12889
|
}
|
|
11729
12890
|
window.addEventListener("resize", this.#onViewportResize);
|
|
12891
|
+
window.visualViewport?.addEventListener("resize", this.#onViewportResize);
|
|
12892
|
+
window.visualViewport?.addEventListener("scroll", this.#onViewportResize);
|
|
12893
|
+
this.#publishVisualViewport();
|
|
11730
12894
|
this.#wireThreadStore();
|
|
11731
12895
|
this.#wireAttachments();
|
|
11732
12896
|
this.#wireVoice();
|
|
@@ -11790,6 +12954,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11790
12954
|
disconnectedCallback() {
|
|
11791
12955
|
this.#connected = false;
|
|
11792
12956
|
window.removeEventListener("resize", this.#onViewportResize);
|
|
12957
|
+
window.visualViewport?.removeEventListener("resize", this.#onViewportResize);
|
|
12958
|
+
window.visualViewport?.removeEventListener("scroll", this.#onViewportResize);
|
|
11793
12959
|
if (this.#claimedNs !== null) {
|
|
11794
12960
|
CLAIMED_NAMESPACES.delete(this.#claimedNs);
|
|
11795
12961
|
this.#claimedNs = null;
|
|
@@ -11862,7 +13028,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11862
13028
|
this.#fileInput.accept = accept;
|
|
11863
13029
|
this.#attachButton.hidden = false;
|
|
11864
13030
|
this.#enableDragAndDrop();
|
|
11865
|
-
this.#enablePaste();
|
|
13031
|
+
this.#enablePaste(tray);
|
|
11866
13032
|
}
|
|
11867
13033
|
/** The built-in multipart upload handler for `data-attachments-url`, or `null`. */
|
|
11868
13034
|
#defaultUploadHandler(url) {
|
|
@@ -12097,6 +13263,62 @@ ${quoted}`;
|
|
|
12097
13263
|
}
|
|
12098
13264
|
});
|
|
12099
13265
|
}
|
|
13266
|
+
/**
|
|
13267
|
+
* Turn a very long text paste into an attachment instead of a wall of text.
|
|
13268
|
+
*
|
|
13269
|
+
* A composer capped at `40vh` is not where forty thousand characters go: the
|
|
13270
|
+
* user cannot read what they pasted, cannot edit around it, and sends one
|
|
13271
|
+
* enormous turn. As a file it stays whole, the model still receives it, and
|
|
13272
|
+
* the box is left for the question about it.
|
|
13273
|
+
*
|
|
13274
|
+
* Only where the host has configured uploads -- and structurally so, rather
|
|
13275
|
+
* than by a check here: the paste listener is wired inside the attachment
|
|
13276
|
+
* setup, so with no tray there is no listener at all and an ordinary paste is
|
|
13277
|
+
* untouched. Quietly dropping a paste for being long would be far worse than
|
|
13278
|
+
* an awkward composer. The tray is passed rather than read off the field for
|
|
13279
|
+
* the same reason its `onChange` hook is: it can only be called from one that
|
|
13280
|
+
* exists, so taking it as an argument removes a null check no caller can
|
|
13281
|
+
* reach.
|
|
13282
|
+
*
|
|
13283
|
+
* Nothing is lost by removing the chip: the text is still on the clipboard,
|
|
13284
|
+
* so pasting again brings it back. That is why this needs no undo of its own.
|
|
13285
|
+
*/
|
|
13286
|
+
#pasteLongTextAsFile(event, clipboard, tray) {
|
|
13287
|
+
const threshold = this.#pasteAttachThreshold();
|
|
13288
|
+
const text4 = clipboard.getData("text/plain");
|
|
13289
|
+
if (threshold === null || text4.length < threshold) {
|
|
13290
|
+
return;
|
|
13291
|
+
}
|
|
13292
|
+
event.preventDefault();
|
|
13293
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13294
|
+
tray.add(new File([text4], `pasted-${stamp}.txt`, { type: "text/plain" }));
|
|
13295
|
+
}
|
|
13296
|
+
/**
|
|
13297
|
+
* How long a pasted string has to be before it becomes a file, or `null` to
|
|
13298
|
+
* leave every paste in the composer.
|
|
13299
|
+
*
|
|
13300
|
+
* One attribute with three answers rather than three attributes: absent is
|
|
13301
|
+
* the default, `off` refuses, and a number states the threshold. A value that
|
|
13302
|
+
* is neither says so, because a typo silently meaning "off" is the failure
|
|
13303
|
+
* this whole release keeps finding.
|
|
13304
|
+
*/
|
|
13305
|
+
#pasteAttachThreshold() {
|
|
13306
|
+
const raw = this.getAttribute("data-paste-attach");
|
|
13307
|
+
if (raw === null) {
|
|
13308
|
+
return PASTE_ATTACH_CHARS;
|
|
13309
|
+
}
|
|
13310
|
+
if (raw === "off") {
|
|
13311
|
+
return null;
|
|
13312
|
+
}
|
|
13313
|
+
const stated = Number.parseInt(raw, 10);
|
|
13314
|
+
if (Number.isNaN(stated) || stated <= 0) {
|
|
13315
|
+
console.warn(
|
|
13316
|
+
`<ag-ui-chat>: data-paste-attach="${raw}" is neither "off" nor a positive number of characters, so the default of ${PASTE_ATTACH_CHARS} is used.`
|
|
13317
|
+
);
|
|
13318
|
+
return PASTE_ATTACH_CHARS;
|
|
13319
|
+
}
|
|
13320
|
+
return stated;
|
|
13321
|
+
}
|
|
12100
13322
|
/**
|
|
12101
13323
|
* Accept files pasted into the composer.
|
|
12102
13324
|
*
|
|
@@ -12110,7 +13332,7 @@ ${quoted}`;
|
|
|
12110
13332
|
* clipboard, and swallowing the words someone meant to paste in order to
|
|
12111
13333
|
* attach a picture they did not is the worse of the two failures.
|
|
12112
13334
|
*/
|
|
12113
|
-
#enablePaste() {
|
|
13335
|
+
#enablePaste(tray) {
|
|
12114
13336
|
this.#chat.addEventListener("paste", (event) => {
|
|
12115
13337
|
const clipboard = event.clipboardData ?? null;
|
|
12116
13338
|
if (clipboard === null) {
|
|
@@ -12118,6 +13340,7 @@ ${quoted}`;
|
|
|
12118
13340
|
}
|
|
12119
13341
|
const files = Array.from(clipboard.files);
|
|
12120
13342
|
if (files.length === 0) {
|
|
13343
|
+
this.#pasteLongTextAsFile(event, clipboard, tray);
|
|
12121
13344
|
return;
|
|
12122
13345
|
}
|
|
12123
13346
|
if (clipboard.getData("text/plain") === "") {
|
|
@@ -12278,7 +13501,13 @@ ${quoted}`;
|
|
|
12278
13501
|
* per-tab, and emit a {@link TOGGLE_EVENT} so a host can mirror the state in
|
|
12279
13502
|
* its own chrome.
|
|
12280
13503
|
*/
|
|
12281
|
-
setCollapsed(collapsed) {
|
|
13504
|
+
setCollapsed(collapsed, options = {}) {
|
|
13505
|
+
if (collapsed && !this.#collapsible()) {
|
|
13506
|
+
return;
|
|
13507
|
+
}
|
|
13508
|
+
if (options.announce === true && collapsed && !this.collapsed) {
|
|
13509
|
+
this.#announceSurfaceChange(this.#strings.chatMinimised, null);
|
|
13510
|
+
}
|
|
12282
13511
|
if (!collapsed) {
|
|
12283
13512
|
this.#restoreLauncherPosition();
|
|
12284
13513
|
}
|
|
@@ -12306,6 +13535,167 @@ ${quoted}`;
|
|
|
12306
13535
|
get unread() {
|
|
12307
13536
|
return this.#unread;
|
|
12308
13537
|
}
|
|
13538
|
+
/**
|
|
13539
|
+
* Whether to mount collapsed.
|
|
13540
|
+
*
|
|
13541
|
+
* A stored choice always wins -- in either direction, so a user who opened
|
|
13542
|
+
* the panel finds it open. With nothing stored, the corner placements start
|
|
13543
|
+
* collapsed: they are the two that have a launcher, and a launcher is the
|
|
13544
|
+
* resting state of every corner chat in the field. Mounting open put a
|
|
13545
|
+
* 380x560 panel over the host page's own bottom-right corner on a visitor's
|
|
13546
|
+
* first load, uninvited.
|
|
13547
|
+
*
|
|
13548
|
+
* The placements that place themselves are unchanged. A host that docks a
|
|
13549
|
+
* sidebar has already decided the widget belongs on screen, and one that
|
|
13550
|
+
* embeds it in its own layout has given it a box to fill.
|
|
13551
|
+
*
|
|
13552
|
+
* `data-start-open` restores the previous behaviour for a host that wants
|
|
13553
|
+
* the panel up immediately.
|
|
13554
|
+
*/
|
|
13555
|
+
#startsCollapsed() {
|
|
13556
|
+
const stored = this.#readScopedItem(COLLAPSED_KEY);
|
|
13557
|
+
if (stored !== null) {
|
|
13558
|
+
return stored === "1";
|
|
13559
|
+
}
|
|
13560
|
+
return DRAGGABLE_PLACEMENTS.has(this.getAttribute("placement")) && !this.hasAttribute("data-start-open");
|
|
13561
|
+
}
|
|
13562
|
+
/**
|
|
13563
|
+
* Whether this placement has a collapsed state at all.
|
|
13564
|
+
*
|
|
13565
|
+
* `page` does not. It is a dedicated route rather than a panel sitting on
|
|
13566
|
+
* someone else's page, so there is no "away" for it to go to: collapsing it
|
|
13567
|
+
* left a strip of application chrome fixed over a route that no longer had an
|
|
13568
|
+
* owner. It is also the placement that hides the launcher, so the usual way
|
|
13569
|
+
* back does not exist here.
|
|
13570
|
+
*
|
|
13571
|
+
* The header hides its collapse control under this placement, but a control
|
|
13572
|
+
* removed from the UI is not a state removed from the model -- the property,
|
|
13573
|
+
* the attribute and a value restored from storage all still reach it. This is
|
|
13574
|
+
* what the reachable paths are gated on; the stylesheet covers the one path
|
|
13575
|
+
* that never passes through here, an attribute written straight onto the
|
|
13576
|
+
* element.
|
|
13577
|
+
*/
|
|
13578
|
+
#collapsible() {
|
|
13579
|
+
return this.getAttribute("placement") !== "page";
|
|
13580
|
+
}
|
|
13581
|
+
/**
|
|
13582
|
+
* Describe the panel to whoever is asking -- in practice the agent, through
|
|
13583
|
+
* the opt-in chat-surface tools.
|
|
13584
|
+
*
|
|
13585
|
+
* `movable` folds two separate reasons into the one answer a caller needs:
|
|
13586
|
+
* a placement that places itself owns its position, and a panel that fills
|
|
13587
|
+
* the screen has nowhere to go. Reporting them apart would make every caller
|
|
13588
|
+
* re-derive the same conjunction.
|
|
13589
|
+
*/
|
|
13590
|
+
describeSurface() {
|
|
13591
|
+
const box = this.getBoundingClientRect();
|
|
13592
|
+
const viewport = this.#viewport();
|
|
13593
|
+
const fullBleed = box.width >= viewport.width - 1 && box.height >= viewport.height - 1;
|
|
13594
|
+
return {
|
|
13595
|
+
placement: this.getAttribute("placement"),
|
|
13596
|
+
collapsed: this.collapsed,
|
|
13597
|
+
collapsible: this.#collapsible(),
|
|
13598
|
+
movable: this.#launcherDraggable() && !fullBleed,
|
|
13599
|
+
draggable: this.#launcherDraggable(),
|
|
13600
|
+
fullBleed,
|
|
13601
|
+
box: {
|
|
13602
|
+
left: Math.round(box.left),
|
|
13603
|
+
top: Math.round(box.top),
|
|
13604
|
+
width: Math.round(box.width),
|
|
13605
|
+
height: Math.round(box.height)
|
|
13606
|
+
},
|
|
13607
|
+
viewport: {
|
|
13608
|
+
left: Math.round(viewport.left),
|
|
13609
|
+
top: Math.round(viewport.top),
|
|
13610
|
+
width: Math.round(viewport.width),
|
|
13611
|
+
height: Math.round(viewport.height)
|
|
13612
|
+
}
|
|
13613
|
+
};
|
|
13614
|
+
}
|
|
13615
|
+
/**
|
|
13616
|
+
* Send the panel to a corner, and report whether it went.
|
|
13617
|
+
*
|
|
13618
|
+
* A third claimant on the axes a placement and a user drag already share, and
|
|
13619
|
+
* it takes them the same way the drag does rather than inventing a second
|
|
13620
|
+
* mechanism: the same commit path, so the launcher travels with the panel,
|
|
13621
|
+
* the corner it opens from is re-picked, and switching placement hands
|
|
13622
|
+
* everything back. What it must not do is claim a move it did not make --
|
|
13623
|
+
* hence the boolean, and hence {@link describeSurface} existing so a caller
|
|
13624
|
+
* can ask first.
|
|
13625
|
+
*/
|
|
13626
|
+
moveTo(corner, options = {}) {
|
|
13627
|
+
if (!this.#launcherDraggable()) {
|
|
13628
|
+
return false;
|
|
13629
|
+
}
|
|
13630
|
+
const restore = options.announce === true ? this.#captureGeometry() : null;
|
|
13631
|
+
const viewport = this.#viewport();
|
|
13632
|
+
const box = this.getBoundingClientRect();
|
|
13633
|
+
if (box.width >= viewport.width - 1 && box.height >= viewport.height - 1) {
|
|
13634
|
+
return false;
|
|
13635
|
+
}
|
|
13636
|
+
const [edgeY, edgeX] = corner.split("-");
|
|
13637
|
+
const nearX = viewport.left + EDGE_MARGIN;
|
|
13638
|
+
const nearY = viewport.top + EDGE_MARGIN;
|
|
13639
|
+
const left = edgeX === "left" ? nearX : Math.max(nearX, viewport.left + viewport.width - EDGE_MARGIN - box.width);
|
|
13640
|
+
const top = edgeY === "top" ? nearY : Math.max(nearY, viewport.top + viewport.height - EDGE_MARGIN - box.height);
|
|
13641
|
+
const host = { left, top, right: left + box.width, bottom: top + box.height };
|
|
13642
|
+
const launcherWidth = this.#launcher.offsetWidth;
|
|
13643
|
+
const launcherHeight = this.#launcher.offsetHeight;
|
|
13644
|
+
this.#placePanelAndLauncher(host, {
|
|
13645
|
+
left: edgeX === "left" ? host.left : host.right - launcherWidth,
|
|
13646
|
+
top: edgeY === "top" ? host.top : host.bottom - launcherHeight,
|
|
13647
|
+
width: launcherWidth,
|
|
13648
|
+
height: launcherHeight
|
|
13649
|
+
});
|
|
13650
|
+
this.#storeLauncherPosition();
|
|
13651
|
+
if (restore !== null) {
|
|
13652
|
+
this.#announceSurfaceChange(this.#strings.chatMoved, restore);
|
|
13653
|
+
}
|
|
13654
|
+
return true;
|
|
13655
|
+
}
|
|
13656
|
+
/**
|
|
13657
|
+
* Snapshot the panel's stated position, and return a function that puts it
|
|
13658
|
+
* back.
|
|
13659
|
+
*
|
|
13660
|
+
* Both insets and the expand corner, because they are one decision: the
|
|
13661
|
+
* corner is what the panel grows from, so restoring a position without it
|
|
13662
|
+
* puts the box back and animates it out of the wrong side. Absent values are
|
|
13663
|
+
* captured as absent and removed on the way back, rather than written as
|
|
13664
|
+
* empty strings that would outrank the placement.
|
|
13665
|
+
*/
|
|
13666
|
+
#captureGeometry() {
|
|
13667
|
+
const inset2 = this.style.getPropertyValue("--ag-ui-inset");
|
|
13668
|
+
const launcherInset = this.style.getPropertyValue("--ag-ui-launcher-inset");
|
|
13669
|
+
const corner = this.getAttribute("data-expand-corner");
|
|
13670
|
+
const launcherPos = this.#launcherPos;
|
|
13671
|
+
const panelPos = this.#panelPos;
|
|
13672
|
+
const expandCorner = this.#expandCorner;
|
|
13673
|
+
return () => {
|
|
13674
|
+
const put = (name, value) => {
|
|
13675
|
+
if (value === "") {
|
|
13676
|
+
this.style.removeProperty(name);
|
|
13677
|
+
} else {
|
|
13678
|
+
this.style.setProperty(name, value);
|
|
13679
|
+
}
|
|
13680
|
+
};
|
|
13681
|
+
put("--ag-ui-inset", inset2);
|
|
13682
|
+
put("--ag-ui-launcher-inset", launcherInset);
|
|
13683
|
+
if (corner === null) {
|
|
13684
|
+
this.removeAttribute("data-expand-corner");
|
|
13685
|
+
} else {
|
|
13686
|
+
this.setAttribute("data-expand-corner", corner);
|
|
13687
|
+
}
|
|
13688
|
+
this.#launcherPos = launcherPos;
|
|
13689
|
+
this.#panelPos = panelPos;
|
|
13690
|
+
this.#expandCorner = expandCorner;
|
|
13691
|
+
if (launcherPos === null) {
|
|
13692
|
+
this.#clearPreference(LAUNCHER_KEY);
|
|
13693
|
+
} else {
|
|
13694
|
+
this.#storeLauncherPosition();
|
|
13695
|
+
}
|
|
13696
|
+
this.#syncResizeAnchor();
|
|
13697
|
+
};
|
|
13698
|
+
}
|
|
12309
13699
|
/** Flip the collapsed state. Bound to the built-in header toggle. */
|
|
12310
13700
|
toggleCollapsed() {
|
|
12311
13701
|
this.setCollapsed(!this.collapsed);
|
|
@@ -12319,7 +13709,7 @@ ${quoted}`;
|
|
|
12319
13709
|
toggleTheme() {
|
|
12320
13710
|
const next = this.getAttribute("theme") === "dark" ? "light" : "dark";
|
|
12321
13711
|
this.setAttribute("theme", next);
|
|
12322
|
-
|
|
13712
|
+
this.#writePreference(THEME_KEY, next);
|
|
12323
13713
|
this.#syncThemeGlyph();
|
|
12324
13714
|
}
|
|
12325
13715
|
/**
|
|
@@ -12447,9 +13837,81 @@ ${quoted}`;
|
|
|
12447
13837
|
#launcherDraggable() {
|
|
12448
13838
|
return this.getAttribute("data-launcher-drag") !== "false" && DRAGGABLE_PLACEMENTS.has(this.getAttribute("placement"));
|
|
12449
13839
|
}
|
|
12450
|
-
/**
|
|
13840
|
+
/**
|
|
13841
|
+
* The viewport the launcher and the panel both have to fit inside.
|
|
13842
|
+
*
|
|
13843
|
+
* The *visual* viewport, not the layout one, because they come apart exactly
|
|
13844
|
+
* when this matters. An on-screen keyboard shrinks the visual viewport and
|
|
13845
|
+
* leaves the layout viewport alone, so clamping against `innerHeight` parks
|
|
13846
|
+
* the launcher behind the keyboard and decides which corner to open into
|
|
13847
|
+
* using space that is not on the screen. Pinch-zoom does the same on both
|
|
13848
|
+
* axes.
|
|
13849
|
+
*
|
|
13850
|
+
* Falls back where the API is absent, which keeps this working in the
|
|
13851
|
+
* happy-dom project as well as in an old browser.
|
|
13852
|
+
*/
|
|
12451
13853
|
#viewport() {
|
|
12452
|
-
|
|
13854
|
+
const visual = window.visualViewport;
|
|
13855
|
+
const width = visual?.width ?? window.innerWidth;
|
|
13856
|
+
const height = visual?.height ?? window.innerHeight;
|
|
13857
|
+
const style = getComputedStyle(this.#viewportProbe);
|
|
13858
|
+
const edge = (name) => {
|
|
13859
|
+
const value = Number.parseFloat(style.getPropertyValue(name));
|
|
13860
|
+
return Number.isFinite(value) ? value : 0;
|
|
13861
|
+
};
|
|
13862
|
+
const left = edge("padding-left");
|
|
13863
|
+
const top = edge("padding-top");
|
|
13864
|
+
return {
|
|
13865
|
+
left,
|
|
13866
|
+
top,
|
|
13867
|
+
width: Math.max(0, width - left - edge("padding-right")),
|
|
13868
|
+
height: Math.max(0, height - top - edge("padding-bottom"))
|
|
13869
|
+
};
|
|
13870
|
+
}
|
|
13871
|
+
/**
|
|
13872
|
+
* The whole viewport, before anything the host reserved is taken out of it.
|
|
13873
|
+
*
|
|
13874
|
+
* Distinct from {@link #viewport} on purpose, and the two must not be
|
|
13875
|
+
* swapped. The usable box decides where the widget may rest; this is what a
|
|
13876
|
+
* CSS `inset` on a fixed element is measured from, because that is what the
|
|
13877
|
+
* browser measures it from.
|
|
13878
|
+
*/
|
|
13879
|
+
#screen() {
|
|
13880
|
+
const root = document.documentElement;
|
|
13881
|
+
return {
|
|
13882
|
+
width: root.clientWidth || window.innerWidth,
|
|
13883
|
+
height: root.clientHeight || window.innerHeight
|
|
13884
|
+
};
|
|
13885
|
+
}
|
|
13886
|
+
/**
|
|
13887
|
+
* Publish the measured viewport height so the stylesheet can size a
|
|
13888
|
+
* full-bleed panel to what the user can see.
|
|
13889
|
+
*
|
|
13890
|
+
* No CSS length carries this. An on-screen keyboard has no effect on any
|
|
13891
|
+
* viewport-percentage unit, so a panel sized from `100dvh` puts its composer
|
|
13892
|
+
* behind the keyboard being typed into. Written inline, and read through a
|
|
13893
|
+
* token the host's own `--ag-ui-viewport-height` still outranks.
|
|
13894
|
+
*
|
|
13895
|
+
* Removed rather than frozen when the two viewports agree, so a desktop that
|
|
13896
|
+
* never diverges carries no inline override at all and the declared fallback
|
|
13897
|
+
* stays in charge.
|
|
13898
|
+
*/
|
|
13899
|
+
#publishVisualViewport() {
|
|
13900
|
+
const visual = window.visualViewport;
|
|
13901
|
+
if (visual === null || visual === void 0) {
|
|
13902
|
+
return;
|
|
13903
|
+
}
|
|
13904
|
+
if (Math.abs(visual.height - window.innerHeight) < 1) {
|
|
13905
|
+
this.style.removeProperty("--ag-ui-visual-viewport-height");
|
|
13906
|
+
this.style.removeProperty("--ag-ui-visual-viewport-inset-bottom");
|
|
13907
|
+
return;
|
|
13908
|
+
}
|
|
13909
|
+
this.style.setProperty("--ag-ui-visual-viewport-height", `${Math.round(visual.height)}px`);
|
|
13910
|
+
const hidden = window.innerHeight - visual.height - visual.offsetTop;
|
|
13911
|
+
this.style.setProperty(
|
|
13912
|
+
"--ag-ui-visual-viewport-inset-bottom",
|
|
13913
|
+
`${Math.max(0, Math.round(hidden))}px`
|
|
13914
|
+
);
|
|
12453
13915
|
}
|
|
12454
13916
|
/**
|
|
12455
13917
|
* The launcher's box in viewport coordinates, with its transform divided out.
|
|
@@ -12493,11 +13955,13 @@ ${quoted}`;
|
|
|
12493
13955
|
return;
|
|
12494
13956
|
}
|
|
12495
13957
|
this.#launcherPos = at2;
|
|
13958
|
+
this.#panelPos = null;
|
|
12496
13959
|
const panel = this.getBoundingClientRect();
|
|
12497
13960
|
const placement = launcherPlacement(
|
|
12498
13961
|
this.#launcherBox(),
|
|
12499
13962
|
{ width: panel.width, height: panel.height },
|
|
12500
|
-
this.#viewport()
|
|
13963
|
+
this.#viewport(),
|
|
13964
|
+
this.#screen()
|
|
12501
13965
|
);
|
|
12502
13966
|
this.style.setProperty("--ag-ui-inset", placement.hostInset);
|
|
12503
13967
|
this.style.setProperty("--ag-ui-launcher-inset", placement.launcherInset);
|
|
@@ -12514,13 +13978,141 @@ ${quoted}`;
|
|
|
12514
13978
|
this.#moveLauncher(left, top);
|
|
12515
13979
|
this.#storeLauncherPosition();
|
|
12516
13980
|
}
|
|
12517
|
-
/**
|
|
13981
|
+
/**
|
|
13982
|
+
* Move the panel live during a header drag, without persisting.
|
|
13983
|
+
*
|
|
13984
|
+
* Only the host box is written, and that is the whole trick: the launcher is
|
|
13985
|
+
* positioned *inside* that box, so leaving its own inset alone carries it
|
|
13986
|
+
* along by exactly the distance the panel travelled -- which is what a person
|
|
13987
|
+
* dragging a window expects of the thing it collapses into. Placing it on the
|
|
13988
|
+
* panel's pinned corner instead, as an earlier version did, sent it leaping
|
|
13989
|
+
* across the panel the moment the drag re-picked that corner.
|
|
13990
|
+
*
|
|
13991
|
+
* The corner is therefore held for the length of the gesture. Both insets are
|
|
13992
|
+
* measured from it, and rewriting one of them from a new corner while the
|
|
13993
|
+
* other still names the old one would move the launcher for no reason.
|
|
13994
|
+
*/
|
|
13995
|
+
#movePanel(box, from) {
|
|
13996
|
+
if (!this.#launcherDraggable()) {
|
|
13997
|
+
return { held: box, launcher: null };
|
|
13998
|
+
}
|
|
13999
|
+
if (this.#launcherPos === null) {
|
|
14000
|
+
const resting = this.#launcherBox();
|
|
14001
|
+
this.#launcherPos = { left: resting.left, top: resting.top };
|
|
14002
|
+
}
|
|
14003
|
+
const start = this.#launcherPos;
|
|
14004
|
+
const held = clampPanel(box, this.#viewport(), SCREEN_EDGE_MARGIN);
|
|
14005
|
+
const corner = this.#expandCorner ?? this.#anchor;
|
|
14006
|
+
const screen = this.#screen();
|
|
14007
|
+
this.style.setProperty(
|
|
14008
|
+
"--ag-ui-inset",
|
|
14009
|
+
[
|
|
14010
|
+
corner.y === "top" ? `${Math.round(held.top)}px` : "auto",
|
|
14011
|
+
corner.x === "right" ? `${Math.round(screen.width - held.right)}px` : "auto",
|
|
14012
|
+
corner.y === "bottom" ? `${Math.round(screen.height - held.bottom)}px` : "auto",
|
|
14013
|
+
corner.x === "left" ? `${Math.round(held.left)}px` : "auto"
|
|
14014
|
+
].join(" ")
|
|
14015
|
+
);
|
|
14016
|
+
this.#panelPos = { left: held.left, top: held.top };
|
|
14017
|
+
const carried = {
|
|
14018
|
+
...this.#launcherBox(),
|
|
14019
|
+
left: start.left + (held.left - from.left),
|
|
14020
|
+
top: start.top + (held.top - from.top)
|
|
14021
|
+
};
|
|
14022
|
+
const launcher = { ...carried, ...clampLauncher(carried, this.#viewport()) };
|
|
14023
|
+
this.style.setProperty(
|
|
14024
|
+
"--ag-ui-launcher-inset",
|
|
14025
|
+
placeWidget(held, launcher, corner, this.#screen()).launcherInset
|
|
14026
|
+
);
|
|
14027
|
+
return { held, launcher };
|
|
14028
|
+
}
|
|
14029
|
+
/**
|
|
14030
|
+
* Finish a header drag: settle where both halves ended up, and remember it.
|
|
14031
|
+
*
|
|
14032
|
+
* The launcher travels the distance the panel actually travelled, which is
|
|
14033
|
+
* the clamped distance rather than the pointer's -- a panel held against the
|
|
14034
|
+
* viewport margin stops, and so does the bubble attached to it. Measured from
|
|
14035
|
+
* the box the press started on, so a long drag cannot accumulate the rounding
|
|
14036
|
+
* each move writes into the inset.
|
|
14037
|
+
*
|
|
14038
|
+
* Only now is the corner re-picked, from where the launcher has ended up, so
|
|
14039
|
+
* the panel opens into clear space next time. Re-picking it moves nothing:
|
|
14040
|
+
* both insets are rewritten from positions that are already decided.
|
|
14041
|
+
*/
|
|
14042
|
+
#commitPanel(box, from) {
|
|
14043
|
+
const { held, launcher } = this.#movePanel(box, from);
|
|
14044
|
+
if (launcher === null) {
|
|
14045
|
+
return;
|
|
14046
|
+
}
|
|
14047
|
+
this.#placePanelAndLauncher(held, launcher);
|
|
14048
|
+
this.#storeLauncherPosition();
|
|
14049
|
+
}
|
|
14050
|
+
/**
|
|
14051
|
+
* Write both insets for a panel and launcher that are already positioned,
|
|
14052
|
+
* re-picking the corner they are measured from.
|
|
14053
|
+
*/
|
|
14054
|
+
#placePanelAndLauncher(host, launcher) {
|
|
14055
|
+
const viewport = this.#viewport();
|
|
14056
|
+
const screen = this.#screen();
|
|
14057
|
+
const size = { width: host.right - host.left, height: host.bottom - host.top };
|
|
14058
|
+
const { corner } = launcherPlacement(launcher, size, viewport, screen);
|
|
14059
|
+
const insets = placeWidget(host, launcher, corner, screen);
|
|
14060
|
+
this.style.setProperty("--ag-ui-inset", insets.hostInset);
|
|
14061
|
+
this.style.setProperty("--ag-ui-launcher-inset", insets.launcherInset);
|
|
14062
|
+
this.#launcherPos = { left: launcher.left, top: launcher.top };
|
|
14063
|
+
this.#panelPos = { left: host.left, top: host.top };
|
|
14064
|
+
this.#expandCorner = corner;
|
|
14065
|
+
this.setAttribute("data-expand-corner", `${corner.y}-${corner.x}`);
|
|
14066
|
+
this.#syncResizeAnchor();
|
|
14067
|
+
}
|
|
14068
|
+
/**
|
|
14069
|
+
* Re-apply a panel position the user stated, against the current viewport.
|
|
14070
|
+
*
|
|
14071
|
+
* The launcher keeps its offset from the panel through the clamp -- it was
|
|
14072
|
+
* put where it is relative to the panel, and a viewport that has since shrunk
|
|
14073
|
+
* is no reason to move one without the other -- and is then held on screen in
|
|
14074
|
+
* its own right.
|
|
14075
|
+
*/
|
|
14076
|
+
#restorePanelPosition(at2) {
|
|
14077
|
+
if (!this.#launcherDraggable()) {
|
|
14078
|
+
return;
|
|
14079
|
+
}
|
|
14080
|
+
const rect = this.getBoundingClientRect();
|
|
14081
|
+
const held = clampPanel(
|
|
14082
|
+
{ left: at2.left, top: at2.top, right: at2.left + rect.width, bottom: at2.top + rect.height },
|
|
14083
|
+
this.#viewport(),
|
|
14084
|
+
SCREEN_EDGE_MARGIN
|
|
14085
|
+
);
|
|
14086
|
+
const launcher = this.#launcherBox();
|
|
14087
|
+
const carried = {
|
|
14088
|
+
...launcher,
|
|
14089
|
+
left: launcher.left + (held.left - at2.left),
|
|
14090
|
+
top: launcher.top + (held.top - at2.top)
|
|
14091
|
+
};
|
|
14092
|
+
this.#placePanelAndLauncher(held, {
|
|
14093
|
+
...carried,
|
|
14094
|
+
...clampLauncher(carried, this.#viewport())
|
|
14095
|
+
});
|
|
14096
|
+
}
|
|
14097
|
+
/**
|
|
14098
|
+
* Write the current position, if this element owns one.
|
|
14099
|
+
*
|
|
14100
|
+
* The panel's own position rides along only when the user stated it, because
|
|
14101
|
+
* its presence is what tells a restore which of the two gestures to honour:
|
|
14102
|
+
* with it, the panel goes back where it was put; without it, the panel is
|
|
14103
|
+
* re-derived from the launcher and opens into whatever room the viewport has
|
|
14104
|
+
* now.
|
|
14105
|
+
*/
|
|
12518
14106
|
#storeLauncherPosition() {
|
|
12519
14107
|
const position = this.#launcherPos;
|
|
12520
14108
|
if (position === null) {
|
|
12521
14109
|
return;
|
|
12522
14110
|
}
|
|
12523
|
-
|
|
14111
|
+
const panel = this.#panelPos;
|
|
14112
|
+
this.#writePreference(
|
|
14113
|
+
LAUNCHER_KEY,
|
|
14114
|
+
JSON.stringify(panel === null ? position : { ...position, panel })
|
|
14115
|
+
);
|
|
12524
14116
|
}
|
|
12525
14117
|
/**
|
|
12526
14118
|
* Re-apply the dragged position against the current viewport: on connect,
|
|
@@ -12529,18 +14121,25 @@ ${quoted}`;
|
|
|
12529
14121
|
* unreachable -- it is the only way back to a collapsed conversation.
|
|
12530
14122
|
*/
|
|
12531
14123
|
#restoreLauncherPosition() {
|
|
12532
|
-
const stored = this.#
|
|
12533
|
-
|
|
14124
|
+
const stored = this.#readLauncherPosition();
|
|
14125
|
+
const launcher = this.#launcherPos ?? stored;
|
|
14126
|
+
if (launcher === null) {
|
|
14127
|
+
return;
|
|
14128
|
+
}
|
|
14129
|
+
const panel = this.#panelPos ?? stored?.panel ?? null;
|
|
14130
|
+
if (panel !== null) {
|
|
14131
|
+
this.#launcherPos = { left: launcher.left, top: launcher.top };
|
|
14132
|
+
this.#restorePanelPosition(panel);
|
|
12534
14133
|
return;
|
|
12535
14134
|
}
|
|
12536
14135
|
const box = this.#launcherBox();
|
|
12537
14136
|
this.#applyLauncherPlacement(
|
|
12538
|
-
clampLauncher({ ...box, left:
|
|
14137
|
+
clampLauncher({ ...box, left: launcher.left, top: launcher.top }, this.#viewport())
|
|
12539
14138
|
);
|
|
12540
14139
|
}
|
|
12541
|
-
/** The persisted
|
|
14140
|
+
/** The persisted position for this instance, or null. */
|
|
12542
14141
|
#readLauncherPosition() {
|
|
12543
|
-
const raw = this.#
|
|
14142
|
+
const raw = this.#readPreference(LAUNCHER_KEY);
|
|
12544
14143
|
if (raw === null) {
|
|
12545
14144
|
return null;
|
|
12546
14145
|
}
|
|
@@ -12549,8 +14148,12 @@ ${quoted}`;
|
|
|
12549
14148
|
if (typeof parsed !== "object" || parsed === null) {
|
|
12550
14149
|
return null;
|
|
12551
14150
|
}
|
|
12552
|
-
const { left, top } = parsed;
|
|
12553
|
-
|
|
14151
|
+
const { left, top, panel } = parsed;
|
|
14152
|
+
if (typeof left !== "number" || typeof top !== "number") {
|
|
14153
|
+
return null;
|
|
14154
|
+
}
|
|
14155
|
+
const at2 = asPoint(panel);
|
|
14156
|
+
return at2 === null ? { left, top } : { left, top, panel: at2 };
|
|
12554
14157
|
} catch {
|
|
12555
14158
|
return null;
|
|
12556
14159
|
}
|
|
@@ -12565,6 +14168,7 @@ ${quoted}`;
|
|
|
12565
14168
|
return;
|
|
12566
14169
|
}
|
|
12567
14170
|
this.#launcherPos = null;
|
|
14171
|
+
this.#panelPos = null;
|
|
12568
14172
|
this.#expandCorner = null;
|
|
12569
14173
|
this.style.removeProperty("--ag-ui-inset");
|
|
12570
14174
|
this.style.removeProperty("--ag-ui-launcher-inset");
|
|
@@ -12585,19 +14189,20 @@ ${quoted}`;
|
|
|
12585
14189
|
* drags the edge it was holding.
|
|
12586
14190
|
*/
|
|
12587
14191
|
#applyResize(grip, box) {
|
|
14192
|
+
box = this.#withinViewport(box);
|
|
12588
14193
|
this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
|
|
12589
14194
|
if (grip.x !== this.#anchor.x && grip.y !== this.#anchor.y) {
|
|
12590
|
-
return;
|
|
14195
|
+
return box;
|
|
12591
14196
|
}
|
|
12592
|
-
const
|
|
14197
|
+
const screen = this.#screen();
|
|
12593
14198
|
const anchor = this.#anchor;
|
|
12594
14199
|
const side = (value) => `${Math.round(value)}px`;
|
|
12595
14200
|
this.style.setProperty(
|
|
12596
14201
|
"--ag-ui-inset",
|
|
12597
14202
|
[
|
|
12598
14203
|
anchor.y === "top" ? side(box.top) : "auto",
|
|
12599
|
-
anchor.x === "right" ? side(
|
|
12600
|
-
anchor.y === "bottom" ? side(
|
|
14204
|
+
anchor.x === "right" ? side(screen.width - box.right) : "auto",
|
|
14205
|
+
anchor.y === "bottom" ? side(screen.height - box.bottom) : "auto",
|
|
12601
14206
|
anchor.x === "left" ? side(box.left) : "auto"
|
|
12602
14207
|
].join(" ")
|
|
12603
14208
|
);
|
|
@@ -12608,11 +14213,15 @@ ${quoted}`;
|
|
|
12608
14213
|
top: anchor.y === "top" ? box.top : box.bottom - size
|
|
12609
14214
|
};
|
|
12610
14215
|
}
|
|
14216
|
+
if (this.#panelPos !== null) {
|
|
14217
|
+
this.#panelPos = { left: box.left, top: box.top };
|
|
14218
|
+
}
|
|
14219
|
+
return box;
|
|
12611
14220
|
}
|
|
12612
14221
|
/** Finish a resize: keep the box, remember it, and re-read the pinned edges. */
|
|
12613
14222
|
#commitResize(grip, box) {
|
|
12614
|
-
this.#applyResize(grip, box);
|
|
12615
|
-
this.#persistSize({ width:
|
|
14223
|
+
const held = this.#applyResize(grip, box);
|
|
14224
|
+
this.#persistSize({ width: held.right - held.left, height: held.bottom - held.top });
|
|
12616
14225
|
this.#storeLauncherPosition();
|
|
12617
14226
|
this.#syncResizeAnchor();
|
|
12618
14227
|
}
|
|
@@ -12639,11 +14248,11 @@ ${quoted}`;
|
|
|
12639
14248
|
/** Persist a dragged size per tab, alongside the collapsed/theme preferences. */
|
|
12640
14249
|
#persistSize(size) {
|
|
12641
14250
|
const stored = { ...this.#readSize(), ...size };
|
|
12642
|
-
|
|
14251
|
+
this.#writePreference(SIZE_KEY, JSON.stringify(stored));
|
|
12643
14252
|
}
|
|
12644
14253
|
/** The persisted size for this instance, or an empty record. */
|
|
12645
14254
|
#readSize() {
|
|
12646
|
-
const raw = this.#
|
|
14255
|
+
const raw = this.#readPreference(SIZE_KEY);
|
|
12647
14256
|
if (raw === null) {
|
|
12648
14257
|
return {};
|
|
12649
14258
|
}
|
|
@@ -12770,10 +14379,81 @@ ${quoted}`;
|
|
|
12770
14379
|
}
|
|
12771
14380
|
return sessionStorage.getItem(base);
|
|
12772
14381
|
}
|
|
14382
|
+
/**
|
|
14383
|
+
* Read a layout preference: where the widget sits, how big it is, which
|
|
14384
|
+
* theme it wears.
|
|
14385
|
+
*
|
|
14386
|
+
* These live in `localStorage` rather than beside the transcript, because a
|
|
14387
|
+
* layout preference is not a conversation. The transcript is deliberately
|
|
14388
|
+
* per-tab -- two tabs are two conversations, and closing the tab ends it --
|
|
14389
|
+
* and everything else inherited that scoping without earning it. A user who
|
|
14390
|
+
* dragged the panel clear of their own UI did it again in the next tab, and
|
|
14391
|
+
* again after every restart.
|
|
14392
|
+
*
|
|
14393
|
+
* Whether the widget is *currently open* stays per-tab with the transcript.
|
|
14394
|
+
* It is a statement about this tab rather than a preference: carrying it
|
|
14395
|
+
* across would pop the panel open on every new tab because it was opened
|
|
14396
|
+
* once, somewhere else.
|
|
14397
|
+
*
|
|
14398
|
+
* Falls back to the session value it used to be written to, so an existing
|
|
14399
|
+
* position survives the upgrade rather than resetting once.
|
|
14400
|
+
*/
|
|
14401
|
+
#readPreference(base) {
|
|
14402
|
+
try {
|
|
14403
|
+
const stored = localStorage.getItem(this.#storageKey(base));
|
|
14404
|
+
if (stored !== null) {
|
|
14405
|
+
return stored;
|
|
14406
|
+
}
|
|
14407
|
+
} catch {
|
|
14408
|
+
}
|
|
14409
|
+
return this.#readScopedItem(base);
|
|
14410
|
+
}
|
|
14411
|
+
/**
|
|
14412
|
+
* Persist a layout preference as durably as this browser allows: to
|
|
14413
|
+
* `localStorage` so it outlives the tab, and to the per-tab store as well.
|
|
14414
|
+
*
|
|
14415
|
+
* The second write is not redundancy for its own sake. A privacy mode can
|
|
14416
|
+
* deny `localStorage` while allowing `sessionStorage`, and losing the
|
|
14417
|
+
* durable copy should degrade to the per-tab behaviour this replaced rather
|
|
14418
|
+
* than to no persistence at all. The read above prefers the durable copy, so
|
|
14419
|
+
* a tab that has both cannot be shadowed by its own stale one.
|
|
14420
|
+
*
|
|
14421
|
+
* Neither write is worth an exception. Losing where the panel sat is not
|
|
14422
|
+
* worth a warning either -- unlike the transcript, which says so once,
|
|
14423
|
+
* because losing that loses the conversation on the next reload.
|
|
14424
|
+
*/
|
|
14425
|
+
#writePreference(base, value) {
|
|
14426
|
+
const key = this.#storageKey(base);
|
|
14427
|
+
try {
|
|
14428
|
+
localStorage.setItem(key, value);
|
|
14429
|
+
} catch {
|
|
14430
|
+
}
|
|
14431
|
+
writeStoredItem(key, value);
|
|
14432
|
+
}
|
|
14433
|
+
/**
|
|
14434
|
+
* Drop a layout preference from both stores.
|
|
14435
|
+
*
|
|
14436
|
+
* The mirror of {@link AgUiChat.#writePreference}, and it has to clear both
|
|
14437
|
+
* for the same reason that writes both: leaving either copy behind means the
|
|
14438
|
+
* value comes back on the next read.
|
|
14439
|
+
*/
|
|
14440
|
+
#clearPreference(base) {
|
|
14441
|
+
const key = this.#storageKey(base);
|
|
14442
|
+
try {
|
|
14443
|
+
localStorage.removeItem(key);
|
|
14444
|
+
} catch {
|
|
14445
|
+
}
|
|
14446
|
+
try {
|
|
14447
|
+
sessionStorage.removeItem(key);
|
|
14448
|
+
} catch {
|
|
14449
|
+
}
|
|
14450
|
+
}
|
|
12773
14451
|
/** Reflect the current theme on the toggle: show the destination's glyph. */
|
|
12774
14452
|
#syncThemeGlyph() {
|
|
12775
14453
|
const dark = this.getAttribute("theme") === "dark";
|
|
12776
|
-
this.#themeToggle.
|
|
14454
|
+
this.#themeToggle.replaceChildren(
|
|
14455
|
+
this.#iconElement("theme", "theme-icon", dark ? ICON_SUN : ICON_MOON, null)
|
|
14456
|
+
);
|
|
12777
14457
|
}
|
|
12778
14458
|
/**
|
|
12779
14459
|
* Open the thread-history drawer: the imperative route to the control that
|
|
@@ -12787,7 +14467,37 @@ ${quoted}`;
|
|
|
12787
14467
|
openThreads() {
|
|
12788
14468
|
this.#checkpoints.close();
|
|
12789
14469
|
void this.#refreshDrawer();
|
|
14470
|
+
this.#drawer.setModal(!this.#threadsDock());
|
|
12790
14471
|
this.#drawer.open();
|
|
14472
|
+
this.#syncThreadsState();
|
|
14473
|
+
}
|
|
14474
|
+
/**
|
|
14475
|
+
* Whether the conversation list docks beside the transcript rather than
|
|
14476
|
+
* covering it.
|
|
14477
|
+
*
|
|
14478
|
+
* Only the full-page placement, and only where there is room. A dedicated
|
|
14479
|
+
* route is the one surface with width to spare -- everywhere else the panel
|
|
14480
|
+
* is a few hundred pixels wide, and a list docked into that leaves a column
|
|
14481
|
+
* of transcript too narrow to read. The width is the panel's own rather than
|
|
14482
|
+
* the window's, because an embedded host can give a full-page-sized box to
|
|
14483
|
+
* something that is not a page.
|
|
14484
|
+
*/
|
|
14485
|
+
#threadsDock() {
|
|
14486
|
+
return this.getAttribute("placement") === "page" && this.getBoundingClientRect().width >= THREADS_DOCK_MIN_WIDTH;
|
|
14487
|
+
}
|
|
14488
|
+
/**
|
|
14489
|
+
* Stamp whether the list is showing, and how.
|
|
14490
|
+
*
|
|
14491
|
+
* On the host rather than inside the shell because the transcript has to move
|
|
14492
|
+
* over for a docked list, and the drawer is the last child of the panel -- CSS
|
|
14493
|
+
* cannot select backwards from it to the rows it needs to shift.
|
|
14494
|
+
*/
|
|
14495
|
+
#syncThreadsState() {
|
|
14496
|
+
if (this.#drawer.isOpen() && this.#threadsDock()) {
|
|
14497
|
+
this.setAttribute("data-threads-docked", "");
|
|
14498
|
+
} else {
|
|
14499
|
+
this.removeAttribute("data-threads-docked");
|
|
14500
|
+
}
|
|
12791
14501
|
}
|
|
12792
14502
|
/**
|
|
12793
14503
|
* Open the checkpoints panel (the `::part(checkpoints-button)` route).
|
|
@@ -12801,6 +14511,16 @@ ${quoted}`;
|
|
|
12801
14511
|
void this.#refreshCheckpoints();
|
|
12802
14512
|
this.#checkpoints.open();
|
|
12803
14513
|
}
|
|
14514
|
+
/**
|
|
14515
|
+
* Close the conversation list, if it is open.
|
|
14516
|
+
*
|
|
14517
|
+
* No state sync here: the drawer reports every close through `onVisibility`,
|
|
14518
|
+
* including the four that never reach this method, and doing it twice would
|
|
14519
|
+
* be a second place to keep right.
|
|
14520
|
+
*/
|
|
14521
|
+
closeThreads() {
|
|
14522
|
+
this.#drawer.close();
|
|
14523
|
+
}
|
|
12804
14524
|
/** Close the checkpoints panel, if it is open. */
|
|
12805
14525
|
closeCheckpoints() {
|
|
12806
14526
|
this.#checkpoints.close();
|
|
@@ -12841,6 +14561,8 @@ ${quoted}`;
|
|
|
12841
14561
|
this.#client = null;
|
|
12842
14562
|
this.#clearTranscript();
|
|
12843
14563
|
this.#initialMessages = [];
|
|
14564
|
+
this.#sentDrafts.length = 0;
|
|
14565
|
+
this.#recallIndex = null;
|
|
12844
14566
|
}
|
|
12845
14567
|
/**
|
|
12846
14568
|
* Wipe the rendered transcript and everything that indexes into it.
|
|
@@ -13174,6 +14896,12 @@ ${quoted}`;
|
|
|
13174
14896
|
}
|
|
13175
14897
|
controls.append(collapse2);
|
|
13176
14898
|
header.append(title, headerActions, controls);
|
|
14899
|
+
enablePanelDrag(header, {
|
|
14900
|
+
enabled: () => !this.collapsed && this.#launcherDraggable(),
|
|
14901
|
+
rect: () => this.getBoundingClientRect(),
|
|
14902
|
+
apply: (box, from) => this.#movePanel(box, from),
|
|
14903
|
+
commit: (box, from) => this.#commitPanel(box, from)
|
|
14904
|
+
});
|
|
13177
14905
|
this.#messages.className = "messages";
|
|
13178
14906
|
this.#messages.setAttribute("part", "messages");
|
|
13179
14907
|
this.#messages.setAttribute("role", "log");
|
|
@@ -13216,7 +14944,16 @@ ${quoted}`;
|
|
|
13216
14944
|
this.#emptyWrap.setAttribute("part", "empty");
|
|
13217
14945
|
const emptySlot = document.createElement("slot");
|
|
13218
14946
|
emptySlot.name = "empty";
|
|
14947
|
+
const starters = this.#starterChips();
|
|
14948
|
+
if (starters !== null) {
|
|
14949
|
+
emptySlot.append(starters);
|
|
14950
|
+
}
|
|
13219
14951
|
this.#emptyWrap.append(emptySlot);
|
|
14952
|
+
this.#queuedRow.className = "queued";
|
|
14953
|
+
this.#queuedRow.setAttribute("part", "queued");
|
|
14954
|
+
this.#queuedRow.setAttribute("role", "group");
|
|
14955
|
+
this.#queuedRow.setAttribute("aria-label", this.#strings.queued);
|
|
14956
|
+
this.#queuedRow.hidden = true;
|
|
13220
14957
|
this.#messages.append(this.#emptyWrap);
|
|
13221
14958
|
const inputRow = document.createElement("div");
|
|
13222
14959
|
inputRow.className = "input-row";
|
|
@@ -13282,6 +15019,7 @@ ${quoted}`;
|
|
|
13282
15019
|
this.#skillsMenu.palette,
|
|
13283
15020
|
this.#skillsMenu.chips,
|
|
13284
15021
|
this.#skillHint,
|
|
15022
|
+
this.#queuedRow,
|
|
13285
15023
|
this.#attachSlot,
|
|
13286
15024
|
inputRow,
|
|
13287
15025
|
footer,
|
|
@@ -13306,8 +15044,13 @@ ${quoted}`;
|
|
|
13306
15044
|
this.#badge.setAttribute("part", "launcher-badge");
|
|
13307
15045
|
this.#badge.setAttribute("aria-hidden", "true");
|
|
13308
15046
|
this.#badge.hidden = true;
|
|
15047
|
+
this.#railLabel.className = "rail-label";
|
|
15048
|
+
this.#railLabel.setAttribute("part", "rail-label");
|
|
15049
|
+
this.#railLabel.setAttribute("aria-hidden", "true");
|
|
15050
|
+
this.#railLabel.textContent = this.getAttribute("title-text") ?? this.#strings.title;
|
|
13309
15051
|
this.#launcher.append(
|
|
13310
15052
|
this.#iconElement("launcher", "launcher-icon", ICON_LAUNCHER, this.#launcherIconUrl()),
|
|
15053
|
+
this.#railLabel,
|
|
13311
15054
|
this.#badge
|
|
13312
15055
|
);
|
|
13313
15056
|
this.#launcher.addEventListener("click", () => this.setCollapsed(false));
|
|
@@ -13333,7 +15076,9 @@ ${quoted}`;
|
|
|
13333
15076
|
}
|
|
13334
15077
|
this.#focusableGrip();
|
|
13335
15078
|
this.#adoptStyles();
|
|
13336
|
-
this.#
|
|
15079
|
+
this.#viewportProbe.className = "viewport-probe";
|
|
15080
|
+
this.#viewportProbe.setAttribute("aria-hidden", "true");
|
|
15081
|
+
this.#root.append(this.#viewportProbe, this.#announcer, this.#chat, this.#launcher);
|
|
13337
15082
|
}
|
|
13338
15083
|
/**
|
|
13339
15084
|
* Attach the stylesheet without an inline `<style>` element.
|
|
@@ -13512,6 +15257,37 @@ ${quoted}`;
|
|
|
13512
15257
|
this.#setUnread(this.#unread + 1);
|
|
13513
15258
|
}
|
|
13514
15259
|
/** Hide the empty-state region once the message list holds anything else. */
|
|
15260
|
+
/**
|
|
15261
|
+
* The prompts offered on an empty transcript, from `data-starters`.
|
|
15262
|
+
*
|
|
15263
|
+
* Different from the suggestion chips a run pushes, which are follow-ups to
|
|
15264
|
+
* something already said. These answer the blank-page question instead, and
|
|
15265
|
+
* they are the host's rather than the model's -- only the host knows what its
|
|
15266
|
+
* page is for. Shares the renderer, the count and the length limit, because
|
|
15267
|
+
* two rows of prompt chips that behaved differently would be the harder
|
|
15268
|
+
* thing to explain.
|
|
15269
|
+
*
|
|
15270
|
+
* Read once at connect: it is content for a state the widget is in before
|
|
15271
|
+
* anything happens, and a host that wants it to change has `slot="empty"`.
|
|
15272
|
+
*/
|
|
15273
|
+
#starterChips() {
|
|
15274
|
+
const raw = this.getAttribute("data-starters");
|
|
15275
|
+
if (raw === null) {
|
|
15276
|
+
return null;
|
|
15277
|
+
}
|
|
15278
|
+
let parsed;
|
|
15279
|
+
try {
|
|
15280
|
+
parsed = JSON.parse(raw);
|
|
15281
|
+
} catch {
|
|
15282
|
+
console.warn(
|
|
15283
|
+
`<ag-ui-chat>: data-starters is not valid JSON, so no starters are shown. It takes an array of strings, e.g. data-starters='["Summarise this page"]'.`
|
|
15284
|
+
);
|
|
15285
|
+
return null;
|
|
15286
|
+
}
|
|
15287
|
+
return renderSuggestionChips({ prompts: parsed }, this.#strings, (prompt) => {
|
|
15288
|
+
void this.sendMessage(prompt);
|
|
15289
|
+
});
|
|
15290
|
+
}
|
|
13515
15291
|
#updateEmptyState() {
|
|
13516
15292
|
this.#emptyWrap.hidden = this.#messages.childElementCount > 1;
|
|
13517
15293
|
}
|
|
@@ -13520,6 +15296,7 @@ ${quoted}`;
|
|
|
13520
15296
|
this.#skillsMenu.onInput(this.#input.value);
|
|
13521
15297
|
this.#skillHint.hidden = true;
|
|
13522
15298
|
this.#autoGrow();
|
|
15299
|
+
this.#recallIndex = null;
|
|
13523
15300
|
}
|
|
13524
15301
|
#onKeydown(event) {
|
|
13525
15302
|
if (this.#skillsMenu.onKeydown(event)) {
|
|
@@ -13534,7 +15311,44 @@ ${quoted}`;
|
|
|
13534
15311
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
13535
15312
|
event.preventDefault();
|
|
13536
15313
|
void this.#submit();
|
|
15314
|
+
return;
|
|
15315
|
+
}
|
|
15316
|
+
this.#recallHistory(event);
|
|
15317
|
+
}
|
|
15318
|
+
/**
|
|
15319
|
+
* Walk back through what the user has already sent, on the arrow keys.
|
|
15320
|
+
*
|
|
15321
|
+
* Only from an empty composer, and only with the palette closed -- which the
|
|
15322
|
+
* caller has already established, since the palette consumes arrows while it
|
|
15323
|
+
* is open. Both conditions matter: arrows inside text are how you move the
|
|
15324
|
+
* caret, and taking them would break editing to add a shortcut.
|
|
15325
|
+
*
|
|
15326
|
+
* The drafts are the user's own turns in this conversation, newest first,
|
|
15327
|
+
* which is what every shell and every coding agent means by this. Arrowing
|
|
15328
|
+
* forward past the newest empties the composer again rather than sticking on
|
|
15329
|
+
* it, so the way out is the same key that got you in.
|
|
15330
|
+
*/
|
|
15331
|
+
#recallHistory(event) {
|
|
15332
|
+
const back = event.key === "ArrowUp";
|
|
15333
|
+
if (!back && event.key !== "ArrowDown" || this.#skillsMenu.isOpen()) {
|
|
15334
|
+
return;
|
|
15335
|
+
}
|
|
15336
|
+
const drafts = this.#sentDrafts;
|
|
15337
|
+
if (drafts.length === 0) {
|
|
15338
|
+
return;
|
|
15339
|
+
}
|
|
15340
|
+
if (this.#recallIndex === null && (!back || this.#input.value !== "")) {
|
|
15341
|
+
return;
|
|
13537
15342
|
}
|
|
15343
|
+
const next = this.#recallIndex === null ? 0 : this.#recallIndex + (back ? 1 : -1);
|
|
15344
|
+
if (next >= drafts.length) {
|
|
15345
|
+
return;
|
|
15346
|
+
}
|
|
15347
|
+
event.preventDefault();
|
|
15348
|
+
this.#recallIndex = next < 0 ? null : next;
|
|
15349
|
+
this.#input.value = next < 0 ? "" : drafts[next];
|
|
15350
|
+
this.#input.setSelectionRange(this.#input.value.length, this.#input.value.length);
|
|
15351
|
+
this.#autoGrow();
|
|
13538
15352
|
}
|
|
13539
15353
|
/**
|
|
13540
15354
|
* Stop the in-flight run: decline any confirmation card awaiting a decision
|
|
@@ -13543,6 +15357,13 @@ ${quoted}`;
|
|
|
13543
15357
|
* observes the disconnect).
|
|
13544
15358
|
*/
|
|
13545
15359
|
#cancelRun() {
|
|
15360
|
+
for (const text4 of this.#queued) {
|
|
15361
|
+
if (this.#sentDrafts[0] !== text4) {
|
|
15362
|
+
this.#sentDrafts.unshift(text4);
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15365
|
+
this.#queued.length = 0;
|
|
15366
|
+
this.#renderQueued();
|
|
13546
15367
|
this.#confirmAbort?.abort();
|
|
13547
15368
|
this.#client?.cancel();
|
|
13548
15369
|
}
|
|
@@ -13554,11 +15375,54 @@ ${quoted}`;
|
|
|
13554
15375
|
* that it carries no text.
|
|
13555
15376
|
*/
|
|
13556
15377
|
#setRunning(running) {
|
|
15378
|
+
const settled = this.#running && !running;
|
|
13557
15379
|
this.#running = running;
|
|
13558
15380
|
const label = running ? this.#strings.stop : this.#strings.send;
|
|
13559
15381
|
this.#send.title = label;
|
|
13560
15382
|
this.#send.setAttribute("aria-label", label);
|
|
13561
15383
|
this.#send.dataset["state"] = running ? "running" : "idle";
|
|
15384
|
+
if (settled) {
|
|
15385
|
+
this.#flushQueued();
|
|
15386
|
+
}
|
|
15387
|
+
}
|
|
15388
|
+
/**
|
|
15389
|
+
* Send the next message that was typed while the run was going.
|
|
15390
|
+
*
|
|
15391
|
+
* One at a time, through the same path as anything else: each queued turn
|
|
15392
|
+
* starts a run of its own, and the next is sent when *that* one settles. Any
|
|
15393
|
+
* other shape would be a second sender racing the guard above.
|
|
15394
|
+
*/
|
|
15395
|
+
#flushQueued() {
|
|
15396
|
+
const next = this.#queued.shift();
|
|
15397
|
+
this.#renderQueued();
|
|
15398
|
+
if (next !== void 0) {
|
|
15399
|
+
void this.sendMessage(next);
|
|
15400
|
+
}
|
|
15401
|
+
}
|
|
15402
|
+
/**
|
|
15403
|
+
* Draw what is waiting, as chips that can be taken back.
|
|
15404
|
+
*
|
|
15405
|
+
* Visible and removable, because a message the user typed and cannot see is
|
|
15406
|
+
* a message they will type again -- and one they changed their mind about
|
|
15407
|
+
* has to be retractable before it is sent on their behalf.
|
|
15408
|
+
*/
|
|
15409
|
+
#renderQueued() {
|
|
15410
|
+
this.#queuedRow.replaceChildren();
|
|
15411
|
+
this.#queuedRow.hidden = this.#queued.length === 0;
|
|
15412
|
+
for (const [index, text4] of this.#queued.entries()) {
|
|
15413
|
+
const chip = document.createElement("button");
|
|
15414
|
+
chip.type = "button";
|
|
15415
|
+
chip.className = "queued-chip";
|
|
15416
|
+
chip.setAttribute("part", "queued-chip");
|
|
15417
|
+
chip.textContent = text4;
|
|
15418
|
+
chip.title = this.#strings.removeQueued.replace("{text}", text4);
|
|
15419
|
+
chip.setAttribute("aria-label", chip.title);
|
|
15420
|
+
chip.addEventListener("click", () => {
|
|
15421
|
+
this.#queued.splice(index, 1);
|
|
15422
|
+
this.#renderQueued();
|
|
15423
|
+
});
|
|
15424
|
+
this.#queuedRow.appendChild(chip);
|
|
15425
|
+
}
|
|
13562
15426
|
}
|
|
13563
15427
|
/**
|
|
13564
15428
|
* Size the field to its content: one row when empty, growing with what is
|
|
@@ -13573,14 +15437,24 @@ ${quoted}`;
|
|
|
13573
15437
|
this.#input.style.height = `${this.#input.scrollHeight}px`;
|
|
13574
15438
|
}
|
|
13575
15439
|
async #submit() {
|
|
13576
|
-
if (this.#running) {
|
|
13577
|
-
return;
|
|
13578
|
-
}
|
|
13579
15440
|
const content = this.#input.value.trim();
|
|
13580
15441
|
const attachments = this.#attachTray?.readyRefs() ?? [];
|
|
13581
15442
|
if (content === "" && attachments.length === 0) {
|
|
13582
15443
|
return;
|
|
13583
15444
|
}
|
|
15445
|
+
if (this.#running) {
|
|
15446
|
+
if (content !== "") {
|
|
15447
|
+
this.#queued.push(content);
|
|
15448
|
+
this.#renderQueued();
|
|
15449
|
+
this.#input.value = "";
|
|
15450
|
+
this.#autoGrow();
|
|
15451
|
+
}
|
|
15452
|
+
return;
|
|
15453
|
+
}
|
|
15454
|
+
if (content !== "" && this.#sentDrafts[0] !== content) {
|
|
15455
|
+
this.#sentDrafts.unshift(content);
|
|
15456
|
+
}
|
|
15457
|
+
this.#recallIndex = null;
|
|
13584
15458
|
this.#input.value = "";
|
|
13585
15459
|
this.#autoGrow();
|
|
13586
15460
|
if (this.#attachTray?.hasPending() === true) {
|
|
@@ -14366,11 +16240,33 @@ ${quoted}`;
|
|
|
14366
16240
|
this.#appendNotice("\u2728", this.#strings.usingSkill.replace("{name}", skill), "skill");
|
|
14367
16241
|
return true;
|
|
14368
16242
|
}
|
|
14369
|
-
|
|
14370
|
-
|
|
16243
|
+
/**
|
|
16244
|
+
* An inline notice about something the run did.
|
|
16245
|
+
*
|
|
16246
|
+
* `undo` is offered only where the agent rearranged the user's own window --
|
|
16247
|
+
* see {@link renderRunNotice} for why a notice may carry that one control and
|
|
16248
|
+
* nothing else.
|
|
16249
|
+
*/
|
|
16250
|
+
#appendNotice(icon, text4, kind, undo) {
|
|
16251
|
+
this.#ensureGroup().appendChild(renderRunNotice(icon, text4, kind, undo));
|
|
14371
16252
|
this.#updateEmptyState();
|
|
14372
16253
|
this.#scroller.follow();
|
|
14373
16254
|
}
|
|
16255
|
+
/**
|
|
16256
|
+
* Say that the agent rearranged the user's window, and offer the way back.
|
|
16257
|
+
*
|
|
16258
|
+
* Only on the agent's path. A host calling {@link moveTo} is arranging its
|
|
16259
|
+
* own page and does not need telling what it just did; an agent doing it
|
|
16260
|
+
* mid-conversation is the case where a panel appears to move on its own.
|
|
16261
|
+
*/
|
|
16262
|
+
#announceSurfaceChange(text4, undo) {
|
|
16263
|
+
this.#appendNotice(
|
|
16264
|
+
"\u2922",
|
|
16265
|
+
text4,
|
|
16266
|
+
"surface",
|
|
16267
|
+
undo === null ? void 0 : { label: this.#strings.undo, onActivate: undo }
|
|
16268
|
+
);
|
|
16269
|
+
}
|
|
14374
16270
|
/**
|
|
14375
16271
|
* Turn on chart rendering, by whichever route this consumer wants.
|
|
14376
16272
|
*
|
|
@@ -14643,13 +16539,14 @@ function setControlValue(el2, value) {
|
|
|
14643
16539
|
}
|
|
14644
16540
|
|
|
14645
16541
|
// src/version.ts
|
|
14646
|
-
var VERSION = "0.
|
|
16542
|
+
var VERSION = "0.35.0";
|
|
14647
16543
|
export {
|
|
14648
16544
|
ATTACHMENT_EVENT,
|
|
14649
16545
|
AgUiChat,
|
|
14650
16546
|
AgUiClient,
|
|
14651
16547
|
CHART_ACTIVITY_TYPE,
|
|
14652
16548
|
CHART_TOOL_NAME,
|
|
16549
|
+
CHAT_CORNERS,
|
|
14653
16550
|
COMPACTION_ACTIVITY_TYPE,
|
|
14654
16551
|
CUSTOM_AGENT_EVENT,
|
|
14655
16552
|
CheckpointMenu,
|
|
@@ -14690,6 +16587,7 @@ export {
|
|
|
14690
16587
|
attachQuoteOffer,
|
|
14691
16588
|
chartSpecFrom,
|
|
14692
16589
|
clickElement,
|
|
16590
|
+
createChatSurfaceTools,
|
|
14693
16591
|
createHttpAgent,
|
|
14694
16592
|
createPageActionTools,
|
|
14695
16593
|
createPageMapContext,
|
|
@@ -14701,6 +16599,7 @@ export {
|
|
|
14701
16599
|
flash,
|
|
14702
16600
|
focusWithFlash,
|
|
14703
16601
|
highlightThenClick,
|
|
16602
|
+
isChatCorner,
|
|
14704
16603
|
isDestructive,
|
|
14705
16604
|
isNavigates,
|
|
14706
16605
|
mergeUiStrings,
|
|
@@ -14725,6 +16624,7 @@ export {
|
|
|
14725
16624
|
setControlValue,
|
|
14726
16625
|
setNativeChecked,
|
|
14727
16626
|
setNativeValue,
|
|
16627
|
+
showHighlightOverlay,
|
|
14728
16628
|
suggestionPrompts,
|
|
14729
16629
|
toggleCheckbox,
|
|
14730
16630
|
toggleControl,
|