pi-studio 0.5.45 → 0.5.47
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 +18 -0
- package/README.md +1 -1
- package/client/studio-client.js +1212 -11
- package/client/studio.css +99 -5
- package/index.ts +5 -2
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
const lineNumberGutterContentEl = document.getElementById("lineNumberGutterContent");
|
|
54
54
|
const lineNumberMeasureEl = document.getElementById("lineNumberMeasure");
|
|
55
55
|
const sourcePreviewEl = document.getElementById("sourcePreview");
|
|
56
|
+
const editorSelectionCommentBtn = document.getElementById("editorSelectionCommentBtn");
|
|
56
57
|
const leftPaneEl = document.getElementById("leftPane");
|
|
57
58
|
const rightPaneEl = document.getElementById("rightPane");
|
|
58
59
|
const sourceBadgeEl = document.getElementById("sourceBadge");
|
|
@@ -304,6 +305,11 @@
|
|
|
304
305
|
let reviewNotesLoadNonce = 0;
|
|
305
306
|
let pendingReviewNoteFocusId = null;
|
|
306
307
|
let pendingReviewNoteInlineFocusId = null;
|
|
308
|
+
let activePreviewCommentSelection = null;
|
|
309
|
+
let suppressEditorSelectionComment = false;
|
|
310
|
+
let suppressedEditorSelectionStart = null;
|
|
311
|
+
let suppressedEditorSelectionEnd = null;
|
|
312
|
+
const previewJumpHighlightState = new WeakMap();
|
|
307
313
|
const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
|
|
308
314
|
const annotationHelpers = globalThis.PiStudioAnnotationHelpers;
|
|
309
315
|
if (!annotationHelpers || typeof annotationHelpers.collectInlineAnnotationMarkers !== "function") {
|
|
@@ -2528,6 +2534,7 @@
|
|
|
2528
2534
|
if (nonce !== responsePreviewRenderNonce || (rightView !== "preview" && rightView !== "editor-preview")) return;
|
|
2529
2535
|
}
|
|
2530
2536
|
|
|
2537
|
+
clearPreviewJumpHighlight(targetEl);
|
|
2531
2538
|
finishPreviewRender(targetEl);
|
|
2532
2539
|
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
|
|
2533
2540
|
applyPreviewAnnotationPlaceholdersToElement(targetEl, previewPrepared.placeholders);
|
|
@@ -2541,6 +2548,15 @@
|
|
|
2541
2548
|
await renderMermaidInElement(targetEl);
|
|
2542
2549
|
await renderMathFallbackInElement(targetEl);
|
|
2543
2550
|
|
|
2551
|
+
const shouldDecoratePreviewComments = supportsPreviewCommentsForCurrentEditor()
|
|
2552
|
+
&& (
|
|
2553
|
+
(pane === "source" && editorView === "preview")
|
|
2554
|
+
|| (pane === "response" && rightView === "editor-preview")
|
|
2555
|
+
);
|
|
2556
|
+
if (shouldDecoratePreviewComments) {
|
|
2557
|
+
decorateRenderedEditorPreviewComments(targetEl, sourceTextEl.value || "");
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2544
2560
|
// Warn if relative images are present but unlikely to resolve (non-file-backed content)
|
|
2545
2561
|
if (!sourceState.path && !(resourceDirInput && resourceDirInput.value.trim())) {
|
|
2546
2562
|
var hasRelativeImages = /!\[.*?\]\((?!https?:\/\/|data:)[^)]+\)/.test(markdown || "");
|
|
@@ -2562,6 +2578,7 @@
|
|
|
2562
2578
|
}
|
|
2563
2579
|
|
|
2564
2580
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
2581
|
+
clearPreviewJumpHighlight(targetEl);
|
|
2565
2582
|
finishPreviewRender(targetEl);
|
|
2566
2583
|
targetEl.innerHTML = buildPreviewErrorHtml("Preview renderer unavailable (" + detail + "). Showing plain markdown.", markdown);
|
|
2567
2584
|
if (pane === "response") {
|
|
@@ -2905,6 +2922,9 @@
|
|
|
2905
2922
|
const value = String(nextText || "");
|
|
2906
2923
|
const preserveScroll = Boolean(options && options.preserveScroll);
|
|
2907
2924
|
const preserveSelection = Boolean(options && options.preserveSelection);
|
|
2925
|
+
if (activePreviewCommentSelection) {
|
|
2926
|
+
clearPreviewCommentSelection();
|
|
2927
|
+
}
|
|
2908
2928
|
const previousScrollTop = sourceTextEl.scrollTop;
|
|
2909
2929
|
const previousScrollLeft = sourceTextEl.scrollLeft;
|
|
2910
2930
|
const previousSelectionStart = sourceTextEl.selectionStart;
|
|
@@ -2943,6 +2963,7 @@
|
|
|
2943
2963
|
if (!options || options.updateMeta !== false) {
|
|
2944
2964
|
scheduleEditorMetaUpdate();
|
|
2945
2965
|
}
|
|
2966
|
+
updateEditorSelectionCommentUi();
|
|
2946
2967
|
}
|
|
2947
2968
|
|
|
2948
2969
|
function setEditorView(nextView) {
|
|
@@ -2961,6 +2982,7 @@
|
|
|
2961
2982
|
}
|
|
2962
2983
|
|
|
2963
2984
|
if (!showPreview) {
|
|
2985
|
+
clearPreviewJumpHighlight(sourcePreviewEl);
|
|
2964
2986
|
finishPreviewRender(sourcePreviewEl);
|
|
2965
2987
|
}
|
|
2966
2988
|
|
|
@@ -2975,6 +2997,7 @@
|
|
|
2975
2997
|
scheduleEditorLineNumberRender();
|
|
2976
2998
|
}
|
|
2977
2999
|
updateReviewNotesUi();
|
|
3000
|
+
updateEditorSelectionCommentUi();
|
|
2978
3001
|
}
|
|
2979
3002
|
|
|
2980
3003
|
function setRightView(nextView) {
|
|
@@ -2989,6 +3012,9 @@
|
|
|
2989
3012
|
window.clearTimeout(responseEditorPreviewTimer);
|
|
2990
3013
|
responseEditorPreviewTimer = null;
|
|
2991
3014
|
}
|
|
3015
|
+
if (rightView !== "editor-preview") {
|
|
3016
|
+
clearPreviewJumpHighlight(critiqueViewEl);
|
|
3017
|
+
}
|
|
2992
3018
|
|
|
2993
3019
|
refreshResponseUi();
|
|
2994
3020
|
syncActionButtons();
|
|
@@ -4082,6 +4108,7 @@
|
|
|
4082
4108
|
lineStart,
|
|
4083
4109
|
lineEnd,
|
|
4084
4110
|
selectedText: typeof note.selectedText === "string" ? note.selectedText : "",
|
|
4111
|
+
selectedDisplayText: typeof note.selectedDisplayText === "string" ? note.selectedDisplayText : "",
|
|
4085
4112
|
};
|
|
4086
4113
|
}
|
|
4087
4114
|
|
|
@@ -4171,6 +4198,7 @@
|
|
|
4171
4198
|
}
|
|
4172
4199
|
updateReviewNotesUi();
|
|
4173
4200
|
renderReviewNotesList();
|
|
4201
|
+
refreshRenderedEditorPreviewComments();
|
|
4174
4202
|
if (editorView === "markdown") {
|
|
4175
4203
|
scheduleEditorLineNumberRender();
|
|
4176
4204
|
}
|
|
@@ -4192,7 +4220,7 @@
|
|
|
4192
4220
|
}
|
|
4193
4221
|
|
|
4194
4222
|
function summarizeReviewNoteQuote(note) {
|
|
4195
|
-
const normalized = String(note && note.selectedText ? note.selectedText : "")
|
|
4223
|
+
const normalized = String(note && (note.selectedDisplayText || note.selectedText) ? (note.selectedDisplayText || note.selectedText) : "")
|
|
4196
4224
|
.replace(/\s+/g, " ")
|
|
4197
4225
|
.trim();
|
|
4198
4226
|
if (!normalized) return "Anchor: current line / empty selection";
|
|
@@ -4252,6 +4280,7 @@
|
|
|
4252
4280
|
lineStart: getLineNumberAtOffset(current, safeStart),
|
|
4253
4281
|
lineEnd: getLineNumberAtOffset(current, Math.max(safeStart, safeEnd - 1)),
|
|
4254
4282
|
selectedText: current.slice(safeStart, safeEnd),
|
|
4283
|
+
selectedDisplayText: current.slice(safeStart, safeEnd),
|
|
4255
4284
|
};
|
|
4256
4285
|
}
|
|
4257
4286
|
const lineRange = getLineRangeAtOffset(current, safeStart);
|
|
@@ -4261,6 +4290,23 @@
|
|
|
4261
4290
|
lineStart: lineRange.lineNumber,
|
|
4262
4291
|
lineEnd: lineRange.lineNumber,
|
|
4263
4292
|
selectedText: current.slice(lineRange.start, lineRange.end),
|
|
4293
|
+
selectedDisplayText: current.slice(lineRange.start, lineRange.end),
|
|
4294
|
+
};
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
function getEditorLineAnchorForReviewNote() {
|
|
4298
|
+
const current = String(sourceTextEl.value || "");
|
|
4299
|
+
const caret = typeof sourceTextEl.selectionStart === "number"
|
|
4300
|
+
? sourceTextEl.selectionStart
|
|
4301
|
+
: 0;
|
|
4302
|
+
const lineRange = getLineRangeAtOffset(current, Math.max(0, Math.min(caret, current.length)));
|
|
4303
|
+
return {
|
|
4304
|
+
selectionStart: lineRange.start,
|
|
4305
|
+
selectionEnd: lineRange.end,
|
|
4306
|
+
lineStart: lineRange.lineNumber,
|
|
4307
|
+
lineEnd: lineRange.lineNumber,
|
|
4308
|
+
selectedText: current.slice(lineRange.start, lineRange.end),
|
|
4309
|
+
selectedDisplayText: current.slice(lineRange.start, lineRange.end),
|
|
4264
4310
|
};
|
|
4265
4311
|
}
|
|
4266
4312
|
|
|
@@ -4314,6 +4360,972 @@
|
|
|
4314
4360
|
return lineMap;
|
|
4315
4361
|
}
|
|
4316
4362
|
|
|
4363
|
+
function supportsPreviewCommentsForCurrentEditor() {
|
|
4364
|
+
return editorLanguage === "markdown";
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4367
|
+
function getPreviewCommentBlockKindLabel(kind) {
|
|
4368
|
+
if (kind === "heading") return "heading";
|
|
4369
|
+
if (kind === "blockquote") return "quote block";
|
|
4370
|
+
if (kind === "list") return "list";
|
|
4371
|
+
if (kind === "code") return "code block";
|
|
4372
|
+
if (kind === "table") return "table";
|
|
4373
|
+
return "paragraph";
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
function supportsPreviewSelectionCommentsForBlockKind(kind) {
|
|
4377
|
+
return kind === "paragraph" || kind === "heading" || kind === "blockquote" || kind === "list";
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
function normalizeVisiblePreviewText(text) {
|
|
4381
|
+
return String(text || "").replace(/\s+/g, " ").trim();
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
function appendMappedPreviewSlice(chars, rawOffsets, lineText, lineBaseOffset, start, end) {
|
|
4385
|
+
const safeStart = Math.max(0, Math.min(start, lineText.length));
|
|
4386
|
+
const safeEnd = Math.max(safeStart, Math.min(end, lineText.length));
|
|
4387
|
+
for (let i = safeStart; i < safeEnd; i += 1) {
|
|
4388
|
+
chars.push(lineText[i]);
|
|
4389
|
+
rawOffsets.push(lineBaseOffset + i);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
function buildPreviewSelectionSourceBody(blockText, kind) {
|
|
4394
|
+
const source = String(blockText || "");
|
|
4395
|
+
const lines = source.split("\n");
|
|
4396
|
+
const lineOffsets = [];
|
|
4397
|
+
let runningOffset = 0;
|
|
4398
|
+
for (const line of lines) {
|
|
4399
|
+
lineOffsets.push(runningOffset);
|
|
4400
|
+
runningOffset += line.length + 1;
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
const chars = [];
|
|
4404
|
+
const rawOffsets = [];
|
|
4405
|
+
|
|
4406
|
+
function appendLineWithStart(lineIndex, start, end) {
|
|
4407
|
+
const line = lineIndex >= 0 && lineIndex < lines.length ? lines[lineIndex] : "";
|
|
4408
|
+
appendMappedPreviewSlice(chars, rawOffsets, line, lineOffsets[lineIndex] || 0, start, end);
|
|
4409
|
+
if (lineIndex < lines.length - 1) {
|
|
4410
|
+
chars.push("\n");
|
|
4411
|
+
rawOffsets.push((lineOffsets[lineIndex] || 0) + line.length);
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
if (kind === "heading") {
|
|
4416
|
+
const firstLine = lines[0] || "";
|
|
4417
|
+
const atxMatch = firstLine.match(/^ {0,3}#{1,6}(?:[ \t]+|$)/);
|
|
4418
|
+
if (atxMatch) {
|
|
4419
|
+
const start = atxMatch[0].length;
|
|
4420
|
+
let end = firstLine.length;
|
|
4421
|
+
const closingMatch = firstLine.slice(start).match(/[ \t]+#+[ \t]*$/);
|
|
4422
|
+
if (closingMatch) {
|
|
4423
|
+
end -= closingMatch[0].length;
|
|
4424
|
+
}
|
|
4425
|
+
appendMappedPreviewSlice(chars, rawOffsets, firstLine, lineOffsets[0] || 0, start, end);
|
|
4426
|
+
return { text: chars.join(""), rawOffsets };
|
|
4427
|
+
}
|
|
4428
|
+
if (lines.length >= 2 && /^ {0,3}(?:={3,}|-{3,})\s*$/.test(lines[1] || "")) {
|
|
4429
|
+
appendMappedPreviewSlice(chars, rawOffsets, firstLine, lineOffsets[0] || 0, 0, firstLine.length);
|
|
4430
|
+
return { text: chars.join(""), rawOffsets };
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
4435
|
+
const line = lines[lineIndex] || "";
|
|
4436
|
+
if (kind === "blockquote") {
|
|
4437
|
+
const prefixMatch = line.match(/^ {0,3}> ?/);
|
|
4438
|
+
appendLineWithStart(lineIndex, prefixMatch ? prefixMatch[0].length : 0, line.length);
|
|
4439
|
+
continue;
|
|
4440
|
+
}
|
|
4441
|
+
if (kind === "list") {
|
|
4442
|
+
if (!line.trim()) {
|
|
4443
|
+
appendLineWithStart(lineIndex, 0, 0);
|
|
4444
|
+
continue;
|
|
4445
|
+
}
|
|
4446
|
+
const itemMatch = line.match(/^ {0,3}(?:[*+-]|\d+[.)])(?:[ \t]+|$)/);
|
|
4447
|
+
if (itemMatch) {
|
|
4448
|
+
appendLineWithStart(lineIndex, itemMatch[0].length, line.length);
|
|
4449
|
+
continue;
|
|
4450
|
+
}
|
|
4451
|
+
const continuationMatch = line.match(/^(?: {1,4}|\t)/);
|
|
4452
|
+
appendLineWithStart(lineIndex, continuationMatch ? continuationMatch[0].length : 0, line.length);
|
|
4453
|
+
continue;
|
|
4454
|
+
}
|
|
4455
|
+
appendLineWithStart(lineIndex, 0, line.length);
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4458
|
+
return { text: chars.join(""), rawOffsets };
|
|
4459
|
+
}
|
|
4460
|
+
|
|
4461
|
+
function buildPreviewInlineDisplayMap(text, rawOffsets) {
|
|
4462
|
+
const source = String(text || "");
|
|
4463
|
+
const rawMap = Array.isArray(rawOffsets) ? rawOffsets : [];
|
|
4464
|
+
const displayChars = [];
|
|
4465
|
+
const charStarts = [];
|
|
4466
|
+
const charEnds = [];
|
|
4467
|
+
|
|
4468
|
+
function appendChar(character, rawStart, rawEnd) {
|
|
4469
|
+
displayChars.push(character);
|
|
4470
|
+
charStarts.push(rawStart);
|
|
4471
|
+
charEnds.push(rawEnd);
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
function appendNestedRange(startIndex, endIndex) {
|
|
4475
|
+
const nested = buildPreviewInlineDisplayMap(
|
|
4476
|
+
source.slice(startIndex, endIndex),
|
|
4477
|
+
rawMap.slice(startIndex, endIndex),
|
|
4478
|
+
);
|
|
4479
|
+
for (let i = 0; i < nested.text.length; i += 1) {
|
|
4480
|
+
appendChar(nested.text[i], nested.charStarts[i], nested.charEnds[i]);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
let index = 0;
|
|
4485
|
+
while (index < source.length) {
|
|
4486
|
+
const remaining = source.slice(index);
|
|
4487
|
+
const linkMatch = remaining.match(/^!?\[([^\]]*)\]\(([^)]*)\)/);
|
|
4488
|
+
if (linkMatch) {
|
|
4489
|
+
const labelStart = index + (remaining[0] === "!" ? 2 : 1);
|
|
4490
|
+
const labelEnd = labelStart + String(linkMatch[1] || "").length;
|
|
4491
|
+
appendNestedRange(labelStart, labelEnd);
|
|
4492
|
+
index += linkMatch[0].length;
|
|
4493
|
+
continue;
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
if (source[index] === "`") {
|
|
4497
|
+
let tickCount = 1;
|
|
4498
|
+
while (source[index + tickCount] === "`") tickCount += 1;
|
|
4499
|
+
const fence = "`".repeat(tickCount);
|
|
4500
|
+
const closeIndex = source.indexOf(fence, index + tickCount);
|
|
4501
|
+
if (closeIndex >= 0) {
|
|
4502
|
+
for (let i = index + tickCount; i < closeIndex; i += 1) {
|
|
4503
|
+
appendChar(source[i], rawMap[i], rawMap[i] + 1);
|
|
4504
|
+
}
|
|
4505
|
+
index = closeIndex + tickCount;
|
|
4506
|
+
continue;
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
if (source[index] === "\\" && index + 1 < source.length) {
|
|
4511
|
+
appendChar(source[index + 1], rawMap[index], rawMap[index + 1] + 1);
|
|
4512
|
+
index += 2;
|
|
4513
|
+
continue;
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
const htmlTagMatch = remaining.match(/^<\/?[A-Za-z][^>]*>/);
|
|
4517
|
+
if (htmlTagMatch) {
|
|
4518
|
+
index += htmlTagMatch[0].length;
|
|
4519
|
+
continue;
|
|
4520
|
+
}
|
|
4521
|
+
|
|
4522
|
+
const emphasisMatch = remaining.match(/^(?:\*\*\*|\*\*|\*|___|__|_|~~)/);
|
|
4523
|
+
if (emphasisMatch) {
|
|
4524
|
+
index += emphasisMatch[0].length;
|
|
4525
|
+
continue;
|
|
4526
|
+
}
|
|
4527
|
+
|
|
4528
|
+
appendChar(source[index], rawMap[index], rawMap[index] + 1);
|
|
4529
|
+
index += 1;
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
return {
|
|
4533
|
+
text: displayChars.join(""),
|
|
4534
|
+
charStarts,
|
|
4535
|
+
charEnds,
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4539
|
+
function buildNormalizedPreviewDisplayMap(displayText, charStarts, charEnds) {
|
|
4540
|
+
const source = String(displayText || "");
|
|
4541
|
+
const outChars = [];
|
|
4542
|
+
const outStarts = [];
|
|
4543
|
+
const outEnds = [];
|
|
4544
|
+
let pendingWhitespaceStart = null;
|
|
4545
|
+
let pendingWhitespaceEnd = null;
|
|
4546
|
+
|
|
4547
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
4548
|
+
const character = source[i];
|
|
4549
|
+
if (/\s/.test(character)) {
|
|
4550
|
+
if (outChars.length === 0) continue;
|
|
4551
|
+
if (pendingWhitespaceStart == null) {
|
|
4552
|
+
pendingWhitespaceStart = charStarts[i];
|
|
4553
|
+
}
|
|
4554
|
+
pendingWhitespaceEnd = charEnds[i];
|
|
4555
|
+
continue;
|
|
4556
|
+
}
|
|
4557
|
+
|
|
4558
|
+
if (pendingWhitespaceStart != null && pendingWhitespaceEnd != null) {
|
|
4559
|
+
outChars.push(" ");
|
|
4560
|
+
outStarts.push(pendingWhitespaceStart);
|
|
4561
|
+
outEnds.push(pendingWhitespaceEnd);
|
|
4562
|
+
pendingWhitespaceStart = null;
|
|
4563
|
+
pendingWhitespaceEnd = null;
|
|
4564
|
+
}
|
|
4565
|
+
|
|
4566
|
+
outChars.push(character);
|
|
4567
|
+
outStarts.push(charStarts[i]);
|
|
4568
|
+
outEnds.push(charEnds[i]);
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
return {
|
|
4572
|
+
text: outChars.join(""),
|
|
4573
|
+
charStarts: outStarts,
|
|
4574
|
+
charEnds: outEnds,
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
function buildNormalizedDomTextMap(rootEl) {
|
|
4579
|
+
if (!rootEl || typeof document.createTreeWalker !== "function") {
|
|
4580
|
+
return { text: "", charStarts: [], charEnds: [] };
|
|
4581
|
+
}
|
|
4582
|
+
const walker = document.createTreeWalker(rootEl, NodeFilter.SHOW_TEXT);
|
|
4583
|
+
const chars = [];
|
|
4584
|
+
const starts = [];
|
|
4585
|
+
const ends = [];
|
|
4586
|
+
let node = walker.nextNode();
|
|
4587
|
+
while (node) {
|
|
4588
|
+
const textNode = node;
|
|
4589
|
+
const value = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
|
|
4590
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
4591
|
+
chars.push(value[i]);
|
|
4592
|
+
starts.push({ node: textNode, offset: i });
|
|
4593
|
+
ends.push({ node: textNode, offset: i + 1 });
|
|
4594
|
+
}
|
|
4595
|
+
node = walker.nextNode();
|
|
4596
|
+
}
|
|
4597
|
+
return buildNormalizedPreviewDisplayMap(chars.join(""), starts, ends);
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
function findPreferredNormalizedTextMatch(haystack, needle, preferredIndex) {
|
|
4601
|
+
const source = String(haystack || "");
|
|
4602
|
+
const query = String(needle || "");
|
|
4603
|
+
if (!source || !query) return -1;
|
|
4604
|
+
let bestIndex = -1;
|
|
4605
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
4606
|
+
const desiredIndex = Number.isFinite(preferredIndex) ? Math.max(0, preferredIndex) : 0;
|
|
4607
|
+
for (let matchIndex = source.indexOf(query); matchIndex >= 0; matchIndex = source.indexOf(query, matchIndex + 1)) {
|
|
4608
|
+
const score = Math.abs(matchIndex - desiredIndex);
|
|
4609
|
+
if (score < bestScore) {
|
|
4610
|
+
bestScore = score;
|
|
4611
|
+
bestIndex = matchIndex;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
return bestIndex;
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4617
|
+
function buildPreviewSelectionDisplayMap(blockText, kind) {
|
|
4618
|
+
const body = buildPreviewSelectionSourceBody(blockText, kind);
|
|
4619
|
+
const inlineMap = buildPreviewInlineDisplayMap(body.text, body.rawOffsets);
|
|
4620
|
+
return buildNormalizedPreviewDisplayMap(inlineMap.text, inlineMap.charStarts, inlineMap.charEnds);
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
function getPreviewCommentBlockKey(blockEl) {
|
|
4624
|
+
if (!blockEl || !blockEl.dataset) return "";
|
|
4625
|
+
return [
|
|
4626
|
+
String(blockEl.dataset.reviewNoteStart || ""),
|
|
4627
|
+
String(blockEl.dataset.reviewNoteEnd || ""),
|
|
4628
|
+
String(blockEl.dataset.previewCommentKind || ""),
|
|
4629
|
+
].join(":");
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
function getPreviewCommentSelectionKey(selection) {
|
|
4633
|
+
if (!selection) return "";
|
|
4634
|
+
return [
|
|
4635
|
+
String(selection.blockKey || ""),
|
|
4636
|
+
String(selection.selectionStart || 0),
|
|
4637
|
+
String(selection.selectionEnd || 0),
|
|
4638
|
+
String(selection.selectedDisplayText || ""),
|
|
4639
|
+
].join(":");
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
function setActivePreviewCommentSelection(nextSelection) {
|
|
4643
|
+
const currentKey = getPreviewCommentSelectionKey(activePreviewCommentSelection);
|
|
4644
|
+
const nextKey = getPreviewCommentSelectionKey(nextSelection);
|
|
4645
|
+
if (currentKey === nextKey) return;
|
|
4646
|
+
activePreviewCommentSelection = nextSelection || null;
|
|
4647
|
+
refreshRenderedEditorPreviewComments();
|
|
4648
|
+
}
|
|
4649
|
+
|
|
4650
|
+
function clearPreviewCommentSelection() {
|
|
4651
|
+
setActivePreviewCommentSelection(null);
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4654
|
+
function findPreviewCommentBlockFromNode(node) {
|
|
4655
|
+
if (!node) return null;
|
|
4656
|
+
const element = node instanceof Element ? node : node.parentElement;
|
|
4657
|
+
return element && typeof element.closest === "function"
|
|
4658
|
+
? element.closest(".preview-comment-block")
|
|
4659
|
+
: null;
|
|
4660
|
+
}
|
|
4661
|
+
|
|
4662
|
+
function unwrapPreviewJumpHighlightElement(element) {
|
|
4663
|
+
if (!element || !element.parentNode) return;
|
|
4664
|
+
const parent = element.parentNode;
|
|
4665
|
+
while (element.firstChild) {
|
|
4666
|
+
parent.insertBefore(element.firstChild, element);
|
|
4667
|
+
}
|
|
4668
|
+
parent.removeChild(element);
|
|
4669
|
+
if (typeof parent.normalize === "function") {
|
|
4670
|
+
parent.normalize();
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
function clearPreviewJumpHighlight(targetEl) {
|
|
4675
|
+
if (!targetEl) return;
|
|
4676
|
+
const state = previewJumpHighlightState.get(targetEl);
|
|
4677
|
+
if (!state) return;
|
|
4678
|
+
if (state.timer != null) {
|
|
4679
|
+
window.clearTimeout(state.timer);
|
|
4680
|
+
}
|
|
4681
|
+
if (state.inlineHighlightEl) {
|
|
4682
|
+
unwrapPreviewJumpHighlightElement(state.inlineHighlightEl);
|
|
4683
|
+
}
|
|
4684
|
+
if (state.contentEl && state.contentEl.classList) {
|
|
4685
|
+
state.contentEl.classList.remove("preview-jump-highlight");
|
|
4686
|
+
}
|
|
4687
|
+
previewJumpHighlightState.delete(targetEl);
|
|
4688
|
+
}
|
|
4689
|
+
|
|
4690
|
+
function setPreviewJumpHighlight(targetEl, contentEl, inlineHighlightEl) {
|
|
4691
|
+
if (!targetEl || !contentEl) return;
|
|
4692
|
+
clearPreviewJumpHighlight(targetEl);
|
|
4693
|
+
if (contentEl.classList) {
|
|
4694
|
+
contentEl.classList.add("preview-jump-highlight");
|
|
4695
|
+
}
|
|
4696
|
+
const timer = window.setTimeout(() => {
|
|
4697
|
+
clearPreviewJumpHighlight(targetEl);
|
|
4698
|
+
}, 1800);
|
|
4699
|
+
previewJumpHighlightState.set(targetEl, {
|
|
4700
|
+
contentEl,
|
|
4701
|
+
inlineHighlightEl: inlineHighlightEl || null,
|
|
4702
|
+
timer,
|
|
4703
|
+
});
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4706
|
+
function rangesOverlap(startA, endA, startB, endB) {
|
|
4707
|
+
const safeStartA = Math.max(0, Number(startA) || 0);
|
|
4708
|
+
const safeStartB = Math.max(0, Number(startB) || 0);
|
|
4709
|
+
const safeEndA = Math.max(safeStartA + 1, Number(endA) || safeStartA);
|
|
4710
|
+
const safeEndB = Math.max(safeStartB + 1, Number(endB) || safeStartB);
|
|
4711
|
+
return safeStartA < safeEndB && safeStartB < safeEndA;
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4714
|
+
function scanMarkdownPreviewCommentBlocks(markdown) {
|
|
4715
|
+
const source = String(markdown || "").replace(/\r\n/g, "\n");
|
|
4716
|
+
const lines = source.split("\n");
|
|
4717
|
+
const lineOffsets = [];
|
|
4718
|
+
let runningOffset = 0;
|
|
4719
|
+
for (const line of lines) {
|
|
4720
|
+
lineOffsets.push(runningOffset);
|
|
4721
|
+
runningOffset += line.length + 1;
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4724
|
+
function getLine(index) {
|
|
4725
|
+
return index >= 0 && index < lines.length ? String(lines[index] || "") : "";
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4728
|
+
function isBlankLine(index) {
|
|
4729
|
+
return /^\s*$/.test(getLine(index));
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
function lineStartsFence(index) {
|
|
4733
|
+
return getLine(index).match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
function isAtxHeadingLine(index) {
|
|
4737
|
+
return /^ {0,3}#{1,6}(?:[ \t]+|$)/.test(getLine(index));
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
function isSetextUnderlineLine(index) {
|
|
4741
|
+
return /^ {0,3}(?:={3,}|-{3,})\s*$/.test(getLine(index));
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4744
|
+
function isThematicBreakLine(index) {
|
|
4745
|
+
return /^ {0,3}(?:(?:-\s*){3,}|(?:_\s*){3,}|(?:\*\s*){3,})$/.test(getLine(index));
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
function isBlockquoteLine(index) {
|
|
4749
|
+
return /^ {0,3}> ?/.test(getLine(index));
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
function isListLine(index) {
|
|
4753
|
+
return /^ {0,3}(?:[*+-]|\d+[.)])(?:[ \t]+|$)/.test(getLine(index));
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4756
|
+
function isContinuationIndentedLine(index) {
|
|
4757
|
+
return /^(?: {2,}|\t+)/.test(getLine(index));
|
|
4758
|
+
}
|
|
4759
|
+
|
|
4760
|
+
function isPotentialTableRow(index) {
|
|
4761
|
+
const line = getLine(index);
|
|
4762
|
+
return /\|/.test(line) && !/^\s*</.test(line);
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4765
|
+
function isTableDividerLine(index) {
|
|
4766
|
+
return /^\s*\|?(?:\s*:?-{3,}:?\s*\|)+(?:\s*:?-{3,}:?\s*)?\|?\s*$/.test(getLine(index));
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
function isHtmlCommentStart(index) {
|
|
4770
|
+
return /^\s*<!--/.test(getLine(index));
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
function makeBlock(kind, startLineIndex, endLineIndex) {
|
|
4774
|
+
const safeStartLine = Math.max(0, Math.min(startLineIndex, Math.max(0, lines.length - 1)));
|
|
4775
|
+
const safeEndLine = Math.max(safeStartLine, Math.min(endLineIndex, Math.max(0, lines.length - 1)));
|
|
4776
|
+
const start = lineOffsets[safeStartLine] || 0;
|
|
4777
|
+
const end = (lineOffsets[safeEndLine] || 0) + getLine(safeEndLine).length;
|
|
4778
|
+
return {
|
|
4779
|
+
kind,
|
|
4780
|
+
start,
|
|
4781
|
+
end,
|
|
4782
|
+
lineStart: safeStartLine + 1,
|
|
4783
|
+
lineEnd: safeEndLine + 1,
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
const blocks = [];
|
|
4788
|
+
let index = 0;
|
|
4789
|
+
|
|
4790
|
+
if (/^\s*---\s*$/.test(getLine(0))) {
|
|
4791
|
+
for (let i = 1; i < Math.min(lines.length, 80); i += 1) {
|
|
4792
|
+
if (/^\s*(?:---|\.\.\.)\s*$/.test(getLine(i))) {
|
|
4793
|
+
index = i + 1;
|
|
4794
|
+
break;
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4799
|
+
while (index < lines.length) {
|
|
4800
|
+
if (isBlankLine(index)) {
|
|
4801
|
+
index += 1;
|
|
4802
|
+
continue;
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4805
|
+
if (isHtmlCommentStart(index)) {
|
|
4806
|
+
let endComment = index;
|
|
4807
|
+
while (endComment < lines.length && getLine(endComment).indexOf("-->") === -1) {
|
|
4808
|
+
endComment += 1;
|
|
4809
|
+
}
|
|
4810
|
+
index = Math.min(lines.length, endComment + 1);
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
if (isThematicBreakLine(index)) {
|
|
4815
|
+
index += 1;
|
|
4816
|
+
continue;
|
|
4817
|
+
}
|
|
4818
|
+
|
|
4819
|
+
const fenceMatch = lineStartsFence(index);
|
|
4820
|
+
if (fenceMatch) {
|
|
4821
|
+
const marker = fenceMatch[1] || "";
|
|
4822
|
+
const markerChar = marker[0] || "`";
|
|
4823
|
+
const markerLength = marker.length;
|
|
4824
|
+
let endFence = index;
|
|
4825
|
+
for (let i = index + 1; i < lines.length; i += 1) {
|
|
4826
|
+
const closingMatch = getLine(i).match(/^ {0,3}(`{3,}|~{3,})\s*$/);
|
|
4827
|
+
if (closingMatch && closingMatch[1] && closingMatch[1][0] === markerChar && closingMatch[1].length >= markerLength) {
|
|
4828
|
+
endFence = i;
|
|
4829
|
+
break;
|
|
4830
|
+
}
|
|
4831
|
+
endFence = i;
|
|
4832
|
+
}
|
|
4833
|
+
blocks.push(makeBlock("code", index, endFence));
|
|
4834
|
+
index = endFence + 1;
|
|
4835
|
+
continue;
|
|
4836
|
+
}
|
|
4837
|
+
|
|
4838
|
+
if (isAtxHeadingLine(index)) {
|
|
4839
|
+
blocks.push(makeBlock("heading", index, index));
|
|
4840
|
+
index += 1;
|
|
4841
|
+
continue;
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4844
|
+
if (!isBlankLine(index) && index + 1 < lines.length && isSetextUnderlineLine(index + 1)) {
|
|
4845
|
+
blocks.push(makeBlock("heading", index, index + 1));
|
|
4846
|
+
index += 2;
|
|
4847
|
+
continue;
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
if (isPotentialTableRow(index) && index + 1 < lines.length && isTableDividerLine(index + 1)) {
|
|
4851
|
+
let endTable = index + 1;
|
|
4852
|
+
for (let i = index + 2; i < lines.length; i += 1) {
|
|
4853
|
+
if (isBlankLine(i) || !isPotentialTableRow(i)) break;
|
|
4854
|
+
endTable = i;
|
|
4855
|
+
}
|
|
4856
|
+
blocks.push(makeBlock("table", index, endTable));
|
|
4857
|
+
index = endTable + 1;
|
|
4858
|
+
continue;
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
if (isBlockquoteLine(index)) {
|
|
4862
|
+
let endQuote = index;
|
|
4863
|
+
for (let i = index + 1; i < lines.length; i += 1) {
|
|
4864
|
+
if (isBlockquoteLine(i)) {
|
|
4865
|
+
endQuote = i;
|
|
4866
|
+
continue;
|
|
4867
|
+
}
|
|
4868
|
+
if (isBlankLine(i) && i + 1 < lines.length && isBlockquoteLine(i + 1)) {
|
|
4869
|
+
endQuote = i;
|
|
4870
|
+
continue;
|
|
4871
|
+
}
|
|
4872
|
+
break;
|
|
4873
|
+
}
|
|
4874
|
+
blocks.push(makeBlock("blockquote", index, endQuote));
|
|
4875
|
+
index = endQuote + 1;
|
|
4876
|
+
continue;
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
if (isListLine(index)) {
|
|
4880
|
+
let endList = index;
|
|
4881
|
+
for (let i = index + 1; i < lines.length; i += 1) {
|
|
4882
|
+
if (isBlankLine(i)) {
|
|
4883
|
+
if (i + 1 < lines.length && (isListLine(i + 1) || isContinuationIndentedLine(i + 1))) {
|
|
4884
|
+
endList = i;
|
|
4885
|
+
continue;
|
|
4886
|
+
}
|
|
4887
|
+
break;
|
|
4888
|
+
}
|
|
4889
|
+
if (isListLine(i) || isContinuationIndentedLine(i)) {
|
|
4890
|
+
endList = i;
|
|
4891
|
+
continue;
|
|
4892
|
+
}
|
|
4893
|
+
if (isAtxHeadingLine(i) || isBlockquoteLine(i) || lineStartsFence(i) || (isPotentialTableRow(i) && i + 1 < lines.length && isTableDividerLine(i + 1))) {
|
|
4894
|
+
break;
|
|
4895
|
+
}
|
|
4896
|
+
endList = i;
|
|
4897
|
+
}
|
|
4898
|
+
blocks.push(makeBlock("list", index, endList));
|
|
4899
|
+
index = endList + 1;
|
|
4900
|
+
continue;
|
|
4901
|
+
}
|
|
4902
|
+
|
|
4903
|
+
let endParagraph = index;
|
|
4904
|
+
for (let i = index + 1; i < lines.length; i += 1) {
|
|
4905
|
+
if (isBlankLine(i) || isHtmlCommentStart(i) || lineStartsFence(i) || isAtxHeadingLine(i) || isBlockquoteLine(i) || isListLine(i)) {
|
|
4906
|
+
break;
|
|
4907
|
+
}
|
|
4908
|
+
if (i + 1 < lines.length && (isSetextUnderlineLine(i + 1) || (isPotentialTableRow(i) && isTableDividerLine(i + 1)))) {
|
|
4909
|
+
break;
|
|
4910
|
+
}
|
|
4911
|
+
endParagraph = i;
|
|
4912
|
+
}
|
|
4913
|
+
blocks.push(makeBlock("paragraph", index, endParagraph));
|
|
4914
|
+
index = endParagraph + 1;
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
return blocks;
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
function getPreviewCommentTargetKind(element) {
|
|
4921
|
+
if (!element || !(element instanceof Element)) return "";
|
|
4922
|
+
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
4923
|
+
if (/^H[1-6]$/.test(tag)) return "heading";
|
|
4924
|
+
if (tag === "P") return "paragraph";
|
|
4925
|
+
if (tag === "BLOCKQUOTE") return "blockquote";
|
|
4926
|
+
if (tag === "UL" || tag === "OL") return "list";
|
|
4927
|
+
if (tag === "TABLE") return "table";
|
|
4928
|
+
if (tag === "PRE") return "code";
|
|
4929
|
+
if (element.classList) {
|
|
4930
|
+
if (
|
|
4931
|
+
element.classList.contains("sourceCode")
|
|
4932
|
+
|| element.classList.contains("mermaid-container")
|
|
4933
|
+
) {
|
|
4934
|
+
return "code";
|
|
4935
|
+
}
|
|
4936
|
+
if (
|
|
4937
|
+
element.classList.contains("callout-note")
|
|
4938
|
+
|| element.classList.contains("callout-tip")
|
|
4939
|
+
|| element.classList.contains("callout-warning")
|
|
4940
|
+
|| element.classList.contains("callout-important")
|
|
4941
|
+
|| element.classList.contains("callout-caution")
|
|
4942
|
+
) {
|
|
4943
|
+
return "blockquote";
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
return "";
|
|
4947
|
+
}
|
|
4948
|
+
|
|
4949
|
+
function isPreviewCommentTargetElement(element) {
|
|
4950
|
+
return Boolean(getPreviewCommentTargetKind(element));
|
|
4951
|
+
}
|
|
4952
|
+
|
|
4953
|
+
function collectPreviewCommentTargetElements(targetEl) {
|
|
4954
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return [];
|
|
4955
|
+
const selector = "h1, h2, h3, h4, h5, h6, p, blockquote, ul, ol, table, div.sourceCode, pre, .callout-note, .callout-tip, .callout-warning, .callout-important, .callout-caution, .mermaid-container";
|
|
4956
|
+
return Array.from(targetEl.querySelectorAll(selector)).filter((element) => {
|
|
4957
|
+
if (!isPreviewCommentTargetElement(element)) return false;
|
|
4958
|
+
let ancestor = element.parentElement;
|
|
4959
|
+
while (ancestor && ancestor !== targetEl) {
|
|
4960
|
+
if (ancestor.classList && ancestor.classList.contains("preview-comment-block")) return false;
|
|
4961
|
+
if (isPreviewCommentTargetElement(ancestor)) return false;
|
|
4962
|
+
ancestor = ancestor.parentElement;
|
|
4963
|
+
}
|
|
4964
|
+
return true;
|
|
4965
|
+
}).map((element) => ({
|
|
4966
|
+
element,
|
|
4967
|
+
kind: getPreviewCommentTargetKind(element),
|
|
4968
|
+
}));
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4971
|
+
function getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock) {
|
|
4972
|
+
if (!sourceBlock) return "";
|
|
4973
|
+
const blockText = String(sourceText || "").slice(sourceBlock.start, sourceBlock.end);
|
|
4974
|
+
if (supportsPreviewSelectionCommentsForBlockKind(sourceBlock.kind)) {
|
|
4975
|
+
return normalizeVisiblePreviewText(buildPreviewSelectionDisplayMap(blockText, sourceBlock.kind).text);
|
|
4976
|
+
}
|
|
4977
|
+
if (sourceBlock.kind === "code") {
|
|
4978
|
+
return normalizeVisiblePreviewText(
|
|
4979
|
+
blockText
|
|
4980
|
+
.replace(/^ {0,3}(`{3,}|~{3,}).*$/gm, "")
|
|
4981
|
+
.replace(/^ {0,3}$/gm, ""),
|
|
4982
|
+
);
|
|
4983
|
+
}
|
|
4984
|
+
if (sourceBlock.kind === "table") {
|
|
4985
|
+
return normalizeVisiblePreviewText(
|
|
4986
|
+
blockText
|
|
4987
|
+
.replace(/^\s*\|?(?:\s*:?-{3,}:?\s*\|)+(?:\s*:?-{3,}:?\s*)?\|?\s*$/gm, "")
|
|
4988
|
+
.replace(/\|/g, " "),
|
|
4989
|
+
);
|
|
4990
|
+
}
|
|
4991
|
+
return normalizeVisiblePreviewText(blockText);
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
function getNormalizedPreviewCommentTargetText(targetEntry) {
|
|
4995
|
+
if (!targetEntry) return "";
|
|
4996
|
+
if (typeof targetEntry.normalizedText === "string") return targetEntry.normalizedText;
|
|
4997
|
+
targetEntry.normalizedText = normalizeVisiblePreviewText(
|
|
4998
|
+
targetEntry.element && typeof targetEntry.element.textContent === "string"
|
|
4999
|
+
? targetEntry.element.textContent
|
|
5000
|
+
: "",
|
|
5001
|
+
);
|
|
5002
|
+
return targetEntry.normalizedText;
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
function findMatchingPreviewCommentTargetIndex(sourceText, sourceBlock, targetBlocks, startIndex) {
|
|
5006
|
+
const desiredKind = sourceBlock ? sourceBlock.kind : "";
|
|
5007
|
+
const desiredText = getNormalizedPreviewCommentSourceBlockText(sourceText, sourceBlock);
|
|
5008
|
+
let fallbackIndex = -1;
|
|
5009
|
+
let containsIndex = -1;
|
|
5010
|
+
|
|
5011
|
+
for (let i = Math.max(0, startIndex || 0); i < targetBlocks.length; i += 1) {
|
|
5012
|
+
const targetEntry = targetBlocks[i];
|
|
5013
|
+
if (!targetEntry || targetEntry.kind !== desiredKind) continue;
|
|
5014
|
+
if (fallbackIndex < 0) fallbackIndex = i;
|
|
5015
|
+
const targetText = getNormalizedPreviewCommentTargetText(targetEntry);
|
|
5016
|
+
if (desiredText && targetText) {
|
|
5017
|
+
if (targetText === desiredText) {
|
|
5018
|
+
return i;
|
|
5019
|
+
}
|
|
5020
|
+
if (containsIndex < 0 && (targetText.includes(desiredText) || desiredText.includes(targetText))) {
|
|
5021
|
+
containsIndex = i;
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
|
|
5026
|
+
if (containsIndex >= 0) return containsIndex;
|
|
5027
|
+
return fallbackIndex;
|
|
5028
|
+
}
|
|
5029
|
+
|
|
5030
|
+
function getPreviewCommentNotesForRange(start, end, sourceText, displayNotes) {
|
|
5031
|
+
const source = String(sourceText || "");
|
|
5032
|
+
const notes = Array.isArray(displayNotes) ? displayNotes : getDisplayReviewNotes();
|
|
5033
|
+
return notes.filter((note) => {
|
|
5034
|
+
const range = resolveReviewNoteRange(note, source);
|
|
5035
|
+
return range && rangesOverlap(range.start, range.end, start, end);
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
5038
|
+
|
|
5039
|
+
function updatePreviewCommentBlockState(blockEl, sourceText, displayNotes) {
|
|
5040
|
+
if (!blockEl || !blockEl.dataset) return;
|
|
5041
|
+
const lineStart = Math.max(1, Number(blockEl.dataset.reviewNoteLineStart) || 1);
|
|
5042
|
+
const lineEnd = Math.max(lineStart, Number(blockEl.dataset.reviewNoteLineEnd) || lineStart);
|
|
5043
|
+
const summaryBtn = blockEl.querySelector(".preview-comment-summary");
|
|
5044
|
+
const addBtn = blockEl.querySelector(".preview-comment-add");
|
|
5045
|
+
const lineLabel = summarizeReviewNoteAnchor({ lineStart: lineStart, lineEnd: lineEnd }).toLowerCase();
|
|
5046
|
+
const blockKindLabel = getPreviewCommentBlockKindLabel(blockEl.dataset.previewCommentKind || "paragraph");
|
|
5047
|
+
const blockKey = getPreviewCommentBlockKey(blockEl);
|
|
5048
|
+
const hasSelection = Boolean(activePreviewCommentSelection && activePreviewCommentSelection.blockKey === blockKey);
|
|
5049
|
+
|
|
5050
|
+
blockEl.classList.remove("has-comments");
|
|
5051
|
+
blockEl.classList.toggle("has-selection", hasSelection);
|
|
5052
|
+
|
|
5053
|
+
if (summaryBtn) {
|
|
5054
|
+
summaryBtn.hidden = true;
|
|
5055
|
+
summaryBtn.textContent = "";
|
|
5056
|
+
summaryBtn.dataset.reviewNoteId = "";
|
|
5057
|
+
}
|
|
5058
|
+
|
|
5059
|
+
if (addBtn) {
|
|
5060
|
+
addBtn.hidden = !hasSelection;
|
|
5061
|
+
addBtn.textContent = "Comment";
|
|
5062
|
+
addBtn.dataset.previewCommentMode = hasSelection ? "selection" : "";
|
|
5063
|
+
addBtn.title = hasSelection
|
|
5064
|
+
? ("Add a local comment from the current preview selection on this " + blockKindLabel + " (" + lineLabel + ").")
|
|
5065
|
+
: "";
|
|
5066
|
+
addBtn.setAttribute("aria-label", addBtn.title || "Comment");
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
|
|
5070
|
+
function updatePreviewCommentBlocksForElement(targetEl) {
|
|
5071
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
5072
|
+
const sourceText = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5073
|
+
Array.from(targetEl.querySelectorAll(".preview-comment-block")).forEach((blockEl) => {
|
|
5074
|
+
updatePreviewCommentBlockState(blockEl, sourceText);
|
|
5075
|
+
});
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
function decorateRenderedEditorPreviewComments(targetEl, sourceText) {
|
|
5079
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
5080
|
+
const sourceBlocks = scanMarkdownPreviewCommentBlocks(sourceText);
|
|
5081
|
+
const targetBlocks = collectPreviewCommentTargetElements(targetEl);
|
|
5082
|
+
if (sourceBlocks.length === 0 || targetBlocks.length === 0) return;
|
|
5083
|
+
|
|
5084
|
+
let targetIndex = 0;
|
|
5085
|
+
for (const sourceBlock of sourceBlocks) {
|
|
5086
|
+
const matchedTargetIndex = findMatchingPreviewCommentTargetIndex(sourceText, sourceBlock, targetBlocks, targetIndex);
|
|
5087
|
+
if (matchedTargetIndex < 0) continue;
|
|
5088
|
+
|
|
5089
|
+
const targetEntry = targetBlocks[matchedTargetIndex];
|
|
5090
|
+
targetIndex = matchedTargetIndex + 1;
|
|
5091
|
+
const originalElement = targetEntry && targetEntry.element ? targetEntry.element : null;
|
|
5092
|
+
if (!originalElement || !originalElement.parentNode) continue;
|
|
5093
|
+
|
|
5094
|
+
const wrapper = document.createElement("div");
|
|
5095
|
+
wrapper.className = "preview-comment-block";
|
|
5096
|
+
wrapper.dataset.reviewNoteStart = String(sourceBlock.start);
|
|
5097
|
+
wrapper.dataset.reviewNoteEnd = String(sourceBlock.end);
|
|
5098
|
+
wrapper.dataset.reviewNoteLineStart = String(sourceBlock.lineStart);
|
|
5099
|
+
wrapper.dataset.reviewNoteLineEnd = String(sourceBlock.lineEnd);
|
|
5100
|
+
wrapper.dataset.previewCommentKind = sourceBlock.kind;
|
|
5101
|
+
|
|
5102
|
+
const controls = document.createElement("div");
|
|
5103
|
+
controls.className = "preview-comment-controls";
|
|
5104
|
+
|
|
5105
|
+
const summaryBtn = document.createElement("button");
|
|
5106
|
+
summaryBtn.type = "button";
|
|
5107
|
+
summaryBtn.className = "preview-comment-summary";
|
|
5108
|
+
summaryBtn.hidden = true;
|
|
5109
|
+
controls.appendChild(summaryBtn);
|
|
5110
|
+
|
|
5111
|
+
const addBtn = document.createElement("button");
|
|
5112
|
+
addBtn.type = "button";
|
|
5113
|
+
addBtn.className = "preview-comment-add";
|
|
5114
|
+
addBtn.textContent = "Comment";
|
|
5115
|
+
controls.appendChild(addBtn);
|
|
5116
|
+
|
|
5117
|
+
originalElement.replaceWith(wrapper);
|
|
5118
|
+
wrapper.appendChild(controls);
|
|
5119
|
+
originalElement.classList.add("preview-comment-block-content");
|
|
5120
|
+
wrapper.appendChild(originalElement);
|
|
5121
|
+
}
|
|
5122
|
+
|
|
5123
|
+
updatePreviewCommentBlocksForElement(targetEl);
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
function refreshRenderedEditorPreviewComments() {
|
|
5127
|
+
if (sourcePreviewEl && !sourcePreviewEl.hidden) {
|
|
5128
|
+
updatePreviewCommentBlocksForElement(sourcePreviewEl);
|
|
5129
|
+
}
|
|
5130
|
+
if (critiqueViewEl && rightView === "editor-preview") {
|
|
5131
|
+
updatePreviewCommentBlocksForElement(critiqueViewEl);
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
|
|
5135
|
+
function buildReviewNoteAnchorFromPreviewBlock(blockEl) {
|
|
5136
|
+
if (!blockEl || !blockEl.dataset) return null;
|
|
5137
|
+
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5138
|
+
const selectionStart = Math.max(0, Math.min(Number(blockEl.dataset.reviewNoteStart) || 0, source.length));
|
|
5139
|
+
const selectionEnd = Math.max(selectionStart, Math.min(Number(blockEl.dataset.reviewNoteEnd) || selectionStart, source.length));
|
|
5140
|
+
const lineStart = Math.max(1, Number(blockEl.dataset.reviewNoteLineStart) || 1);
|
|
5141
|
+
const lineEnd = Math.max(lineStart, Number(blockEl.dataset.reviewNoteLineEnd) || lineStart);
|
|
5142
|
+
return {
|
|
5143
|
+
selectionStart,
|
|
5144
|
+
selectionEnd,
|
|
5145
|
+
lineStart,
|
|
5146
|
+
lineEnd,
|
|
5147
|
+
selectedText: source.slice(selectionStart, selectionEnd),
|
|
5148
|
+
selectedDisplayText: source.slice(selectionStart, selectionEnd),
|
|
5149
|
+
};
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
function buildReviewNoteAnchorFromPreviewSelection(blockEl, contentEl, range) {
|
|
5153
|
+
if (!blockEl || !blockEl.dataset || !contentEl || !range) return null;
|
|
5154
|
+
const kind = String(blockEl.dataset.previewCommentKind || "");
|
|
5155
|
+
if (!supportsPreviewSelectionCommentsForBlockKind(kind)) return null;
|
|
5156
|
+
if (!contentEl.contains(range.startContainer) || !contentEl.contains(range.endContainer)) return null;
|
|
5157
|
+
|
|
5158
|
+
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5159
|
+
const blockStart = Math.max(0, Math.min(Number(blockEl.dataset.reviewNoteStart) || 0, source.length));
|
|
5160
|
+
const blockEnd = Math.max(blockStart, Math.min(Number(blockEl.dataset.reviewNoteEnd) || blockStart, source.length));
|
|
5161
|
+
if (blockEnd <= blockStart) return null;
|
|
5162
|
+
|
|
5163
|
+
const sourceBlockText = source.slice(blockStart, blockEnd);
|
|
5164
|
+
const displayMap = buildPreviewSelectionDisplayMap(sourceBlockText, kind);
|
|
5165
|
+
if (!displayMap.text || !displayMap.charStarts.length || !displayMap.charEnds.length) return null;
|
|
5166
|
+
|
|
5167
|
+
const prefixRange = document.createRange();
|
|
5168
|
+
prefixRange.selectNodeContents(contentEl);
|
|
5169
|
+
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
5170
|
+
const prefixText = normalizeVisiblePreviewText(prefixRange.toString());
|
|
5171
|
+
const selectedDisplayText = normalizeVisiblePreviewText(range.toString());
|
|
5172
|
+
if (!selectedDisplayText) return null;
|
|
5173
|
+
|
|
5174
|
+
const desiredStart = Math.max(0, Math.min(prefixText.length, displayMap.text.length));
|
|
5175
|
+
const bestIndex = findPreferredNormalizedTextMatch(displayMap.text, selectedDisplayText, desiredStart);
|
|
5176
|
+
if (bestIndex < 0) return null;
|
|
5177
|
+
|
|
5178
|
+
const endIndex = bestIndex + selectedDisplayText.length - 1;
|
|
5179
|
+
const rawStartRel = displayMap.charStarts[bestIndex];
|
|
5180
|
+
const rawEndRel = displayMap.charEnds[endIndex];
|
|
5181
|
+
if (!Number.isFinite(rawStartRel) || !Number.isFinite(rawEndRel) || rawEndRel <= rawStartRel) {
|
|
5182
|
+
return null;
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
const selectionStart = blockStart + rawStartRel;
|
|
5186
|
+
const selectionEnd = blockStart + rawEndRel;
|
|
5187
|
+
return {
|
|
5188
|
+
selectionStart,
|
|
5189
|
+
selectionEnd,
|
|
5190
|
+
lineStart: getLineNumberAtOffset(source, selectionStart),
|
|
5191
|
+
lineEnd: getLineNumberAtOffset(source, Math.max(selectionStart, selectionEnd - 1)),
|
|
5192
|
+
selectedText: source.slice(selectionStart, selectionEnd),
|
|
5193
|
+
selectedDisplayText,
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
|
|
5197
|
+
function getPreviewJumpNormalizedSelectionStart(note, blockEl, range) {
|
|
5198
|
+
if (!note || !blockEl || !blockEl.dataset || !range) return 0;
|
|
5199
|
+
const kind = String(blockEl.dataset.previewCommentKind || "");
|
|
5200
|
+
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5201
|
+
const blockStart = Math.max(0, Math.min(Number(blockEl.dataset.reviewNoteStart) || 0, source.length));
|
|
5202
|
+
const blockEnd = Math.max(blockStart, Math.min(Number(blockEl.dataset.reviewNoteEnd) || blockStart, source.length));
|
|
5203
|
+
const displayMap = buildPreviewSelectionDisplayMap(source.slice(blockStart, blockEnd), kind);
|
|
5204
|
+
if (!displayMap || !displayMap.charStarts || displayMap.charStarts.length === 0) return 0;
|
|
5205
|
+
const relativeStart = Math.max(0, range.start - blockStart);
|
|
5206
|
+
for (let i = 0; i < displayMap.charStarts.length; i += 1) {
|
|
5207
|
+
const charStart = Number(displayMap.charStarts[i]);
|
|
5208
|
+
const charEnd = Number(displayMap.charEnds[i]);
|
|
5209
|
+
if (charEnd > relativeStart && charStart <= relativeStart) {
|
|
5210
|
+
return i;
|
|
5211
|
+
}
|
|
5212
|
+
if (charStart >= relativeStart) {
|
|
5213
|
+
return i;
|
|
5214
|
+
}
|
|
5215
|
+
}
|
|
5216
|
+
return Math.max(0, displayMap.text.length - 1);
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5219
|
+
function createPreviewJumpInlineHighlight(contentEl, blockEl, note, range) {
|
|
5220
|
+
if (!contentEl || !note || !range) return null;
|
|
5221
|
+
const selectedDisplayText = normalizeVisiblePreviewText(note.selectedDisplayText || note.selectedText || "");
|
|
5222
|
+
if (!selectedDisplayText) return null;
|
|
5223
|
+
const domMap = buildNormalizedDomTextMap(contentEl);
|
|
5224
|
+
if (!domMap.text || !domMap.charStarts.length || !domMap.charEnds.length) return null;
|
|
5225
|
+
const preferredStart = getPreviewJumpNormalizedSelectionStart(note, blockEl, range);
|
|
5226
|
+
const matchIndex = findPreferredNormalizedTextMatch(domMap.text, selectedDisplayText, preferredStart);
|
|
5227
|
+
if (matchIndex < 0) return null;
|
|
5228
|
+
const endIndex = matchIndex + selectedDisplayText.length - 1;
|
|
5229
|
+
const startRef = domMap.charStarts[matchIndex];
|
|
5230
|
+
const endRef = domMap.charEnds[endIndex];
|
|
5231
|
+
if (!startRef || !endRef || !startRef.node || !endRef.node) return null;
|
|
5232
|
+
|
|
5233
|
+
const domRange = document.createRange();
|
|
5234
|
+
domRange.setStart(startRef.node, startRef.offset);
|
|
5235
|
+
domRange.setEnd(endRef.node, endRef.offset);
|
|
5236
|
+
|
|
5237
|
+
const highlightEl = document.createElement("span");
|
|
5238
|
+
highlightEl.className = "preview-comment-inline-highlight";
|
|
5239
|
+
try {
|
|
5240
|
+
domRange.surroundContents(highlightEl);
|
|
5241
|
+
} catch {
|
|
5242
|
+
const fragment = domRange.extractContents();
|
|
5243
|
+
highlightEl.appendChild(fragment);
|
|
5244
|
+
domRange.insertNode(highlightEl);
|
|
5245
|
+
}
|
|
5246
|
+
return highlightEl;
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
function findPreviewCommentBlockForRange(targetEl, range) {
|
|
5250
|
+
if (!targetEl || !range || typeof targetEl.querySelectorAll !== "function") return null;
|
|
5251
|
+
let bestBlock = null;
|
|
5252
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
5253
|
+
Array.from(targetEl.querySelectorAll(".preview-comment-block")).forEach((blockEl) => {
|
|
5254
|
+
const blockStart = Math.max(0, Number(blockEl.dataset && blockEl.dataset.reviewNoteStart) || 0);
|
|
5255
|
+
const blockEnd = Math.max(blockStart, Number(blockEl.dataset && blockEl.dataset.reviewNoteEnd) || blockStart);
|
|
5256
|
+
const overlapStart = Math.max(blockStart, range.start);
|
|
5257
|
+
const overlapEnd = Math.min(blockEnd, range.end);
|
|
5258
|
+
const overlap = Math.max(0, overlapEnd - overlapStart);
|
|
5259
|
+
const contains = range.start >= blockStart && range.end <= blockEnd;
|
|
5260
|
+
const distance = contains
|
|
5261
|
+
? 0
|
|
5262
|
+
: Math.min(Math.abs(range.start - blockEnd), Math.abs(range.end - blockStart));
|
|
5263
|
+
const score = contains
|
|
5264
|
+
? (1000000 - (blockEnd - blockStart))
|
|
5265
|
+
: (overlap > 0 ? overlap : -distance);
|
|
5266
|
+
if (score > bestScore) {
|
|
5267
|
+
bestScore = score;
|
|
5268
|
+
bestBlock = blockEl;
|
|
5269
|
+
}
|
|
5270
|
+
});
|
|
5271
|
+
return bestBlock;
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
function revealReviewNoteInPreviewElement(targetEl, note) {
|
|
5275
|
+
if (!targetEl || !note) return false;
|
|
5276
|
+
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5277
|
+
const range = resolveReviewNoteRange(note, source);
|
|
5278
|
+
if (!range) return false;
|
|
5279
|
+
const blockEl = findPreviewCommentBlockForRange(targetEl, range);
|
|
5280
|
+
if (!blockEl) return false;
|
|
5281
|
+
const contentEl = blockEl.querySelector(".preview-comment-block-content") || blockEl;
|
|
5282
|
+
const inlineHighlightEl = createPreviewJumpInlineHighlight(contentEl, blockEl, note, range);
|
|
5283
|
+
if (typeof blockEl.scrollIntoView === "function") {
|
|
5284
|
+
blockEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
5285
|
+
}
|
|
5286
|
+
setPreviewJumpHighlight(targetEl, contentEl, inlineHighlightEl);
|
|
5287
|
+
return true;
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5290
|
+
function revealReviewNoteInPreview(note) {
|
|
5291
|
+
if (rightView === "editor-preview" && critiqueViewEl && critiqueViewEl.isConnected) {
|
|
5292
|
+
revealReviewNoteInPreviewElement(critiqueViewEl, note);
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5296
|
+
function updateActivePreviewCommentSelectionFromDom() {
|
|
5297
|
+
const selection = typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
5298
|
+
if (!selection || selection.rangeCount <= 0 || selection.isCollapsed) {
|
|
5299
|
+
clearPreviewCommentSelection();
|
|
5300
|
+
return;
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5303
|
+
const range = selection.getRangeAt(0);
|
|
5304
|
+
const startBlock = findPreviewCommentBlockFromNode(range.startContainer);
|
|
5305
|
+
const endBlock = findPreviewCommentBlockFromNode(range.endContainer);
|
|
5306
|
+
if (!startBlock || !endBlock || startBlock !== endBlock) {
|
|
5307
|
+
clearPreviewCommentSelection();
|
|
5308
|
+
return;
|
|
5309
|
+
}
|
|
5310
|
+
|
|
5311
|
+
const contentEl = startBlock.querySelector(".preview-comment-block-content");
|
|
5312
|
+
if (!contentEl || !contentEl.contains(range.startContainer) || !contentEl.contains(range.endContainer)) {
|
|
5313
|
+
clearPreviewCommentSelection();
|
|
5314
|
+
return;
|
|
5315
|
+
}
|
|
5316
|
+
|
|
5317
|
+
const anchor = buildReviewNoteAnchorFromPreviewSelection(startBlock, contentEl, range);
|
|
5318
|
+
if (!anchor) {
|
|
5319
|
+
clearPreviewCommentSelection();
|
|
5320
|
+
return;
|
|
5321
|
+
}
|
|
5322
|
+
|
|
5323
|
+
setActivePreviewCommentSelection({
|
|
5324
|
+
...anchor,
|
|
5325
|
+
blockKey: getPreviewCommentBlockKey(startBlock),
|
|
5326
|
+
});
|
|
5327
|
+
}
|
|
5328
|
+
|
|
4317
5329
|
function getDisplayReviewNotes() {
|
|
4318
5330
|
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
4319
5331
|
return reviewNotes.slice().sort((left, right) => {
|
|
@@ -4386,6 +5398,7 @@
|
|
|
4386
5398
|
reviewNotes = cloneReviewNotes(nextNotes);
|
|
4387
5399
|
updateReviewNotesUi();
|
|
4388
5400
|
renderReviewNotesList();
|
|
5401
|
+
refreshRenderedEditorPreviewComments();
|
|
4389
5402
|
if (editorView === "markdown") {
|
|
4390
5403
|
scheduleEditorLineNumberRender();
|
|
4391
5404
|
}
|
|
@@ -4394,6 +5407,31 @@
|
|
|
4394
5407
|
}
|
|
4395
5408
|
}
|
|
4396
5409
|
|
|
5410
|
+
function updateEditorSelectionCommentUi() {
|
|
5411
|
+
if (!editorSelectionCommentBtn) return;
|
|
5412
|
+
const hasSelection = Boolean(
|
|
5413
|
+
!suppressEditorSelectionComment
|
|
5414
|
+
&& editorView === "markdown"
|
|
5415
|
+
&& document.activeElement === sourceTextEl
|
|
5416
|
+
&& typeof sourceTextEl.selectionStart === "number"
|
|
5417
|
+
&& typeof sourceTextEl.selectionEnd === "number"
|
|
5418
|
+
&& sourceTextEl.selectionEnd > sourceTextEl.selectionStart
|
|
5419
|
+
);
|
|
5420
|
+
editorSelectionCommentBtn.hidden = !hasSelection;
|
|
5421
|
+
if (hasSelection) {
|
|
5422
|
+
editorSelectionCommentBtn.title = "Create a new local comment from the current editor selection.";
|
|
5423
|
+
editorSelectionCommentBtn.setAttribute("aria-label", editorSelectionCommentBtn.title);
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
|
|
5427
|
+
function clearSuppressedEditorSelectionComment() {
|
|
5428
|
+
if (!suppressEditorSelectionComment) return;
|
|
5429
|
+
suppressEditorSelectionComment = false;
|
|
5430
|
+
suppressedEditorSelectionStart = null;
|
|
5431
|
+
suppressedEditorSelectionEnd = null;
|
|
5432
|
+
updateEditorSelectionCommentUi();
|
|
5433
|
+
}
|
|
5434
|
+
|
|
4397
5435
|
function updateReviewNotesUi() {
|
|
4398
5436
|
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
4399
5437
|
const count = reviewNotes.length;
|
|
@@ -4421,8 +5459,10 @@
|
|
|
4421
5459
|
if (reviewNotesAddBtn) {
|
|
4422
5460
|
reviewNotesAddBtn.disabled = editorView !== "markdown";
|
|
4423
5461
|
reviewNotesAddBtn.title = editorView === "markdown"
|
|
4424
|
-
? "Create a new local comment
|
|
4425
|
-
:
|
|
5462
|
+
? "Create a new local comment on the current editor line."
|
|
5463
|
+
: (supportsPreviewCommentsForCurrentEditor()
|
|
5464
|
+
? "Select preview text and use Comment for a local preview-anchored comment."
|
|
5465
|
+
: "Switch to Editor (Raw) to comment on the current line.");
|
|
4426
5466
|
}
|
|
4427
5467
|
if (reviewNotesInlineAllBtn) {
|
|
4428
5468
|
const currentText = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
@@ -4580,12 +5620,49 @@
|
|
|
4580
5620
|
pendingReviewNoteInlineFocusId = null;
|
|
4581
5621
|
}
|
|
4582
5622
|
|
|
4583
|
-
function
|
|
4584
|
-
if (
|
|
4585
|
-
|
|
4586
|
-
|
|
5623
|
+
function focusReviewNotesForPreviewBlock(blockEl) {
|
|
5624
|
+
if (!blockEl) return;
|
|
5625
|
+
const start = Math.max(0, Number(blockEl.dataset && blockEl.dataset.reviewNoteStart) || 0);
|
|
5626
|
+
const end = Math.max(start, Number(blockEl.dataset && blockEl.dataset.reviewNoteEnd) || start);
|
|
5627
|
+
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5628
|
+
const notes = getPreviewCommentNotesForRange(start, end, source);
|
|
5629
|
+
if (!notes.length) return;
|
|
5630
|
+
focusReviewNoteInPanel(notes[0].id);
|
|
5631
|
+
}
|
|
5632
|
+
|
|
5633
|
+
function addReviewNoteFromPreviewBlock(blockEl) {
|
|
5634
|
+
const anchor = buildReviewNoteAnchorFromPreviewBlock(blockEl);
|
|
5635
|
+
if (!anchor) return null;
|
|
5636
|
+
return addReviewNoteFromAnchor(anchor, {
|
|
5637
|
+
statusMessage: "Added local comment from editor preview.",
|
|
5638
|
+
});
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5641
|
+
function addReviewNoteFromPreviewSelection(blockEl) {
|
|
5642
|
+
if (!blockEl) return null;
|
|
5643
|
+
const blockKey = getPreviewCommentBlockKey(blockEl);
|
|
5644
|
+
const anchor = activePreviewCommentSelection && activePreviewCommentSelection.blockKey === blockKey
|
|
5645
|
+
? activePreviewCommentSelection
|
|
5646
|
+
: null;
|
|
5647
|
+
if (!anchor) {
|
|
5648
|
+
setStatus("Select some preview text within a single block first.", "warning");
|
|
5649
|
+
return null;
|
|
5650
|
+
}
|
|
5651
|
+
const note = addReviewNoteFromAnchor(anchor, {
|
|
5652
|
+
statusMessage: "Added local comment from preview selection.",
|
|
5653
|
+
});
|
|
5654
|
+
if (note) {
|
|
5655
|
+
const selection = typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
5656
|
+
if (selection && typeof selection.removeAllRanges === "function") {
|
|
5657
|
+
selection.removeAllRanges();
|
|
5658
|
+
}
|
|
5659
|
+
clearPreviewCommentSelection();
|
|
4587
5660
|
}
|
|
4588
|
-
|
|
5661
|
+
return note;
|
|
5662
|
+
}
|
|
5663
|
+
|
|
5664
|
+
function addReviewNoteFromAnchor(anchor, options) {
|
|
5665
|
+
if (!anchor || typeof anchor !== "object") return null;
|
|
4589
5666
|
const note = normalizeReviewNote({
|
|
4590
5667
|
id: makeRequestId(),
|
|
4591
5668
|
text: "",
|
|
@@ -4596,14 +5673,49 @@
|
|
|
4596
5673
|
lineStart: anchor.lineStart,
|
|
4597
5674
|
lineEnd: anchor.lineEnd,
|
|
4598
5675
|
selectedText: anchor.selectedText,
|
|
5676
|
+
selectedDisplayText: typeof anchor.selectedDisplayText === "string" ? anchor.selectedDisplayText : (typeof anchor.selectedText === "string" ? anchor.selectedText : ""),
|
|
4599
5677
|
});
|
|
4600
|
-
if (!note) return;
|
|
5678
|
+
if (!note) return null;
|
|
5679
|
+
if (editorSelectionCommentBtn) {
|
|
5680
|
+
editorSelectionCommentBtn.hidden = true;
|
|
5681
|
+
}
|
|
5682
|
+
const shouldOpenReviewNotes = !isReviewNotesOpen();
|
|
4601
5683
|
pendingReviewNoteFocusId = note.id;
|
|
4602
5684
|
setReviewNotes(reviewNotes.concat([note]));
|
|
4603
|
-
if (
|
|
5685
|
+
if (shouldOpenReviewNotes) {
|
|
5686
|
+
pendingReviewNoteFocusId = note.id;
|
|
4604
5687
|
openReviewNotes();
|
|
4605
5688
|
}
|
|
4606
|
-
|
|
5689
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
5690
|
+
? window.requestAnimationFrame.bind(window)
|
|
5691
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
5692
|
+
schedule(() => {
|
|
5693
|
+
updateEditorSelectionCommentUi();
|
|
5694
|
+
});
|
|
5695
|
+
if (!options || options.status !== false) {
|
|
5696
|
+
setStatus((options && options.statusMessage) || "Added local comment.", "success");
|
|
5697
|
+
}
|
|
5698
|
+
return note;
|
|
5699
|
+
}
|
|
5700
|
+
|
|
5701
|
+
function addReviewNoteFromEditorSelection() {
|
|
5702
|
+
if (editorView !== "markdown") {
|
|
5703
|
+
setStatus("Switch to Editor (Raw) before adding an anchored comment.", "warning");
|
|
5704
|
+
return;
|
|
5705
|
+
}
|
|
5706
|
+
addReviewNoteFromAnchor(getEditorAnchorForReviewNote(), {
|
|
5707
|
+
statusMessage: "Added local comment.",
|
|
5708
|
+
});
|
|
5709
|
+
}
|
|
5710
|
+
|
|
5711
|
+
function addReviewNoteFromEditorLine() {
|
|
5712
|
+
if (editorView !== "markdown") {
|
|
5713
|
+
setStatus("Switch to Editor (Raw) before adding a line comment.", "warning");
|
|
5714
|
+
return;
|
|
5715
|
+
}
|
|
5716
|
+
addReviewNoteFromAnchor(getEditorLineAnchorForReviewNote(), {
|
|
5717
|
+
statusMessage: "Added local line comment.",
|
|
5718
|
+
});
|
|
4607
5719
|
}
|
|
4608
5720
|
|
|
4609
5721
|
function jumpToReviewNote(noteId) {
|
|
@@ -4615,6 +5727,10 @@
|
|
|
4615
5727
|
setStatus("Could not find the anchored location for this comment.", "warning");
|
|
4616
5728
|
return;
|
|
4617
5729
|
}
|
|
5730
|
+
suppressEditorSelectionComment = true;
|
|
5731
|
+
suppressedEditorSelectionStart = range.start;
|
|
5732
|
+
suppressedEditorSelectionEnd = range.end;
|
|
5733
|
+
updateEditorSelectionCommentUi();
|
|
4618
5734
|
setEditorView("markdown");
|
|
4619
5735
|
setActivePane("left");
|
|
4620
5736
|
sourceTextEl.focus();
|
|
@@ -4624,6 +5740,8 @@
|
|
|
4624
5740
|
: (cb) => window.setTimeout(cb, 16);
|
|
4625
5741
|
schedule(() => {
|
|
4626
5742
|
scrollEditorRangeIntoView(range);
|
|
5743
|
+
revealReviewNoteInPreview(note);
|
|
5744
|
+
updateEditorSelectionCommentUi();
|
|
4627
5745
|
});
|
|
4628
5746
|
}
|
|
4629
5747
|
|
|
@@ -6116,14 +7234,52 @@
|
|
|
6116
7234
|
});
|
|
6117
7235
|
|
|
6118
7236
|
sourceTextEl.addEventListener("input", () => {
|
|
7237
|
+
if (activePreviewCommentSelection) {
|
|
7238
|
+
clearPreviewCommentSelection();
|
|
7239
|
+
}
|
|
7240
|
+
clearSuppressedEditorSelectionComment();
|
|
6119
7241
|
renderSourcePreview({ previewDelayMs: PREVIEW_INPUT_DEBOUNCE_MS });
|
|
6120
7242
|
scheduleEditorMetaUpdate();
|
|
7243
|
+
updateEditorSelectionCommentUi();
|
|
6121
7244
|
if (isReviewNotesOpen() && reviewNotes.length > 0) {
|
|
6122
7245
|
renderReviewNotesList();
|
|
6123
7246
|
updateReviewNotesUi();
|
|
6124
7247
|
}
|
|
6125
7248
|
});
|
|
6126
7249
|
|
|
7250
|
+
sourceTextEl.addEventListener("select", () => {
|
|
7251
|
+
if (suppressEditorSelectionComment) {
|
|
7252
|
+
const selectionStart = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : 0;
|
|
7253
|
+
const selectionEnd = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : selectionStart;
|
|
7254
|
+
const matchesSuppressedSelection = selectionStart === suppressedEditorSelectionStart && selectionEnd === suppressedEditorSelectionEnd;
|
|
7255
|
+
if (!matchesSuppressedSelection && selectionEnd > selectionStart) {
|
|
7256
|
+
clearSuppressedEditorSelectionComment();
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
updateEditorSelectionCommentUi();
|
|
7260
|
+
});
|
|
7261
|
+
|
|
7262
|
+
sourceTextEl.addEventListener("keyup", () => {
|
|
7263
|
+
updateEditorSelectionCommentUi();
|
|
7264
|
+
});
|
|
7265
|
+
|
|
7266
|
+
sourceTextEl.addEventListener("mouseup", () => {
|
|
7267
|
+
updateEditorSelectionCommentUi();
|
|
7268
|
+
});
|
|
7269
|
+
|
|
7270
|
+
sourceTextEl.addEventListener("focus", () => {
|
|
7271
|
+
updateEditorSelectionCommentUi();
|
|
7272
|
+
});
|
|
7273
|
+
|
|
7274
|
+
sourceTextEl.addEventListener("blur", () => {
|
|
7275
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
7276
|
+
? window.requestAnimationFrame.bind(window)
|
|
7277
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
7278
|
+
schedule(() => {
|
|
7279
|
+
updateEditorSelectionCommentUi();
|
|
7280
|
+
});
|
|
7281
|
+
});
|
|
7282
|
+
|
|
6127
7283
|
sourceTextEl.addEventListener("scroll", () => {
|
|
6128
7284
|
if (editorView !== "markdown") return;
|
|
6129
7285
|
syncEditorHighlightScroll();
|
|
@@ -6487,6 +7643,15 @@
|
|
|
6487
7643
|
|
|
6488
7644
|
if (reviewNotesAddBtn) {
|
|
6489
7645
|
reviewNotesAddBtn.addEventListener("click", () => {
|
|
7646
|
+
addReviewNoteFromEditorLine();
|
|
7647
|
+
});
|
|
7648
|
+
}
|
|
7649
|
+
|
|
7650
|
+
if (editorSelectionCommentBtn) {
|
|
7651
|
+
editorSelectionCommentBtn.addEventListener("mousedown", (event) => {
|
|
7652
|
+
event.preventDefault();
|
|
7653
|
+
});
|
|
7654
|
+
editorSelectionCommentBtn.addEventListener("click", () => {
|
|
6490
7655
|
addReviewNoteFromEditorSelection();
|
|
6491
7656
|
});
|
|
6492
7657
|
}
|
|
@@ -6508,6 +7673,42 @@
|
|
|
6508
7673
|
});
|
|
6509
7674
|
}
|
|
6510
7675
|
|
|
7676
|
+
function handlePreviewCommentActionMouseDown(event) {
|
|
7677
|
+
const target = event.target;
|
|
7678
|
+
const actionBtn = target instanceof Element ? target.closest(".preview-comment-add, .preview-comment-summary") : null;
|
|
7679
|
+
if (!actionBtn) return;
|
|
7680
|
+
event.preventDefault();
|
|
7681
|
+
}
|
|
7682
|
+
|
|
7683
|
+
function handlePreviewCommentActionClick(event) {
|
|
7684
|
+
const target = event.target;
|
|
7685
|
+
const actionBtn = target instanceof Element ? target.closest(".preview-comment-add, .preview-comment-summary") : null;
|
|
7686
|
+
if (!actionBtn) return;
|
|
7687
|
+
const blockEl = actionBtn.closest(".preview-comment-block");
|
|
7688
|
+
if (!blockEl) return;
|
|
7689
|
+
event.preventDefault();
|
|
7690
|
+
event.stopPropagation();
|
|
7691
|
+
const mode = String(actionBtn.dataset && actionBtn.dataset.previewCommentMode ? actionBtn.dataset.previewCommentMode : "");
|
|
7692
|
+
if (mode !== "selection") return;
|
|
7693
|
+
addReviewNoteFromPreviewSelection(blockEl);
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7696
|
+
if (leftPaneEl) {
|
|
7697
|
+
leftPaneEl.addEventListener("mousedown", handlePreviewCommentActionMouseDown);
|
|
7698
|
+
leftPaneEl.addEventListener("click", handlePreviewCommentActionClick);
|
|
7699
|
+
}
|
|
7700
|
+
|
|
7701
|
+
if (rightPaneEl) {
|
|
7702
|
+
rightPaneEl.addEventListener("mousedown", handlePreviewCommentActionMouseDown);
|
|
7703
|
+
rightPaneEl.addEventListener("click", handlePreviewCommentActionClick);
|
|
7704
|
+
}
|
|
7705
|
+
|
|
7706
|
+
if (typeof document.addEventListener === "function") {
|
|
7707
|
+
document.addEventListener("selectionchange", () => {
|
|
7708
|
+
updateActivePreviewCommentSelectionFromDom();
|
|
7709
|
+
});
|
|
7710
|
+
}
|
|
7711
|
+
|
|
6511
7712
|
if (scratchpadBtn) {
|
|
6512
7713
|
scratchpadBtn.addEventListener("click", () => {
|
|
6513
7714
|
openScratchpad();
|