chrome-devtools-frontend
Version:
Chrome DevTools UI
121 lines (104 loc) • 3.5 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 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 backend NodeId for Elements panel DOM nodes (`data-backend-node-id`) */
backendNodeId?: number;
/** 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';
changes?: Array<Record<string, unknown>>;
}
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 id = `comment-${this.#nextId++}`;
const thread: CommentThread = {
id,
anchor,
comments: [{
author,
text,
timestamp: Date.now(),
}],
status: 'ACTIVE',
changes,
};
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());
}
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, []);
}
}