chrome-devtools-frontend 1.0.1697595 → 1.0.1697998
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/.stylelintignore +2 -0
- package/front_end/core/common/VersionController.ts +5 -1
- package/front_end/core/sdk/AnimationModel.ts +9 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/protocol.ts +8 -2
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +26 -0
- package/front_end/models/ai_assistance/ai_assistance.ts +4 -4
- package/front_end/models/ai_assistance/skills/performance.md +4 -3
- package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
- package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
- package/front_end/models/ai_assistance/tools/Tool.ts +2 -2
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
- package/front_end/models/elements/DOMChanges.ts +238 -0
- package/front_end/models/elements/elements.ts +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -1
- package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
- package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
- package/front_end/panels/elements/ElementsTreeElement.ts +98 -20
- package/front_end/panels/elements/ElementsTreeOutline.ts +271 -43
- package/front_end/panels/elements/EventListenersWidget.ts +0 -1
- package/front_end/panels/event_listeners/EventListenersView.ts +241 -297
- package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +1 -1
- package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
- package/front_end/panels/network/NetworkConfigView.ts +141 -116
- package/front_end/panels/network/NetworkPanel.ts +7 -4
- package/front_end/panels/network/RequestHeadersView.ts +1 -1
- package/front_end/panels/network/forward/BackendLinking.ts +5 -2
- package/front_end/panels/settings/BackendLinkingSettingsTab.ts +7 -2
- package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/comments/CommentOverlayManager.ts +169 -40
- package/front_end/ui/comments/CommentThreadWidget.ts +221 -0
- package/front_end/ui/comments/CommentsOverlayWidget.ts +124 -12
- package/front_end/ui/comments/commentThreadWidget.css +173 -0
- package/front_end/ui/comments/comments.ts +2 -0
- package/front_end/ui/comments/commentsOverlay.css +6 -0
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +1 -1
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +4 -0
- package/package.json +5 -5
|
@@ -6,6 +6,7 @@ import * as Common from '../../core/common/common.js';
|
|
|
6
6
|
import * as CommentManager from '../../models/comment_manager/comment_manager.js';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
closestAcrossShadow,
|
|
9
10
|
type CommentAnchorSignature,
|
|
10
11
|
type CommentThread,
|
|
11
12
|
computeVisibleRect,
|
|
@@ -25,7 +26,6 @@ export interface StartOptions {
|
|
|
25
26
|
root?: Document|Element;
|
|
26
27
|
scrollTarget?: EventTarget;
|
|
27
28
|
resizeTarget?: Element;
|
|
28
|
-
defaultText?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface PinPositionData {
|
|
@@ -36,6 +36,8 @@ export interface PinPositionData {
|
|
|
36
36
|
index: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export type PendingPinPositionData = Omit<PinPositionData, 'id'>;
|
|
40
|
+
|
|
39
41
|
export interface HighlightRectData {
|
|
40
42
|
id: string;
|
|
41
43
|
top: number;
|
|
@@ -45,6 +47,23 @@ export interface HighlightRectData {
|
|
|
45
47
|
visible: boolean;
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
export type PendingHighlightRectData = Omit<HighlightRectData, 'id'>;
|
|
51
|
+
|
|
52
|
+
export interface PendingDraft {
|
|
53
|
+
element: Element;
|
|
54
|
+
anchor: CommentAnchorSignature;
|
|
55
|
+
pin: PendingPinPositionData|null;
|
|
56
|
+
highlight: PendingHighlightRectData|null;
|
|
57
|
+
pinOffset: {offsetX: number, offsetY: number}|null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CreateCommentOptions {
|
|
61
|
+
author?: 'DEVELOPER'|'AGENT';
|
|
62
|
+
changes?: CommentManager.CommentManager.ChangeRecord[];
|
|
63
|
+
coordinates?: {clientX: number, clientY: number};
|
|
64
|
+
pendingDraft?: PendingDraft;
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
export interface HoverHighlightData {
|
|
49
68
|
top: number;
|
|
50
69
|
left: number;
|
|
@@ -80,6 +99,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
80
99
|
#hoverData: HoverHighlightData|null = null;
|
|
81
100
|
#pinPositions: PinPositionData[] = [];
|
|
82
101
|
#highlightRects: HighlightRectData[] = [];
|
|
102
|
+
#pendingDraft: PendingDraft|null = null;
|
|
83
103
|
|
|
84
104
|
#clickListener?: (event: Event) => void;
|
|
85
105
|
#hoverListener?: (event: Event) => void;
|
|
@@ -129,6 +149,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
129
149
|
({data: active}) => {
|
|
130
150
|
if (!active) {
|
|
131
151
|
this.#clearHover();
|
|
152
|
+
this.clearPendingAnchor();
|
|
132
153
|
}
|
|
133
154
|
document.body.style.cursor = active ? COMMENT_MODE_CURSOR : '';
|
|
134
155
|
},
|
|
@@ -216,56 +237,123 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
216
237
|
return this.#highlightRects;
|
|
217
238
|
}
|
|
218
239
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!this
|
|
225
|
-
return
|
|
240
|
+
getPendingDraft(): PendingDraft|null {
|
|
241
|
+
return this.#pendingDraft;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
clearPendingAnchor(): void {
|
|
245
|
+
if (!this.#pendingDraft) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!this.#observedThreads.has(this.#pendingDraft.element)) {
|
|
250
|
+
this.#intersectionObserver?.unobserve(this.#pendingDraft.element);
|
|
251
|
+
}
|
|
252
|
+
this.#pendingDraft = null;
|
|
253
|
+
this.#updatePositions();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
handleElementClick(element: Element, options?: {clientX: number, clientY: number}): boolean {
|
|
257
|
+
if (!this.isCommentMode() || closestAcrossShadow(element, '.comment-thread-widget')) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
this.clearPendingAnchor();
|
|
261
|
+
|
|
262
|
+
const resolved = this.#resolveAnchor(element, options);
|
|
263
|
+
if (!resolved) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const {anchor, anchorElement: anchorEl} = resolved;
|
|
267
|
+
const visibleRect = computeVisibleRect(anchorEl);
|
|
268
|
+
if (!visibleRect) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
272
|
+
|
|
273
|
+
let pinOffset: {offsetX: number, offsetY: number}|null = null;
|
|
274
|
+
if (options) {
|
|
275
|
+
pinOffset = {
|
|
276
|
+
offsetX: Math.min(Math.max(options.clientX, visibleRect.left), visibleRect.right) - anchorRect.left,
|
|
277
|
+
offsetY: Math.min(Math.max(options.clientY, visibleRect.top), visibleRect.bottom) - anchorRect.top,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.#pendingDraft = {
|
|
282
|
+
element: anchorEl,
|
|
283
|
+
anchor,
|
|
284
|
+
pin: null,
|
|
285
|
+
highlight: null,
|
|
286
|
+
pinOffset,
|
|
287
|
+
};
|
|
288
|
+
const observer = this.#getIntersectionObserver();
|
|
289
|
+
if (!this.#observedThreads.has(anchorEl)) {
|
|
290
|
+
observer.observe(anchorEl);
|
|
226
291
|
}
|
|
227
|
-
|
|
292
|
+
this.#updatePositions();
|
|
293
|
+
return true;
|
|
228
294
|
}
|
|
229
295
|
|
|
230
296
|
createComment(
|
|
231
297
|
element: Element,
|
|
232
298
|
text: string,
|
|
233
|
-
|
|
234
|
-
changes?: CommentManager.CommentManager.ChangeRecord[],
|
|
235
|
-
options?: {clientX: number, clientY: number},
|
|
299
|
+
options?: CreateCommentOptions,
|
|
236
300
|
): CommentThread|null {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
anchor = result.anchor;
|
|
244
|
-
anchorEl = result.anchorElement ?? element;
|
|
245
|
-
}
|
|
301
|
+
const author = options?.author ?? 'DEVELOPER';
|
|
302
|
+
const changes = options?.changes;
|
|
303
|
+
const pendingDraft = options?.pendingDraft;
|
|
304
|
+
let resolved: {anchor: CommentAnchorSignature, anchorElement: Element}|null;
|
|
305
|
+
if (pendingDraft) {
|
|
306
|
+
resolved = {anchor: pendingDraft.anchor, anchorElement: pendingDraft.element};
|
|
246
307
|
} else {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return null;
|
|
308
|
+
resolved = this.#resolveAnchor(element, options?.coordinates);
|
|
309
|
+
if (!resolved) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
252
312
|
}
|
|
253
313
|
|
|
314
|
+
const {anchor, anchorElement} = resolved;
|
|
315
|
+
|
|
254
316
|
const thread = this.#commentManager.createCommentThread(anchor, text, author, changes);
|
|
255
317
|
// Non-DOM anchors (e.g. canvas-rendered timeline entries) manage their own overlays
|
|
256
318
|
// and do not have individual backing DOM nodes to cache or observe.
|
|
257
319
|
if (isDomTrackedAnchor(anchor)) {
|
|
258
|
-
this.#liveNodeCache.set(thread,
|
|
320
|
+
this.#liveNodeCache.set(thread, anchorElement);
|
|
259
321
|
|
|
260
322
|
const observer = this.#getIntersectionObserver();
|
|
261
|
-
observer.observe(
|
|
262
|
-
this.#observedThreads.add(
|
|
323
|
+
observer.observe(anchorElement);
|
|
324
|
+
this.#observedThreads.add(anchorElement);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (pendingDraft) {
|
|
328
|
+
this.clearPendingAnchor();
|
|
329
|
+
} else {
|
|
330
|
+
this.#updatePositions();
|
|
263
331
|
}
|
|
264
332
|
|
|
265
|
-
this.#updatePositions();
|
|
266
333
|
return thread;
|
|
267
334
|
}
|
|
268
335
|
|
|
336
|
+
#resolveAnchor(element: Element, options?: {clientX: number, clientY: number}):
|
|
337
|
+
{anchor: CommentAnchorSignature, anchorElement: Element}|null {
|
|
338
|
+
let anchorElement: Element|null = null;
|
|
339
|
+
let anchor: CommentAnchorSignature|null = null;
|
|
340
|
+
const customResolver = getCustomAnchorResolverForElement(element);
|
|
341
|
+
if (customResolver) {
|
|
342
|
+
const result = customResolver.resolve(element, options);
|
|
343
|
+
if (result) {
|
|
344
|
+
anchor = result.anchor;
|
|
345
|
+
anchorElement = result.anchorElement ?? element;
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
anchorElement = resolveCommentAnchorElement(element, options);
|
|
349
|
+
anchor = resolveCommentAnchor(element, undefined, options);
|
|
350
|
+
}
|
|
351
|
+
if (!anchor || !anchorElement) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
return {anchor, anchorElement};
|
|
355
|
+
}
|
|
356
|
+
|
|
269
357
|
getCommentThread(id: string): CommentThread|undefined {
|
|
270
358
|
return this.#commentManager.getCommentThread(id);
|
|
271
359
|
}
|
|
@@ -405,12 +493,54 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
405
493
|
|
|
406
494
|
this.#pinPositions = newPins;
|
|
407
495
|
this.#highlightRects = newHighlights;
|
|
496
|
+
this.#updatePendingPositions(scrollX, scrollY);
|
|
408
497
|
this.dispatchEventToListeners(Events.POSITIONS_UPDATED, {
|
|
409
498
|
pins: newPins,
|
|
410
499
|
highlights: newHighlights,
|
|
411
500
|
});
|
|
412
501
|
}
|
|
413
502
|
|
|
503
|
+
#updatePendingPositions(scrollX: number, scrollY: number): void {
|
|
504
|
+
if (!this.#pendingDraft) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const visibleRect = computeVisibleRect(this.#pendingDraft.element);
|
|
509
|
+
if (!visibleRect) {
|
|
510
|
+
if (this.#pendingDraft.pin) {
|
|
511
|
+
this.#pendingDraft.pin.visible = false;
|
|
512
|
+
}
|
|
513
|
+
if (this.#pendingDraft.highlight) {
|
|
514
|
+
this.#pendingDraft.highlight.visible = false;
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const anchorRect = this.#pendingDraft.element.getBoundingClientRect();
|
|
520
|
+
const pinOffset = this.#pendingDraft.pinOffset;
|
|
521
|
+
const pinClientX = pinOffset ?
|
|
522
|
+
Math.min(Math.max(anchorRect.left + pinOffset.offsetX, visibleRect.left), visibleRect.right) :
|
|
523
|
+
visibleRect.right;
|
|
524
|
+
const pinClientY = pinOffset ?
|
|
525
|
+
Math.min(Math.max(anchorRect.top + pinOffset.offsetY, visibleRect.top), visibleRect.bottom) :
|
|
526
|
+
visibleRect.top;
|
|
527
|
+
|
|
528
|
+
this.#pendingDraft.pin = {
|
|
529
|
+
top: scrollY + pinClientY - 12,
|
|
530
|
+
left: scrollX + pinClientX - 12,
|
|
531
|
+
visible: true,
|
|
532
|
+
index: this.#commentManager.getCommentThreads().length + 1,
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
this.#pendingDraft.highlight = {
|
|
536
|
+
top: scrollY + visibleRect.top,
|
|
537
|
+
left: scrollX + visibleRect.left,
|
|
538
|
+
width: visibleRect.width,
|
|
539
|
+
height: visibleRect.height,
|
|
540
|
+
visible: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
414
544
|
/**
|
|
415
545
|
* Initializes event listeners and lifecycle observers across the target DOM container.
|
|
416
546
|
*
|
|
@@ -420,17 +550,15 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
420
550
|
* - A ResizeObserver to recalculate overlay coordinates when DevTools panels or drawers are resized.
|
|
421
551
|
* - A MutationObserver to automatically rematch existing comment anchors when the DOM re-renders.
|
|
422
552
|
*/
|
|
423
|
-
start(rootOrOptions?: Document|Element|StartOptions
|
|
553
|
+
start(rootOrOptions?: Document|Element|StartOptions): void {
|
|
424
554
|
let root: Document|Element|undefined;
|
|
425
555
|
let scrollTarget: EventTarget|undefined;
|
|
426
556
|
let resizeTarget: Element|undefined;
|
|
427
|
-
let text = defaultText;
|
|
428
557
|
|
|
429
558
|
if (rootOrOptions && !(rootOrOptions instanceof Document) && !(rootOrOptions instanceof Element)) {
|
|
430
559
|
root = rootOrOptions.root;
|
|
431
560
|
scrollTarget = rootOrOptions.scrollTarget;
|
|
432
561
|
resizeTarget = rootOrOptions.resizeTarget;
|
|
433
|
-
text = rootOrOptions.defaultText ?? defaultText;
|
|
434
562
|
} else if (rootOrOptions) {
|
|
435
563
|
root = rootOrOptions;
|
|
436
564
|
}
|
|
@@ -440,7 +568,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
440
568
|
resizeTarget = resizeTarget || (root instanceof Document ? (root.body || root.documentElement) : root);
|
|
441
569
|
|
|
442
570
|
this.stop();
|
|
443
|
-
this.#installClickListener(root
|
|
571
|
+
this.#installClickListener(root);
|
|
444
572
|
this.#installScrollListener(scrollTarget);
|
|
445
573
|
this.#installResizeObserver(resizeTarget);
|
|
446
574
|
this.#installMutationObserver(root);
|
|
@@ -451,6 +579,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
451
579
|
*/
|
|
452
580
|
stop(): void {
|
|
453
581
|
document.body.style.cursor = '';
|
|
582
|
+
this.clearPendingAnchor();
|
|
454
583
|
this.#removeClickListener();
|
|
455
584
|
this.#removeScrollListener();
|
|
456
585
|
this.#removeResizeObserver();
|
|
@@ -465,12 +594,12 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
465
594
|
* Sets up capturing click, hover, and pointer interaction listeners on the container.
|
|
466
595
|
*
|
|
467
596
|
* When Comment Mode is active:
|
|
468
|
-
* - Clicks on anchorable elements
|
|
597
|
+
* - Clicks on anchorable elements open a comment thread creation draft and consume the click event,
|
|
469
598
|
* preventing normal DevTools UI triggers such as node selection or navigation.
|
|
470
599
|
* - Pointer and mouse press events are suppressed to prevent accidental text selections or drag interactions.
|
|
471
600
|
* - Hover events compute and display a real-time preview highlight over the candidate anchor element.
|
|
472
601
|
*/
|
|
473
|
-
#installClickListener(container: Element|Document = document
|
|
602
|
+
#installClickListener(container: Element|Document = document): void {
|
|
474
603
|
this.#removeClickListener();
|
|
475
604
|
this.#clickContainer = container;
|
|
476
605
|
|
|
@@ -478,15 +607,15 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
|
|
|
478
607
|
if (!this.isCommentMode()) {
|
|
479
608
|
return;
|
|
480
609
|
}
|
|
610
|
+
|
|
481
611
|
const composedTarget = event.composedPath()[0];
|
|
482
612
|
const target = (composedTarget instanceof Element) ? composedTarget : event.target;
|
|
483
613
|
if (!(target instanceof Element)) {
|
|
484
614
|
return;
|
|
485
615
|
}
|
|
486
|
-
|
|
487
|
-
const options = {clientX:
|
|
488
|
-
|
|
489
|
-
if (thread) {
|
|
616
|
+
|
|
617
|
+
const options = event instanceof MouseEvent ? {clientX: event.clientX, clientY: event.clientY} : undefined;
|
|
618
|
+
if (this.handleElementClick(target, options)) {
|
|
490
619
|
event.consume(true);
|
|
491
620
|
}
|
|
492
621
|
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import '../components/tooltips/tooltips.js';
|
|
6
|
+
|
|
7
|
+
import * as i18n from '../../core/i18n/i18n.js';
|
|
8
|
+
import type * as CommentManager from '../../models/comment_manager/comment_manager.js';
|
|
9
|
+
import * as UI from '../../ui/legacy/legacy.js';
|
|
10
|
+
import * as Lit from '../../ui/lit/lit.js';
|
|
11
|
+
import * as Input from '../components/input/input.js';
|
|
12
|
+
import * as MarkdownView from '../components/markdown_view/markdown_view.js';
|
|
13
|
+
|
|
14
|
+
import commentThreadWidgetStyles from './commentThreadWidget.css.js';
|
|
15
|
+
|
|
16
|
+
const {html, render, Directives: {createRef, ref}} = Lit;
|
|
17
|
+
|
|
18
|
+
const UIStrings = {
|
|
19
|
+
/**
|
|
20
|
+
* @description Link text in the info tooltip for learning more about comments sent to the agent.
|
|
21
|
+
*/
|
|
22
|
+
learnMore: 'Learn more',
|
|
23
|
+
/**
|
|
24
|
+
* @description Text next to the checkmark in the comment thread header indicating that comments have been sent to
|
|
25
|
+
* the agent.
|
|
26
|
+
*/
|
|
27
|
+
sent: 'Sent',
|
|
28
|
+
/**
|
|
29
|
+
* @description Alt text for the checkmark icon in the comment thread header indicating that comments have been sent
|
|
30
|
+
* to the agent.
|
|
31
|
+
*/
|
|
32
|
+
sentCheckmark: 'Sent checkmark',
|
|
33
|
+
/**
|
|
34
|
+
* @description Label for the agent response section in the comment thread.
|
|
35
|
+
*/
|
|
36
|
+
response: 'Response:',
|
|
37
|
+
/**
|
|
38
|
+
* @description Label for the aria-label of the add comment button.
|
|
39
|
+
*/
|
|
40
|
+
addCommentButton: 'Add comment',
|
|
41
|
+
/**
|
|
42
|
+
* @description aria-label for the comment text area.
|
|
43
|
+
*/
|
|
44
|
+
commentInputAriaLabel: 'Comment input',
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
const UIStringsNotTranslate = {
|
|
48
|
+
/**
|
|
49
|
+
* @description Disclaimer text in the comment thread info tooltip.
|
|
50
|
+
*/
|
|
51
|
+
inputDisclaimer:
|
|
52
|
+
'Comment strings, DOM hierarchy snippets, tracked CSS and DOM changes, Visual Element (VE) paths and signatures, and tracked presenter changes are sent to the connected third-party agent to assist with debugging and code updates.',
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
const str_ = i18n.i18n.registerUIStrings('ui/comments/CommentThreadWidget.ts', UIStrings);
|
|
56
|
+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
57
|
+
const lockedString = i18n.i18n.lockedString;
|
|
58
|
+
|
|
59
|
+
export interface ViewInput {
|
|
60
|
+
title: string;
|
|
61
|
+
comments: CommentManager.CommentManager.Comment[];
|
|
62
|
+
commentText: string;
|
|
63
|
+
textAreaRef: Lit.Directives.Ref<HTMLTextAreaElement>;
|
|
64
|
+
onLearnMoreClick: () => void;
|
|
65
|
+
onAddComment: (text: string) => void;
|
|
66
|
+
onCommentTextChange: (event: Event) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type ViewOutput = undefined;
|
|
70
|
+
|
|
71
|
+
export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTMLElement): void => {
|
|
72
|
+
const hasComment = input.comments.length > 0;
|
|
73
|
+
|
|
74
|
+
// clang-format off
|
|
75
|
+
render(html`
|
|
76
|
+
<style>${Input.textInputStyles}${commentThreadWidgetStyles}</style>
|
|
77
|
+
<div class="comment-thread-widget ${hasComment ? 'submitted' : ''}">
|
|
78
|
+
<div class="header">
|
|
79
|
+
<span class="selected-item">
|
|
80
|
+
<span class="selected-item-text">${input.title}</span>
|
|
81
|
+
</span>
|
|
82
|
+
${hasComment ? html`
|
|
83
|
+
<div class="sent-status">
|
|
84
|
+
<devtools-icon
|
|
85
|
+
class="check-icon"
|
|
86
|
+
name="checkmark"
|
|
87
|
+
aria-label=${i18nString(UIStrings.sentCheckmark)}>
|
|
88
|
+
</devtools-icon>
|
|
89
|
+
<span>${i18nString(UIStrings.sent)}</span>
|
|
90
|
+
</div>
|
|
91
|
+
` : Lit.nothing}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
${hasComment ? html`
|
|
95
|
+
${input.comments.map(comment => comment.author === 'DEVELOPER' ? html`
|
|
96
|
+
<div class="comment-text">
|
|
97
|
+
${comment.text}
|
|
98
|
+
</div>
|
|
99
|
+
` : html`
|
|
100
|
+
<div class="agent-response">
|
|
101
|
+
<div class="elbow"></div>
|
|
102
|
+
<div class="response-content">
|
|
103
|
+
<div class="response-header">${i18nString(UIStrings.response)}</div>
|
|
104
|
+
${MarkdownView.MarkdownView.renderTextAsMarkdown(
|
|
105
|
+
comment.text,
|
|
106
|
+
new MarkdownView.MarkdownView.MarkdownInsightRenderer(),
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
`)}
|
|
111
|
+
` : Lit.nothing}
|
|
112
|
+
|
|
113
|
+
${!hasComment ? html`
|
|
114
|
+
<textarea
|
|
115
|
+
${ref(input.textAreaRef)}
|
|
116
|
+
class="devtools-text-input input"
|
|
117
|
+
rows="1"
|
|
118
|
+
aria-label=${i18nString(UIStrings.commentInputAriaLabel)}
|
|
119
|
+
.value=${input.commentText}
|
|
120
|
+
@input=${input.onCommentTextChange}
|
|
121
|
+
@keydown=${(event: KeyboardEvent) => {
|
|
122
|
+
if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
input.onAddComment(input.commentText);
|
|
125
|
+
}
|
|
126
|
+
}}
|
|
127
|
+
></textarea>
|
|
128
|
+
<div class="footer">
|
|
129
|
+
<devtools-icon
|
|
130
|
+
class="info-icon"
|
|
131
|
+
name="info"
|
|
132
|
+
aria-label="Info"
|
|
133
|
+
aria-details="comment-thread-info-tooltip"
|
|
134
|
+
tabindex="0"
|
|
135
|
+
></devtools-icon>
|
|
136
|
+
<devtools-tooltip
|
|
137
|
+
id="comment-thread-info-tooltip"
|
|
138
|
+
variant="rich"
|
|
139
|
+
>
|
|
140
|
+
<div class="info-tooltip-container">
|
|
141
|
+
${lockedString(UIStringsNotTranslate.inputDisclaimer)}
|
|
142
|
+
<button
|
|
143
|
+
class="tooltip-link"
|
|
144
|
+
role="link"
|
|
145
|
+
@click=${input.onLearnMoreClick}
|
|
146
|
+
>${i18nString(UIStrings.learnMore)}</button>
|
|
147
|
+
</div>
|
|
148
|
+
</devtools-tooltip>
|
|
149
|
+
<devtools-button
|
|
150
|
+
aria-label=${i18nString(UIStrings.addCommentButton)}
|
|
151
|
+
.disabled=${!input.commentText.trim()}
|
|
152
|
+
@click=${() => input.onAddComment(input.commentText)}>
|
|
153
|
+
${i18nString(UIStrings.addCommentButton)}
|
|
154
|
+
</devtools-button>
|
|
155
|
+
</div>
|
|
156
|
+
` : Lit.nothing}
|
|
157
|
+
</div>
|
|
158
|
+
`, target);
|
|
159
|
+
// clang-format on
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
type View = typeof DEFAULT_VIEW;
|
|
163
|
+
|
|
164
|
+
export class CommentThreadWidget extends UI.Widget.Widget {
|
|
165
|
+
title = 'Comment Thread';
|
|
166
|
+
#comments: CommentManager.CommentManager.Comment[] = [];
|
|
167
|
+
#commentText = '';
|
|
168
|
+
#textAreaRef = createRef<HTMLTextAreaElement>();
|
|
169
|
+
#view: View;
|
|
170
|
+
onAddComment?: (text: string) => void;
|
|
171
|
+
|
|
172
|
+
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
|
|
173
|
+
super(element);
|
|
174
|
+
this.#view = view;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
override wasShown(): void {
|
|
178
|
+
super.wasShown();
|
|
179
|
+
this.requestUpdate();
|
|
180
|
+
void this.updateComplete.then(() => {
|
|
181
|
+
this.#textAreaRef.value?.focus({preventScroll: true});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
set comments(comments: CommentManager.CommentManager.Comment[]) {
|
|
186
|
+
this.#comments = comments;
|
|
187
|
+
this.requestUpdate();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#handleLearnMoreClick = (): void => {
|
|
191
|
+
// TODO: wire up the learn more click
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
#handleAddComment = (text: string): void => {
|
|
195
|
+
const commentText = text.trim();
|
|
196
|
+
if (commentText && this.onAddComment) {
|
|
197
|
+
this.onAddComment(commentText);
|
|
198
|
+
this.#commentText = '';
|
|
199
|
+
this.requestUpdate();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
#handleCommentTextChange = (event: Event): void => {
|
|
204
|
+
const input = event.target as HTMLTextAreaElement;
|
|
205
|
+
this.#commentText = input.value;
|
|
206
|
+
this.requestUpdate();
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
override performUpdate(): void {
|
|
210
|
+
const viewInput: ViewInput = {
|
|
211
|
+
title: this.title,
|
|
212
|
+
comments: this.#comments,
|
|
213
|
+
commentText: this.#commentText,
|
|
214
|
+
textAreaRef: this.#textAreaRef,
|
|
215
|
+
onLearnMoreClick: this.#handleLearnMoreClick,
|
|
216
|
+
onAddComment: this.#handleAddComment,
|
|
217
|
+
onCommentTextChange: this.#handleCommentTextChange,
|
|
218
|
+
};
|
|
219
|
+
this.#view(viewInput, undefined, this.contentElement);
|
|
220
|
+
}
|
|
221
|
+
}
|