chrome-devtools-frontend
Version:
Chrome DevTools UI
163 lines (143 loc) • 4.67 kB
text/typescript
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Common from '../../core/common/common.js';
export interface EditorAnchorSignature {
/** 1-based line number for CodeMirror text editor anchors */
lineNumber: number;
/** File path associated with the editor */
filePath?: string;
}
export interface DOMNodeAnchorSignature {
/** Backend NodeId for DOM nodes (`data-backend-node-id`) */
backendNodeId: number;
/** Target ID associated with the DOM node (`data-target-id`) */
targetId: string;
}
export interface CommentAnchorSignature {
/** Visual logging tree path, e.g. "Panel: elements > Pane: styles > TreeOutline > TreeItem: color" */
vePath: string;
/** Normalized text content of the target node */
textSignature: string;
/** Text content of the parent container VE node for sibling disambiguation */
parentTextSignature?: string;
/** 0-indexed position among siblings sharing the same visual logging path */
siblingIndex?: number;
/** Optional backend RequestId for Network panel elements (`data-network-request-id`) */
networkRequestId?: string;
/** Optional DOM node identifiers (`data-backend-node-id`, `data-target-id`) */
node?: DOMNodeAnchorSignature;
/** Optional editor anchor coordinates for CodeMirror text editors */
editor?: EditorAnchorSignature;
}
export interface Comment {
author: 'DEVELOPER'|'AGENT';
text: string;
timestamp: number;
}
export interface CommentThread {
id: string;
anchor: CommentAnchorSignature;
comments: Comment[];
status: 'ACTIVE'|'RESOLVED';
transmitted?: boolean;
changes?: Array<Record<string, unknown>>;
index: number;
}
export const enum Events {
COMMENT_THREADS_CHANGED = 'CommentThreadsChanged',
COMMENT_MODE_CHANGED = 'CommentModeChanged',
}
export interface EventTypes {
[Events.COMMENT_THREADS_CHANGED]: CommentThread[];
[Events.COMMENT_MODE_CHANGED]: boolean;
}
/**
* Headless model managing comment thread data, CRUD operations, and comment mode.
*/
export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
readonly #commentThreads = new Map<string, CommentThread>();
#commentMode = false;
#nextId = 1;
setCommentMode(active: boolean): void {
if (this.#commentMode === active) {
return;
}
this.#commentMode = active;
this.dispatchEventToListeners(Events.COMMENT_MODE_CHANGED, active);
}
isCommentMode(): boolean {
return this.#commentMode;
}
createCommentThread(
anchor: CommentAnchorSignature,
text: string,
author: 'DEVELOPER'|'AGENT' = 'DEVELOPER',
changes?: Array<Record<string, unknown>>,
): CommentThread {
const index = this.#nextId++;
const id = `comment-${index}`;
const comment: Comment = {
author,
text,
timestamp: Date.now(),
};
const thread: CommentThread = {
id,
anchor,
comments: [comment],
status: 'ACTIVE',
transmitted: false,
changes,
index,
};
this.#commentThreads.set(id, thread);
this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
return thread;
}
getCommentThread(id: string): CommentThread|undefined {
return this.#commentThreads.get(id);
}
getCommentThreads(): CommentThread[] {
return Array.from(this.#commentThreads.values());
}
takeComments(): CommentThread[] {
const threads: CommentThread[] = [];
for (const thread of this.#commentThreads.values()) {
if (!thread.transmitted) {
thread.transmitted = true;
threads.push(thread);
}
}
return threads;
}
resolveCommentThread(threadId: string, replyText?: string): boolean {
const thread = this.#commentThreads.get(threadId);
if (!thread) {
return false;
}
if (replyText && replyText.trim().length > 0) {
const comment: Comment = {
author: 'AGENT',
text: replyText.trim(),
timestamp: Date.now(),
};
thread.comments.push(comment);
}
thread.status = 'RESOLVED';
this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
return true;
}
removeCommentThread(id: string): void {
if (!this.#commentThreads.has(id)) {
return;
}
this.#commentThreads.delete(id);
this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, this.getCommentThreads());
}
clear(): void {
this.setCommentMode(false);
this.#commentThreads.clear();
this.dispatchEventToListeners(Events.COMMENT_THREADS_CHANGED, []);
}
}