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