@diegopetrucci/pi-annotate-last-message 0.1.9 → 0.1.11
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/README.md +10 -2
- package/glimpseui.d.ts +87 -78
- package/index.ts +218 -174
- package/package.json +2 -1
- package/prompt.ts +87 -66
- package/quiet-glimpse.ts +161 -153
- package/session.ts +94 -89
- package/theme.ts +253 -0
- package/types.ts +23 -23
- package/ui.ts +35 -7
- package/web/app.js +348 -162
- package/web/index.html +153 -24
- package/web/md-renderer.js +458 -0
package/web/app.js
CHANGED
|
@@ -1,211 +1,397 @@
|
|
|
1
1
|
/* global window, document */
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Markdown parsing helpers are provided by the md-renderer.js script that
|
|
4
|
+
// ui.ts inlines before this file. Access them via globalThis.__tlhMdRenderer.
|
|
4
5
|
|
|
6
|
+
const messageData = JSON.parse(
|
|
7
|
+
document.getElementById("annotate-last-message-data").textContent || "{}",
|
|
8
|
+
);
|
|
5
9
|
if (!Array.isArray(messageData.lines)) messageData.lines = [];
|
|
6
10
|
if (!Array.isArray(messageData.sections)) messageData.sections = [];
|
|
7
11
|
|
|
8
12
|
const state = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
overallComment: "",
|
|
14
|
+
inlineComments: new Map(),
|
|
15
|
+
sectionComments: new Map(),
|
|
12
16
|
};
|
|
13
17
|
|
|
14
18
|
const elements = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
messageLines: document.getElementById("message-lines"),
|
|
20
|
+
overallComment: document.getElementById("overall-comment"),
|
|
21
|
+
sectionComments: document.getElementById("section-comments"),
|
|
22
|
+
status: document.getElementById("status"),
|
|
23
|
+
submitButton: document.getElementById("submit-button"),
|
|
24
|
+
cancelButton: document.getElementById("cancel-button"),
|
|
21
25
|
};
|
|
22
26
|
|
|
27
|
+
// Compute fence context once over the complete source message. Section cards
|
|
28
|
+
// reuse this source-line metadata so a section that starts after a blank line
|
|
29
|
+
// inside a fence remains a code preview instead of reopening markdown parsing.
|
|
30
|
+
const fullMessageFenceState = globalThis.__tlhMdRenderer.applyFenceState(
|
|
31
|
+
messageData.lines.map((line) => line.text),
|
|
32
|
+
);
|
|
33
|
+
|
|
23
34
|
function feedbackCount() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
let count = state.overallComment.trim().length > 0 ? 1 : 0;
|
|
36
|
+
for (const value of state.inlineComments.values()) {
|
|
37
|
+
if (value.trim().length > 0) count += 1;
|
|
38
|
+
}
|
|
39
|
+
for (const value of state.sectionComments.values()) {
|
|
40
|
+
if (value.trim().length > 0) count += 1;
|
|
41
|
+
}
|
|
42
|
+
return count;
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
function setStatus(message, status = "idle") {
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
elements.status.textContent = message;
|
|
47
|
+
elements.status.dataset.state = status;
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
function updateSubmitState() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
const count = feedbackCount();
|
|
52
|
+
elements.submitButton.disabled = count === 0;
|
|
53
|
+
if (count === 0) {
|
|
54
|
+
setStatus("Add any feedback you want to send to the agent.");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const noun = count === 1 ? "item" : "items";
|
|
58
|
+
setStatus(`Ready to submit ${count} feedback ${noun}.`, "ready");
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
function setInlineComment(lineNumber, value) {
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
state.inlineComments.set(lineNumber, value);
|
|
63
|
+
updateSubmitState();
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
function setSectionComment(sectionId, value) {
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
state.sectionComments.set(sectionId, value);
|
|
68
|
+
updateSubmitState();
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
function createInlineEditor(line) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function createLineRow(line) {
|
|
79
|
-
const wrapper = document.createElement("div");
|
|
80
|
-
wrapper.className = "message-line";
|
|
81
|
-
|
|
82
|
-
const row = document.createElement("div");
|
|
83
|
-
row.className = "message-line-row";
|
|
84
|
-
|
|
85
|
-
const lineNumber = document.createElement("div");
|
|
86
|
-
lineNumber.className = "line-number";
|
|
87
|
-
lineNumber.textContent = String(line.number);
|
|
88
|
-
row.append(lineNumber);
|
|
89
|
-
|
|
90
|
-
const lineText = document.createElement("pre");
|
|
91
|
-
lineText.className = "line-text";
|
|
92
|
-
lineText.textContent = line.text.length > 0 ? line.text : " ";
|
|
93
|
-
row.append(lineText);
|
|
94
|
-
|
|
95
|
-
const toggle = document.createElement("button");
|
|
96
|
-
toggle.className = "inline-toggle";
|
|
97
|
-
toggle.type = "button";
|
|
98
|
-
toggle.textContent = "Add inline note";
|
|
99
|
-
row.append(toggle);
|
|
100
|
-
|
|
101
|
-
const editor = createInlineEditor(line);
|
|
102
|
-
const syncToggle = () => {
|
|
103
|
-
const hasValue = (state.inlineComments.get(line.number) || "").trim().length > 0;
|
|
104
|
-
toggle.dataset.active = hasValue || !editor.container.hidden ? "true" : "false";
|
|
105
|
-
toggle.textContent = hasValue ? "Edit inline note" : "Add inline note";
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
editor.textarea.addEventListener("input", () => {
|
|
109
|
-
setInlineComment(line.number, editor.textarea.value);
|
|
110
|
-
syncToggle();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
toggle.addEventListener("click", () => {
|
|
114
|
-
editor.container.hidden = !editor.container.hidden;
|
|
115
|
-
syncToggle();
|
|
116
|
-
if (!editor.container.hidden) {
|
|
117
|
-
editor.textarea.focus();
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
wrapper.append(row);
|
|
122
|
-
wrapper.append(editor.container);
|
|
123
|
-
syncToggle();
|
|
124
|
-
return wrapper;
|
|
72
|
+
const container = document.createElement("div");
|
|
73
|
+
container.className = "inline-editor";
|
|
74
|
+
container.hidden = true;
|
|
75
|
+
|
|
76
|
+
const meta = document.createElement("p");
|
|
77
|
+
meta.className = "line-meta";
|
|
78
|
+
meta.textContent = `Inline note for line ${line.number}`;
|
|
79
|
+
container.append(meta);
|
|
80
|
+
|
|
81
|
+
const textarea = document.createElement("textarea");
|
|
82
|
+
textarea.placeholder =
|
|
83
|
+
"Explain what should change here, what is unclear, or what planning detail is missing.";
|
|
84
|
+
textarea.value = state.inlineComments.get(line.number) || "";
|
|
85
|
+
container.append(textarea);
|
|
86
|
+
|
|
87
|
+
return { container, textarea };
|
|
125
88
|
}
|
|
126
89
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Markdown DOM rendering helpers
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Render an array of inline tokens into a parent element using
|
|
96
|
+
* createElement/textContent only (no innerHTML).
|
|
97
|
+
*/
|
|
98
|
+
function renderInlineTokens(parent, tokens) {
|
|
99
|
+
for (const token of tokens) {
|
|
100
|
+
switch (token.type) {
|
|
101
|
+
case "text":
|
|
102
|
+
parent.append(document.createTextNode(token.text));
|
|
103
|
+
break;
|
|
104
|
+
case "code": {
|
|
105
|
+
const code = document.createElement("code");
|
|
106
|
+
code.textContent = token.text;
|
|
107
|
+
parent.append(code);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "bold": {
|
|
111
|
+
const strong = document.createElement("strong");
|
|
112
|
+
renderInlineTokens(strong, token.children);
|
|
113
|
+
parent.append(strong);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "italic": {
|
|
117
|
+
const em = document.createElement("em");
|
|
118
|
+
renderInlineTokens(em, token.children);
|
|
119
|
+
parent.append(em);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case "boldItalic": {
|
|
123
|
+
const strong = document.createElement("strong");
|
|
124
|
+
const em = document.createElement("em");
|
|
125
|
+
renderInlineTokens(em, token.children);
|
|
126
|
+
strong.append(em);
|
|
127
|
+
parent.append(strong);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "strikethrough": {
|
|
131
|
+
const s = document.createElement("s");
|
|
132
|
+
renderInlineTokens(s, token.children);
|
|
133
|
+
parent.append(s);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "link": {
|
|
137
|
+
// Render the link label text inside an <a> element.
|
|
138
|
+
// href is intentionally not set to avoid javascript: URL XSS;
|
|
139
|
+
// the URL is shown as muted text beside the link.
|
|
140
|
+
const a = document.createElement("a");
|
|
141
|
+
renderInlineTokens(a, token.labelTokens);
|
|
142
|
+
const urlSpan = document.createElement("span");
|
|
143
|
+
urlSpan.className = "md-link-url";
|
|
144
|
+
urlSpan.textContent = ` (${token.url})`;
|
|
145
|
+
parent.append(a);
|
|
146
|
+
parent.append(urlSpan);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
if (typeof token.text === "string") {
|
|
151
|
+
parent.append(document.createTextNode(token.text));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
137
156
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Populate a container element with DOM nodes for a classified (non-fence) line.
|
|
159
|
+
* Leading indentation is preserved via text nodes where relevant.
|
|
160
|
+
*/
|
|
161
|
+
function renderClassifiedLine(container, classification) {
|
|
162
|
+
switch (classification.type) {
|
|
163
|
+
case "blank":
|
|
164
|
+
container.textContent = " ";
|
|
165
|
+
break;
|
|
166
|
+
case "heading": {
|
|
167
|
+
const h = document.createElement(`h${classification.level}`);
|
|
168
|
+
renderInlineTokens(h, globalThis.__tlhMdRenderer.tokenizeLine(classification.text));
|
|
169
|
+
container.append(h);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "hr":
|
|
173
|
+
container.append(document.createElement("hr"));
|
|
174
|
+
break;
|
|
175
|
+
case "blockquote": {
|
|
176
|
+
const bq = document.createElement("blockquote");
|
|
177
|
+
renderInlineTokens(bq, globalThis.__tlhMdRenderer.tokenizeLine(classification.text));
|
|
178
|
+
container.append(bq);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case "ul":
|
|
182
|
+
case "ol": {
|
|
183
|
+
// Preserve leading indentation as a text node.
|
|
184
|
+
if (classification.indent) {
|
|
185
|
+
container.append(document.createTextNode(classification.indent));
|
|
186
|
+
}
|
|
187
|
+
const bullet = document.createElement("span");
|
|
188
|
+
bullet.style.cssText = "color: var(--mdListBullet)";
|
|
189
|
+
bullet.textContent = classification.bullet;
|
|
190
|
+
container.append(bullet);
|
|
191
|
+
container.append(document.createTextNode(" "));
|
|
192
|
+
renderInlineTokens(container, globalThis.__tlhMdRenderer.tokenizeLine(classification.text));
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
default: {
|
|
196
|
+
// 'plain' and any unrecognised types
|
|
197
|
+
const lineText = typeof classification.text === "string" ? classification.text : "";
|
|
198
|
+
renderInlineTokens(container, globalThis.__tlhMdRenderer.tokenizeLine(lineText));
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
142
203
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Build the content of a markdown line-text container element.
|
|
206
|
+
* lineType is one of: 'plain' | 'fence-open' | 'fence-body' | 'fence-close'
|
|
207
|
+
*/
|
|
208
|
+
function renderLineContent(container, rawText, lineType) {
|
|
209
|
+
if (lineType === "fence-body") {
|
|
210
|
+
// Code-block body: raw text, no inline markdown, code-block styling.
|
|
211
|
+
container.style.cssText = "border-left: 3px solid var(--mdCodeBlockBorder); padding-left: 8px;";
|
|
212
|
+
const code = document.createElement("code");
|
|
213
|
+
code.style.cssText = "display: block; color: var(--mdCodeBlock, var(--text))";
|
|
214
|
+
code.textContent = rawText.length > 0 ? rawText : " ";
|
|
215
|
+
container.append(code);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (lineType === "fence-open" || lineType === "fence-close") {
|
|
220
|
+
// Fence delimiter: visible, styled as block-edge marker.
|
|
221
|
+
const span = document.createElement("span");
|
|
222
|
+
span.style.cssText = "color: var(--mdCodeBlockBorder)";
|
|
223
|
+
span.textContent = rawText;
|
|
224
|
+
container.append(span);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Plain line: classify and render inline markdown.
|
|
229
|
+
if (rawText.trim().length === 0) {
|
|
230
|
+
container.textContent = " ";
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
renderClassifiedLine(container, globalThis.__tlhMdRenderer.classifyLine(rawText));
|
|
235
|
+
}
|
|
147
236
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Line row and section card builders
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
function createLineRow(line, lineType) {
|
|
242
|
+
const wrapper = document.createElement("div");
|
|
243
|
+
wrapper.className = "message-line";
|
|
244
|
+
const row = document.createElement("div");
|
|
245
|
+
row.className = "message-line-row";
|
|
246
|
+
|
|
247
|
+
const lineNumber = document.createElement("div");
|
|
248
|
+
lineNumber.className = "line-number";
|
|
249
|
+
lineNumber.textContent = String(line.number);
|
|
250
|
+
row.append(lineNumber);
|
|
251
|
+
|
|
252
|
+
// Use a <div> with markdown-content class so heading/blockquote/hr/etc.
|
|
253
|
+
// CSS rules inside .markdown-content apply correctly.
|
|
254
|
+
const lineText = document.createElement("div");
|
|
255
|
+
lineText.className = "line-text markdown-content";
|
|
256
|
+
renderLineContent(lineText, line.text, lineType);
|
|
257
|
+
row.append(lineText);
|
|
258
|
+
wrapper.append(row);
|
|
259
|
+
|
|
260
|
+
const toggle = document.createElement("button");
|
|
261
|
+
toggle.className = "inline-toggle";
|
|
262
|
+
toggle.type = "button";
|
|
263
|
+
toggle.textContent = "Add inline note";
|
|
264
|
+
row.append(toggle);
|
|
265
|
+
|
|
266
|
+
const editor = createInlineEditor(line);
|
|
267
|
+
const syncToggle = () => {
|
|
268
|
+
const hasValue = (state.inlineComments.get(line.number) || "").trim().length > 0;
|
|
269
|
+
toggle.dataset.active = hasValue || !editor.container.hidden ? "true" : "false";
|
|
270
|
+
toggle.textContent = hasValue ? "Edit inline note" : "Add inline note";
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
editor.textarea.addEventListener("input", () => {
|
|
274
|
+
setInlineComment(line.number, editor.textarea.value);
|
|
275
|
+
syncToggle();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
toggle.addEventListener("click", () => {
|
|
279
|
+
editor.container.hidden = !editor.container.hidden;
|
|
280
|
+
syncToggle();
|
|
281
|
+
if (!editor.container.hidden) {
|
|
282
|
+
editor.textarea.focus();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
wrapper.append(editor.container);
|
|
287
|
+
syncToggle();
|
|
288
|
+
return wrapper;
|
|
289
|
+
}
|
|
155
290
|
|
|
156
|
-
|
|
291
|
+
function createSectionCard(section) {
|
|
292
|
+
const card = document.createElement("div");
|
|
293
|
+
card.className = "section-card";
|
|
294
|
+
|
|
295
|
+
const title = document.createElement("h3");
|
|
296
|
+
const lineRange =
|
|
297
|
+
section.startLine === section.endLine
|
|
298
|
+
? `line ${section.startLine}`
|
|
299
|
+
: `lines ${section.startLine}-${section.endLine}`;
|
|
300
|
+
title.textContent = `Section ${section.index}`;
|
|
301
|
+
card.append(title);
|
|
302
|
+
|
|
303
|
+
const meta = document.createElement("p");
|
|
304
|
+
meta.className = "section-meta";
|
|
305
|
+
meta.textContent = lineRange;
|
|
306
|
+
card.append(meta);
|
|
307
|
+
|
|
308
|
+
const preview = document.createElement("div");
|
|
309
|
+
preview.className = "section-preview markdown-content";
|
|
310
|
+
|
|
311
|
+
const startIndex = Number.isInteger(section.startLine) ? section.startLine - 1 : -1;
|
|
312
|
+
const endIndex = Number.isInteger(section.endLine) ? section.endLine : -1;
|
|
313
|
+
const sourceLines =
|
|
314
|
+
startIndex >= 0 && endIndex >= startIndex && endIndex <= messageData.lines.length
|
|
315
|
+
? messageData.lines.slice(startIndex, endIndex).map((line) => line.text)
|
|
316
|
+
: [];
|
|
317
|
+
for (let idx = 0; idx < sourceLines.length; idx++) {
|
|
318
|
+
const lineDiv = document.createElement("div");
|
|
319
|
+
const sourceIndex = startIndex >= 0 ? startIndex + idx : -1;
|
|
320
|
+
const lineType = fullMessageFenceState[sourceIndex]?.lineType ?? "plain";
|
|
321
|
+
renderLineContent(lineDiv, sourceLines[idx], lineType);
|
|
322
|
+
preview.append(lineDiv);
|
|
323
|
+
}
|
|
324
|
+
card.append(preview);
|
|
325
|
+
const textarea = document.createElement("textarea");
|
|
326
|
+
textarea.placeholder =
|
|
327
|
+
"Describe what should change across this section or what larger concern should be addressed.";
|
|
328
|
+
textarea.value = state.sectionComments.get(section.id) || "";
|
|
329
|
+
textarea.addEventListener("input", () => {
|
|
330
|
+
setSectionComment(section.id, textarea.value);
|
|
331
|
+
});
|
|
332
|
+
card.append(textarea);
|
|
333
|
+
|
|
334
|
+
return card;
|
|
157
335
|
}
|
|
158
336
|
|
|
159
337
|
function renderMessageLines() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
338
|
+
elements.messageLines.replaceChildren();
|
|
339
|
+
for (let i = 0; i < messageData.lines.length; i++) {
|
|
340
|
+
elements.messageLines.append(
|
|
341
|
+
createLineRow(messageData.lines[i], fullMessageFenceState[i].lineType),
|
|
342
|
+
);
|
|
343
|
+
}
|
|
164
344
|
}
|
|
165
345
|
|
|
166
346
|
function renderSectionComments() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
347
|
+
elements.sectionComments.replaceChildren();
|
|
348
|
+
if (messageData.sections.length === 0) {
|
|
349
|
+
const empty = document.createElement("p");
|
|
350
|
+
empty.className = "empty-hint";
|
|
351
|
+
empty.textContent = "This message does not have any non-empty sections to annotate.";
|
|
352
|
+
elements.sectionComments.append(empty);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
for (const section of messageData.sections) {
|
|
356
|
+
elements.sectionComments.append(createSectionCard(section));
|
|
357
|
+
}
|
|
178
358
|
}
|
|
179
359
|
|
|
180
360
|
function collectPayload() {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
361
|
+
return {
|
|
362
|
+
type: "submit",
|
|
363
|
+
overallComment: state.overallComment,
|
|
364
|
+
inlineComments: Array.from(state.inlineComments.entries()).map(([line, body]) => ({
|
|
365
|
+
line,
|
|
366
|
+
body,
|
|
367
|
+
})),
|
|
368
|
+
sectionComments: Array.from(state.sectionComments.entries()).map(([sectionId, body]) => ({
|
|
369
|
+
sectionId,
|
|
370
|
+
body,
|
|
371
|
+
})),
|
|
372
|
+
};
|
|
187
373
|
}
|
|
188
374
|
|
|
189
375
|
function sendPayload(payload) {
|
|
190
|
-
|
|
191
|
-
|
|
376
|
+
window.glimpse?.send?.(payload);
|
|
377
|
+
window.glimpse?.close?.();
|
|
192
378
|
}
|
|
193
379
|
|
|
194
380
|
function submit() {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
381
|
+
if (feedbackCount() === 0) {
|
|
382
|
+
setStatus("Add at least one comment before submitting.", "error");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
sendPayload(collectPayload());
|
|
200
386
|
}
|
|
201
387
|
|
|
202
388
|
function cancel() {
|
|
203
|
-
|
|
389
|
+
sendPayload({ type: "cancel" });
|
|
204
390
|
}
|
|
205
391
|
|
|
206
392
|
elements.overallComment.addEventListener("input", () => {
|
|
207
|
-
|
|
208
|
-
|
|
393
|
+
state.overallComment = elements.overallComment.value;
|
|
394
|
+
updateSubmitState();
|
|
209
395
|
});
|
|
210
396
|
|
|
211
397
|
elements.submitButton.addEventListener("click", submit);
|
|
@@ -213,15 +399,15 @@ elements.submitButton.addEventListener("click", submit);
|
|
|
213
399
|
elements.cancelButton.addEventListener("click", cancel);
|
|
214
400
|
|
|
215
401
|
document.addEventListener("keydown", (event) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
402
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
submit();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (event.key === "Escape") {
|
|
408
|
+
event.preventDefault();
|
|
409
|
+
cancel();
|
|
410
|
+
}
|
|
225
411
|
});
|
|
226
412
|
|
|
227
413
|
renderMessageLines();
|