@nkzw/codiff-core 1.10.0 → 1.11.0
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/dist/SharedWalkthroughApp.d.ts +2 -1
- package/dist/app/components/DefinitionPopover.d.ts +12 -0
- package/dist/app/components/Panels.d.ts +3 -2
- package/dist/app/components/ReadOnlyMarkdownView.d.ts +1 -0
- package/dist/app/components/ResolvedThreadDisclosure.d.ts +9 -0
- package/dist/app/components/ReviewCodeView.d.ts +4 -2
- package/dist/app/components/ReviewModeControl.d.ts +14 -0
- package/dist/app/components/ReviewTopBar.d.ts +13 -13
- package/dist/app/components/merge-request/GeneralComments.d.ts +2 -1
- package/dist/app/components/walkthrough/NarrativeWalkthroughView.d.ts +6 -4
- package/dist/app/components/walkthrough/useNarrativeNavigation.d.ts +1 -1
- package/dist/app/hooks/useAppReviewComments.d.ts +1 -0
- package/dist/app/hooks/useAppWalkthrough.d.ts +1 -1
- package/dist/app/hooks/useResizableSidebar.d.ts +2 -1
- package/dist/app/hooks/useReviewCommentDrafts.d.ts +1 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/lib/app-types.d.ts +1 -0
- package/dist/lib/code-view-options.d.ts +1 -1
- package/dist/lib/identifier-navigation.d.ts +10 -0
- package/dist/lib/keyboard.d.ts +1 -0
- package/dist/react.mjs +1071 -262
- package/dist/share.mjs +8 -7
- package/dist/style.css +285 -43
- package/dist/types.d.ts +26 -1
- package/package.json +4 -4
package/dist/react.mjs
CHANGED
|
@@ -2,12 +2,13 @@ import { filterPatchToHunkIds, getSectionWalkthroughHunks, isGeneratedWalkthroug
|
|
|
2
2
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { Fragment, Suspense, forwardRef, lazy, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useOptimistic, useRef, useState, useTransition } from "react";
|
|
5
|
-
import { useFormStatus } from "react-dom";
|
|
5
|
+
import { createPortal, useFormStatus } from "react-dom";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { MarkdownEditor } from "@nkzw/mdx-editor";
|
|
8
8
|
import { frontmatterPlugin, imagePlugin } from "@nkzw/mdx-editor/core";
|
|
9
9
|
import { DownloadSimpleIcon } from "@phosphor-icons/react/DownloadSimple";
|
|
10
10
|
import { Copy, Trash2, X } from "lucide-react";
|
|
11
|
+
import { CheckIcon } from "@phosphor-icons/react/Check";
|
|
11
12
|
import { ArrowSquareOutIcon } from "@phosphor-icons/react/ArrowSquareOut";
|
|
12
13
|
import { ChatCircleIcon } from "@phosphor-icons/react/ChatCircle";
|
|
13
14
|
import { PathIcon } from "@phosphor-icons/react/Path";
|
|
@@ -17,7 +18,6 @@ import { hydratePartialDiff, parseDiffFromFile, parsePatchFiles, registerCustomT
|
|
|
17
18
|
import useRelativeTime from "@nkzw/use-relative-time";
|
|
18
19
|
import { CaretDownIcon } from "@phosphor-icons/react/CaretDown";
|
|
19
20
|
import "@phosphor-icons/react/CaretUp";
|
|
20
|
-
import { CheckIcon } from "@phosphor-icons/react/Check";
|
|
21
21
|
import { CheckCircleIcon } from "@phosphor-icons/react/CheckCircle";
|
|
22
22
|
import "@phosphor-icons/react/CircleNotch";
|
|
23
23
|
import { PowerIcon } from "@phosphor-icons/react/Power";
|
|
@@ -28,6 +28,8 @@ import { ColumnsIcon } from "@phosphor-icons/react/Columns";
|
|
|
28
28
|
import { ImageBrokenIcon } from "@phosphor-icons/react/ImageBroken";
|
|
29
29
|
import { SquareSplitVerticalIcon } from "@phosphor-icons/react/SquareSplitVertical";
|
|
30
30
|
import { CodeView, WorkerPoolContextProvider } from "@pierre/diffs/react";
|
|
31
|
+
import { CrosshairSimpleIcon } from "@phosphor-icons/react/CrosshairSimple";
|
|
32
|
+
import { ProhibitIcon } from "@phosphor-icons/react/Prohibit";
|
|
31
33
|
import { PersistentMarkdownEditor } from "@nkzw/mdx-editor/persistence";
|
|
32
34
|
import { GitBranchIcon } from "@phosphor-icons/react/GitBranch";
|
|
33
35
|
import { ShareNetworkIcon } from "@phosphor-icons/react/ShareNetwork";
|
|
@@ -294,13 +296,40 @@ const renderInlineMarkdown = (text) => {
|
|
|
294
296
|
return nodes.length > 0 ? nodes : text;
|
|
295
297
|
};
|
|
296
298
|
//#endregion
|
|
299
|
+
//#region app/components/useCopiedState.ts
|
|
300
|
+
function useCopiedState(timeoutMs) {
|
|
301
|
+
const [copied, setCopied] = useState(false);
|
|
302
|
+
const copiedTimerRef = useRef(null);
|
|
303
|
+
useEffect(() => () => {
|
|
304
|
+
if (copiedTimerRef.current != null) window.clearTimeout(copiedTimerRef.current);
|
|
305
|
+
}, []);
|
|
306
|
+
return [copied, useCallback(() => {
|
|
307
|
+
setCopied(true);
|
|
308
|
+
if (copiedTimerRef.current != null) window.clearTimeout(copiedTimerRef.current);
|
|
309
|
+
copiedTimerRef.current = window.setTimeout(() => {
|
|
310
|
+
setCopied(false);
|
|
311
|
+
copiedTimerRef.current = null;
|
|
312
|
+
}, timeoutMs);
|
|
313
|
+
}, [timeoutMs])];
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
297
316
|
//#region app/components/ReadOnlyMarkdownView.tsx
|
|
298
|
-
const detailsBlockPattern = /<details\b([^>]*)>\s*<summary\b[^>]*>([\s\S]*?)<\/summary>([\s\S]*?)<\/details>/gi;
|
|
299
317
|
const htmlCommentPattern$1 = /<!--[\s\S]*?-->/g;
|
|
318
|
+
const detailsOpenPattern = /<details\b([^>]*)>/gi;
|
|
319
|
+
const detailsTagPattern = /<\/?details\b[^>]*>/gi;
|
|
320
|
+
const summaryOpenPattern = /^\s*<summary\b[^>]*>/i;
|
|
321
|
+
const summaryClosePattern = /<\/summary>/gi;
|
|
300
322
|
const hasOpenAttribute = (attributes) => /(?:^|\s)open(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?(?:\s|$)/i.test(attributes);
|
|
301
323
|
const stripHtmlComments$1 = (value) => value.replaceAll(htmlCommentPattern$1, "");
|
|
302
|
-
const
|
|
303
|
-
|
|
324
|
+
const getOpeningFenceMarker = (line) => {
|
|
325
|
+
const match = /^(?: {0,3})(`{3,}|~{3,})/.exec(line);
|
|
326
|
+
const marker = match?.[1] ?? null;
|
|
327
|
+
if (!marker || marker[0] === "`" && line.slice(match[0].length).includes("`")) return null;
|
|
328
|
+
return marker;
|
|
329
|
+
};
|
|
330
|
+
const isClosingFence = (marker, line) => {
|
|
331
|
+
const candidate = /^(?: {0,3})(`{3,}|~{3,})[ \t]*$/.exec(line)?.[1] ?? null;
|
|
332
|
+
return candidate != null && candidate[0] === marker[0] && candidate.length >= marker.length;
|
|
304
333
|
};
|
|
305
334
|
const normalizeReadOnlyMarkdownValue = (value) => {
|
|
306
335
|
const normalizedLineEndings = value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
@@ -310,10 +339,9 @@ const normalizeReadOnlyMarkdownValue = (value) => {
|
|
|
310
339
|
let pendingBlankLine = false;
|
|
311
340
|
let fenceMarker = null;
|
|
312
341
|
for (const line of lines) {
|
|
313
|
-
const currentFenceMarker = getFenceMarker(line);
|
|
314
342
|
if (fenceMarker) {
|
|
315
343
|
normalizedLines.push(line);
|
|
316
|
-
if (
|
|
344
|
+
if (isClosingFence(fenceMarker, line)) fenceMarker = null;
|
|
317
345
|
continue;
|
|
318
346
|
}
|
|
319
347
|
if (!line.trim()) {
|
|
@@ -323,27 +351,83 @@ const normalizeReadOnlyMarkdownValue = (value) => {
|
|
|
323
351
|
if (pendingBlankLine && normalizedLines.length > 0) normalizedLines.push("");
|
|
324
352
|
pendingBlankLine = false;
|
|
325
353
|
normalizedLines.push(line);
|
|
326
|
-
|
|
354
|
+
const openingFenceMarker = getOpeningFenceMarker(line);
|
|
355
|
+
if (openingFenceMarker) fenceMarker = openingFenceMarker;
|
|
327
356
|
}
|
|
328
357
|
const normalizedValue = normalizedLines.join("\n");
|
|
329
358
|
return preserveSingleTrailingNewline && normalizedValue ? `${normalizedValue}\n` : normalizedValue;
|
|
330
359
|
};
|
|
360
|
+
const extractFencedCodeBlocks = (value) => {
|
|
361
|
+
const blocks = [];
|
|
362
|
+
let fenceMarker = null;
|
|
363
|
+
let currentBlock = [];
|
|
364
|
+
for (const line of value.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n")) {
|
|
365
|
+
if (fenceMarker) {
|
|
366
|
+
if (isClosingFence(fenceMarker, line)) {
|
|
367
|
+
blocks.push(currentBlock.join("\n"));
|
|
368
|
+
currentBlock = [];
|
|
369
|
+
fenceMarker = null;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
currentBlock.push(line);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const openingFenceMarker = getOpeningFenceMarker(line);
|
|
376
|
+
if (openingFenceMarker) fenceMarker = openingFenceMarker;
|
|
377
|
+
}
|
|
378
|
+
while (fenceMarker && currentBlock.at(-1) === "") currentBlock.pop();
|
|
379
|
+
if (fenceMarker && currentBlock.length > 0) blocks.push(currentBlock.join("\n"));
|
|
380
|
+
return blocks.filter((block) => block.trim().length > 0);
|
|
381
|
+
};
|
|
331
382
|
const parseMarkdownDetails = (value) => {
|
|
332
383
|
const parts = [];
|
|
333
384
|
let lastIndex = 0;
|
|
334
|
-
let
|
|
335
|
-
while (
|
|
336
|
-
|
|
385
|
+
let searchIndex = 0;
|
|
386
|
+
while (searchIndex < value.length) {
|
|
387
|
+
detailsOpenPattern.lastIndex = searchIndex;
|
|
388
|
+
const detailsOpen = detailsOpenPattern.exec(value);
|
|
389
|
+
if (!detailsOpen) break;
|
|
390
|
+
const detailsBodyStart = detailsOpenPattern.lastIndex;
|
|
391
|
+
const summaryOpen = summaryOpenPattern.exec(value.slice(detailsBodyStart));
|
|
392
|
+
if (!summaryOpen) {
|
|
393
|
+
searchIndex = detailsBodyStart;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const summaryStart = detailsBodyStart + summaryOpen[0].length;
|
|
397
|
+
summaryClosePattern.lastIndex = summaryStart;
|
|
398
|
+
const summaryClose = summaryClosePattern.exec(value);
|
|
399
|
+
if (!summaryClose) {
|
|
400
|
+
searchIndex = detailsBodyStart;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const bodyStart = summaryClosePattern.lastIndex;
|
|
404
|
+
detailsTagPattern.lastIndex = bodyStart;
|
|
405
|
+
let depth = 1;
|
|
406
|
+
let detailsClose = null;
|
|
407
|
+
while (depth > 0) {
|
|
408
|
+
const tag = detailsTagPattern.exec(value);
|
|
409
|
+
if (!tag) break;
|
|
410
|
+
if (/^<\/details\b/i.test(tag[0])) {
|
|
411
|
+
depth -= 1;
|
|
412
|
+
if (depth === 0) detailsClose = tag;
|
|
413
|
+
} else depth += 1;
|
|
414
|
+
}
|
|
415
|
+
if (!detailsClose) {
|
|
416
|
+
searchIndex = detailsBodyStart;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (detailsOpen.index > lastIndex) parts.push({
|
|
337
420
|
type: "markdown",
|
|
338
|
-
value: value.slice(lastIndex,
|
|
421
|
+
value: value.slice(lastIndex, detailsOpen.index)
|
|
339
422
|
});
|
|
340
423
|
parts.push({
|
|
341
|
-
body:
|
|
342
|
-
open: hasOpenAttribute(
|
|
343
|
-
summary: stripHtmlComments$1(
|
|
424
|
+
body: value.slice(bodyStart, detailsClose.index),
|
|
425
|
+
open: hasOpenAttribute(detailsOpen[1] ?? ""),
|
|
426
|
+
summary: stripHtmlComments$1(value.slice(summaryStart, summaryClose.index)).trim(),
|
|
344
427
|
type: "details"
|
|
345
428
|
});
|
|
346
|
-
lastIndex =
|
|
429
|
+
lastIndex = detailsTagPattern.lastIndex;
|
|
430
|
+
searchIndex = lastIndex;
|
|
347
431
|
}
|
|
348
432
|
if (lastIndex < value.length) parts.push({
|
|
349
433
|
type: "markdown",
|
|
@@ -352,6 +436,42 @@ const parseMarkdownDetails = (value) => {
|
|
|
352
436
|
return parts;
|
|
353
437
|
};
|
|
354
438
|
const hasDetailsBlock = (parts) => parts.some((part) => part.type === "details");
|
|
439
|
+
const getOwnFencedCode = (parts) => parts.filter((part) => part.type === "markdown").flatMap((part) => extractFencedCodeBlocks(part.value)).join("\n\n");
|
|
440
|
+
function CopyCodeButton({ code }) {
|
|
441
|
+
const [copied, markCopied] = useCopiedState(1600);
|
|
442
|
+
const handleClick = useCallback(async (event) => {
|
|
443
|
+
event.preventDefault();
|
|
444
|
+
event.stopPropagation();
|
|
445
|
+
try {
|
|
446
|
+
await navigator.clipboard.writeText(code);
|
|
447
|
+
} catch {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
markCopied();
|
|
451
|
+
}, [code, markCopied]);
|
|
452
|
+
const label = copied ? "Code copied" : "Copy code";
|
|
453
|
+
return /* @__PURE__ */ jsx("button", {
|
|
454
|
+
"aria-label": label,
|
|
455
|
+
className: `codiff-copy-path-button codiff-copy-code-button${copied ? " copied" : ""}`,
|
|
456
|
+
onClick: (event) => void handleClick(event),
|
|
457
|
+
onKeyDown: (event) => {
|
|
458
|
+
if (event.key === "Enter" || event.key === " ") event.stopPropagation();
|
|
459
|
+
},
|
|
460
|
+
title: label,
|
|
461
|
+
type: "button",
|
|
462
|
+
children: copied ? /* @__PURE__ */ jsx(CheckIcon, {
|
|
463
|
+
"aria-hidden": true,
|
|
464
|
+
className: "codiff-copy-path-icon check",
|
|
465
|
+
size: 16,
|
|
466
|
+
weight: "bold"
|
|
467
|
+
}) : /* @__PURE__ */ jsx(Copy, {
|
|
468
|
+
"aria-hidden": true,
|
|
469
|
+
className: "codiff-copy-path-icon",
|
|
470
|
+
size: 16,
|
|
471
|
+
strokeWidth: 2.25
|
|
472
|
+
})
|
|
473
|
+
});
|
|
474
|
+
}
|
|
355
475
|
function MarkdownSegment({ additionalPlugins, ariaLabel, contentClassName, editorClassName, onHeightChange, value }) {
|
|
356
476
|
const normalizedValue = normalizeReadOnlyMarkdownValue(value);
|
|
357
477
|
if (!normalizedValue.trim()) return null;
|
|
@@ -383,17 +503,22 @@ function MarkdownParts({ additionalPlugins, ariaLabel, contentClassName, editorC
|
|
|
383
503
|
onHeightChange,
|
|
384
504
|
value: part.value
|
|
385
505
|
}, `markdown:${index}`);
|
|
506
|
+
const bodyParts = parseMarkdownDetails(part.body);
|
|
507
|
+
const code = getOwnFencedCode(bodyParts);
|
|
386
508
|
return /* @__PURE__ */ jsxs("details", {
|
|
387
509
|
className: "codiff-markdown-details",
|
|
388
510
|
onToggle: (event) => onHeightChange?.(event.currentTarget.getBoundingClientRect().height),
|
|
389
511
|
open: part.open,
|
|
390
|
-
children: [/* @__PURE__ */
|
|
512
|
+
children: [/* @__PURE__ */ jsxs("summary", { children: [/* @__PURE__ */ jsx("span", {
|
|
513
|
+
className: "codiff-markdown-details-summary-label",
|
|
514
|
+
children: renderInlineMarkdown(part.summary || "Details")
|
|
515
|
+
}), code ? /* @__PURE__ */ jsx(CopyCodeButton, { code }) : null] }), /* @__PURE__ */ jsx(MarkdownParts, {
|
|
391
516
|
additionalPlugins,
|
|
392
517
|
ariaLabel: `${ariaLabel} details`,
|
|
393
518
|
contentClassName,
|
|
394
519
|
editorClassName,
|
|
395
520
|
onHeightChange,
|
|
396
|
-
parts:
|
|
521
|
+
parts: bodyParts
|
|
397
522
|
})]
|
|
398
523
|
}, `details:${index}`);
|
|
399
524
|
});
|
|
@@ -497,8 +622,7 @@ const getPlanCommentTargetLabel = (anchor) => {
|
|
|
497
622
|
const entity = anchor.kind === "text" ? "Selection" : planCommentEntityLabels[anchor.block.type] ?? "Block";
|
|
498
623
|
const targetText = (anchor.kind === "text" ? anchor.quote?.exact : anchor.block.text)?.replaceAll(/\s+/g, " ").trim();
|
|
499
624
|
if (!targetText) return entity;
|
|
500
|
-
|
|
501
|
-
return `${entity} · ${targetText.length > maximumLength ? `${targetText.slice(0, maximumLength - 1).trimEnd()}…` : targetText}`;
|
|
625
|
+
return `${entity} · ${targetText.length > 48 ? `${targetText.slice(0, 47).trimEnd()}…` : targetText}`;
|
|
502
626
|
};
|
|
503
627
|
const escapeInlineMarkdown = (value) => value.replaceAll("\\", String.raw`\\`).replaceAll(/([`*_[\]<>])/g, String.raw`\$1`);
|
|
504
628
|
const getPlanCommentAuthorLabel = (author) => {
|
|
@@ -1420,6 +1544,7 @@ const getFirstVisibleSection = (file, showWhitespace) => getVisibleDiffSections(
|
|
|
1420
1544
|
//#region lib/files.ts
|
|
1421
1545
|
const statusForTree = {
|
|
1422
1546
|
added: "added",
|
|
1547
|
+
conflicted: "modified",
|
|
1423
1548
|
deleted: "deleted",
|
|
1424
1549
|
modified: "modified",
|
|
1425
1550
|
renamed: "renamed",
|
|
@@ -1558,22 +1683,23 @@ function ReviewFileTree({ files, onActivatePath, reloadDeltaPaths = emptyPaths$2
|
|
|
1558
1683
|
scrollSelectedPathIntoView,
|
|
1559
1684
|
selectedPath
|
|
1560
1685
|
]);
|
|
1686
|
+
const handleTreeClick = useCallback((event) => {
|
|
1687
|
+
for (const target of event.nativeEvent.composedPath()) {
|
|
1688
|
+
if (!("getAttribute" in target) || typeof target.getAttribute !== "function") continue;
|
|
1689
|
+
const path = target.getAttribute("data-item-path");
|
|
1690
|
+
if (path && filePathSet.has(path)) {
|
|
1691
|
+
onActivatePath(path);
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}, [filePathSet, onActivatePath]);
|
|
1561
1696
|
return /* @__PURE__ */ jsx("div", {
|
|
1562
1697
|
className: "file-tree-shell",
|
|
1563
1698
|
ref: treeHostRef,
|
|
1564
1699
|
children: /* @__PURE__ */ jsx(FileTree, {
|
|
1565
1700
|
className: "file-tree",
|
|
1566
1701
|
model,
|
|
1567
|
-
onClick:
|
|
1568
|
-
for (const target of event.nativeEvent.composedPath()) {
|
|
1569
|
-
if (!("getAttribute" in target) || typeof target.getAttribute !== "function") continue;
|
|
1570
|
-
const path = target.getAttribute("data-item-path");
|
|
1571
|
-
if (path && filePathSet.has(path)) {
|
|
1572
|
-
onActivatePath(path);
|
|
1573
|
-
return;
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
}, [filePathSet, onActivatePath])
|
|
1702
|
+
onClick: handleTreeClick
|
|
1577
1703
|
})
|
|
1578
1704
|
});
|
|
1579
1705
|
}
|
|
@@ -1727,6 +1853,66 @@ const matchesCombo = (event, combo, matchesKey) => {
|
|
|
1727
1853
|
const matchesBinding = (event, binding, matchesKey) => getBindingCombos(binding).some((combo) => matchesCombo(event, combo, matchesKey));
|
|
1728
1854
|
const canFallBackToPhysicalKey = (event, keymap) => event.altKey && isMac() && isDeadOrCharacterKey(event.key) && !Object.values(keymap).some((binding) => matchesBinding(event, binding, matchesTypedKey));
|
|
1729
1855
|
const matchesShortcut = (event, keymap, action) => matchesBinding(event, keymap[action], matchesTypedKey) || canFallBackToPhysicalKey(event, keymap) && matchesBinding(event, keymap[action], matchesPhysicalKey);
|
|
1856
|
+
const getShortcutLabel = (keymap, action) => {
|
|
1857
|
+
const combo = getBindingCombos(keymap[action])[0] ?? "";
|
|
1858
|
+
const mac = isMac();
|
|
1859
|
+
return combo.split("+").map((part) => {
|
|
1860
|
+
const lower = part.trim().toLowerCase();
|
|
1861
|
+
if (lower === "mod") return mac ? "⌘" : "Ctrl";
|
|
1862
|
+
if (lower === "shift") return mac ? "⇧" : "Shift";
|
|
1863
|
+
if (lower === "alt") return mac ? "⌥" : "Alt";
|
|
1864
|
+
if (lower === "ctrl") return mac ? "⌃" : "Ctrl";
|
|
1865
|
+
if (lower === "meta") return mac ? "⌘" : "Win";
|
|
1866
|
+
if (lower === "enter") return mac ? "↩" : "Enter";
|
|
1867
|
+
if (lower === "escape") return "Esc";
|
|
1868
|
+
if (lower === "arrowup") return "↑";
|
|
1869
|
+
if (lower === "arrowdown") return "↓";
|
|
1870
|
+
if (lower === "arrowleft") return "←";
|
|
1871
|
+
if (lower === "arrowright") return "→";
|
|
1872
|
+
return part.trim().toUpperCase();
|
|
1873
|
+
}).join("+");
|
|
1874
|
+
};
|
|
1875
|
+
//#endregion
|
|
1876
|
+
//#region app/components/ResolvedThreadDisclosure.tsx
|
|
1877
|
+
function ResolvedThreadDisclosure({ children, commentCount, focused = false, focusRequest = 0, initialExpanded = focused, saving = false }) {
|
|
1878
|
+
const [expansion, setExpansion] = useState({
|
|
1879
|
+
expanded: initialExpanded,
|
|
1880
|
+
focusRequest
|
|
1881
|
+
});
|
|
1882
|
+
const expanded = focused && expansion.focusRequest !== focusRequest ? true : expansion.expanded;
|
|
1883
|
+
const countLabel = `${commentCount} ${commentCount === 1 ? "comment" : "comments"}`;
|
|
1884
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1885
|
+
className: `resolved-thread-disclosure${expanded ? " expanded" : ""}`,
|
|
1886
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
1887
|
+
"aria-expanded": expanded,
|
|
1888
|
+
className: "resolved-thread-toggle",
|
|
1889
|
+
onClick: () => setExpansion({
|
|
1890
|
+
expanded: !expanded,
|
|
1891
|
+
focusRequest
|
|
1892
|
+
}),
|
|
1893
|
+
type: "button",
|
|
1894
|
+
children: [
|
|
1895
|
+
/* @__PURE__ */ jsx("span", {
|
|
1896
|
+
className: "resolved-thread-label",
|
|
1897
|
+
children: saving ? "Saving resolved conversation" : "Resolved conversation"
|
|
1898
|
+
}),
|
|
1899
|
+
/* @__PURE__ */ jsx("span", {
|
|
1900
|
+
className: "resolved-thread-count",
|
|
1901
|
+
children: countLabel
|
|
1902
|
+
}),
|
|
1903
|
+
/* @__PURE__ */ jsx(CaretDownIcon, {
|
|
1904
|
+
"aria-hidden": true,
|
|
1905
|
+
className: "resolved-thread-caret",
|
|
1906
|
+
size: 14,
|
|
1907
|
+
weight: "bold"
|
|
1908
|
+
})
|
|
1909
|
+
]
|
|
1910
|
+
}), expanded ? /* @__PURE__ */ jsx("div", {
|
|
1911
|
+
className: "resolved-thread-content",
|
|
1912
|
+
children
|
|
1913
|
+
}) : null]
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1730
1916
|
//#endregion
|
|
1731
1917
|
//#region app/components/merge-request/GeneralComments.tsx
|
|
1732
1918
|
const getAuthorDisplayName = (author) => author.name || author.login;
|
|
@@ -1770,13 +1956,14 @@ function SubmittedAtTime({ submittedAt }) {
|
|
|
1770
1956
|
}
|
|
1771
1957
|
function ReadOnlyGeneralCommentCard({ className = "", comment, focused = false }) {
|
|
1772
1958
|
const displayName = getAuthorDisplayName(comment.author);
|
|
1959
|
+
const classes = [
|
|
1960
|
+
"review-comment",
|
|
1961
|
+
"general-comment-card",
|
|
1962
|
+
focused ? "focused" : "",
|
|
1963
|
+
className
|
|
1964
|
+
].filter(Boolean).join(" ");
|
|
1773
1965
|
return /* @__PURE__ */ jsxs("article", {
|
|
1774
|
-
className:
|
|
1775
|
-
"review-comment",
|
|
1776
|
-
"general-comment-card",
|
|
1777
|
-
focused ? "focused" : "",
|
|
1778
|
-
className
|
|
1779
|
-
].filter(Boolean).join(" "),
|
|
1966
|
+
className: classes,
|
|
1780
1967
|
id: getGeneralCommentElementId(comment.id),
|
|
1781
1968
|
children: [/* @__PURE__ */ jsx(Avatar, {
|
|
1782
1969
|
name: displayName,
|
|
@@ -1913,13 +2100,16 @@ function GeneralCommentCard({ comment, editDraft, editError, editing, editSubmit
|
|
|
1913
2100
|
})]
|
|
1914
2101
|
});
|
|
1915
2102
|
}
|
|
1916
|
-
function GeneralCommentThreadCard({ canComment, editDraft, editError, editingCommentId, editSubmitting, focusedCommentId, keymap, onCancelEdit, onChangeEditDraft, onDelete, onReply, onResolve, onSaveEdit, onStartEdit, thread }) {
|
|
2103
|
+
function GeneralCommentThreadCard({ canComment, editDraft, editError, editingCommentId, editSubmitting, focusedCommentId, focusedCommentRequest, keymap, onCancelEdit, onChangeEditDraft, onDelete, onReply, onResolve, onSaveEdit, onStartEdit, thread }) {
|
|
1917
2104
|
const [replyDraft, setReplyDraft] = useState("");
|
|
1918
2105
|
const [replyError, setReplyError] = useState(null);
|
|
1919
2106
|
const [replying, setReplying] = useState(false);
|
|
1920
2107
|
const [showReply, setShowReply] = useState(false);
|
|
1921
2108
|
const [resolving, setResolving] = useState(false);
|
|
1922
2109
|
const resolved = thread.isResolved === true;
|
|
2110
|
+
const hasFocusedComment = focusedCommentId != null && thread.comments.some((comment) => comment.id === focusedCommentId);
|
|
2111
|
+
const [resolutionOverride, setResolutionOverride] = useState(null);
|
|
2112
|
+
const effectiveResolved = resolutionOverride?.base === resolved ? resolutionOverride.value : resolved;
|
|
1923
2113
|
const submitReply = useCallback(() => {
|
|
1924
2114
|
const body = replyDraft.trim();
|
|
1925
2115
|
if (!body || replying) return;
|
|
@@ -1939,62 +2129,79 @@ function GeneralCommentThreadCard({ canComment, editDraft, editError, editingCom
|
|
|
1939
2129
|
]);
|
|
1940
2130
|
const toggleResolved = useCallback(() => {
|
|
1941
2131
|
if (resolving) return;
|
|
2132
|
+
const nextResolved = !effectiveResolved;
|
|
2133
|
+
setResolutionOverride({
|
|
2134
|
+
base: resolved,
|
|
2135
|
+
value: nextResolved
|
|
2136
|
+
});
|
|
1942
2137
|
setResolving(true);
|
|
1943
|
-
onResolve(thread.id,
|
|
2138
|
+
onResolve(thread.id, nextResolved).catch((error) => {
|
|
2139
|
+
setResolutionOverride(null);
|
|
1944
2140
|
window.alert(error instanceof Error ? error.message : String(error));
|
|
1945
2141
|
}).finally(() => setResolving(false));
|
|
1946
2142
|
}, [
|
|
2143
|
+
effectiveResolved,
|
|
1947
2144
|
onResolve,
|
|
1948
2145
|
resolved,
|
|
1949
2146
|
resolving,
|
|
1950
2147
|
thread.id
|
|
1951
2148
|
]);
|
|
1952
|
-
|
|
2149
|
+
const threadContent = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2150
|
+
thread.comments.map((comment) => /* @__PURE__ */ jsx(GeneralCommentCard, {
|
|
2151
|
+
comment,
|
|
2152
|
+
editDraft,
|
|
2153
|
+
editError: editingCommentId === comment.id ? editError : null,
|
|
2154
|
+
editing: editingCommentId === comment.id,
|
|
2155
|
+
editSubmitting: editSubmitting && editingCommentId === comment.id,
|
|
2156
|
+
focused: comment.id === focusedCommentId,
|
|
2157
|
+
keymap,
|
|
2158
|
+
onCancelEdit,
|
|
2159
|
+
onChangeEditDraft,
|
|
2160
|
+
onDelete,
|
|
2161
|
+
onSaveEdit,
|
|
2162
|
+
onStartEdit
|
|
2163
|
+
}, comment.id)),
|
|
2164
|
+
thread.canReply && canComment && !effectiveResolved && !resolving ? showReply ? /* @__PURE__ */ jsx(GeneralCommentComposer, {
|
|
2165
|
+
disabled: false,
|
|
2166
|
+
draft: replyDraft,
|
|
2167
|
+
error: replyError,
|
|
2168
|
+
gitIdentity: null,
|
|
2169
|
+
keymap,
|
|
2170
|
+
onChangeDraft: setReplyDraft,
|
|
2171
|
+
onSubmit: submitReply,
|
|
2172
|
+
submitting: replying
|
|
2173
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
2174
|
+
className: "review-comment-thread-footer",
|
|
2175
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2176
|
+
className: "review-comment-action",
|
|
2177
|
+
onClick: () => setShowReply(true),
|
|
2178
|
+
type: "button",
|
|
2179
|
+
children: "Reply"
|
|
2180
|
+
})
|
|
2181
|
+
}) : null,
|
|
2182
|
+
thread.canResolve ? /* @__PURE__ */ jsx("div", {
|
|
2183
|
+
className: "review-comment-thread-footer",
|
|
2184
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2185
|
+
className: "review-comment-action",
|
|
2186
|
+
disabled: resolving,
|
|
2187
|
+
onClick: toggleResolved,
|
|
2188
|
+
type: "button",
|
|
2189
|
+
children: resolving ? "Saving" : effectiveResolved ? "Reopen" : "Resolve"
|
|
2190
|
+
})
|
|
2191
|
+
}) : null
|
|
2192
|
+
] });
|
|
2193
|
+
return effectiveResolved ? /* @__PURE__ */ jsx("section", {
|
|
1953
2194
|
className: "general-comment-thread",
|
|
1954
|
-
children:
|
|
1955
|
-
thread.comments.
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
onChangeEditDraft,
|
|
1965
|
-
onDelete,
|
|
1966
|
-
onSaveEdit,
|
|
1967
|
-
onStartEdit
|
|
1968
|
-
}, comment.id)),
|
|
1969
|
-
thread.canReply && canComment && !resolved ? showReply ? /* @__PURE__ */ jsx(GeneralCommentComposer, {
|
|
1970
|
-
disabled: false,
|
|
1971
|
-
draft: replyDraft,
|
|
1972
|
-
error: replyError,
|
|
1973
|
-
gitIdentity: null,
|
|
1974
|
-
keymap,
|
|
1975
|
-
onChangeDraft: setReplyDraft,
|
|
1976
|
-
onSubmit: submitReply,
|
|
1977
|
-
submitting: replying
|
|
1978
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
1979
|
-
className: "review-comment-thread-footer",
|
|
1980
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
1981
|
-
className: "review-comment-action",
|
|
1982
|
-
onClick: () => setShowReply(true),
|
|
1983
|
-
type: "button",
|
|
1984
|
-
children: "Reply"
|
|
1985
|
-
})
|
|
1986
|
-
}) : null,
|
|
1987
|
-
thread.canResolve ? /* @__PURE__ */ jsx("div", {
|
|
1988
|
-
className: "review-comment-thread-footer",
|
|
1989
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
1990
|
-
className: "review-comment-action",
|
|
1991
|
-
disabled: resolving,
|
|
1992
|
-
onClick: toggleResolved,
|
|
1993
|
-
type: "button",
|
|
1994
|
-
children: resolving ? "Saving" : resolved ? "Reopen" : "Resolve"
|
|
1995
|
-
})
|
|
1996
|
-
}) : null
|
|
1997
|
-
]
|
|
2195
|
+
children: /* @__PURE__ */ jsx(ResolvedThreadDisclosure, {
|
|
2196
|
+
commentCount: thread.comments.length,
|
|
2197
|
+
focused: hasFocusedComment,
|
|
2198
|
+
focusRequest: focusedCommentRequest,
|
|
2199
|
+
saving: resolving,
|
|
2200
|
+
children: threadContent
|
|
2201
|
+
})
|
|
2202
|
+
}) : /* @__PURE__ */ jsx("section", {
|
|
2203
|
+
className: "general-comment-thread",
|
|
2204
|
+
children: threadContent
|
|
1998
2205
|
});
|
|
1999
2206
|
}
|
|
2000
2207
|
function SidebarGeneralCommentList({ comments, focusedCommentId, onActivateComment }) {
|
|
@@ -2102,7 +2309,7 @@ function GeneralCommentComposer({ disabled, draft, error, gitIdentity, keymap, o
|
|
|
2102
2309
|
})
|
|
2103
2310
|
});
|
|
2104
2311
|
}
|
|
2105
|
-
function MergeRequestCommentsView({ canComment, commenting, draft, editDraft, editError, editingCommentId, editSubmitting, error, focusedCommentId, focusedCommentRequest, gitIdentity, keymap, onCancelEdit, onChangeDraft, onChangeEditDraft, onSaveEdit, onStartEdit, onSubmit, signInLabel, sourceDescription, submitting, threads }) {
|
|
2312
|
+
function MergeRequestCommentsView({ canComment, commenting, draft, editDraft, editError, editingCommentId, editSubmitting, error, focusedCommentId, focusedCommentRequest, gitIdentity, keymap, onCancelEdit, onChangeDraft, onChangeEditDraft, onResolveDiscussion, onSaveEdit, onStartEdit, onSubmit, signInLabel, sourceDescription, submitting, threads }) {
|
|
2106
2313
|
const commentsRef = useRef(null);
|
|
2107
2314
|
useEffect(() => {
|
|
2108
2315
|
if (focusedCommentId == null) return;
|
|
@@ -2128,6 +2335,7 @@ function MergeRequestCommentsView({ canComment, commenting, draft, editDraft, ed
|
|
|
2128
2335
|
editingCommentId,
|
|
2129
2336
|
editSubmitting,
|
|
2130
2337
|
focusedCommentId,
|
|
2338
|
+
focusedCommentRequest,
|
|
2131
2339
|
keymap,
|
|
2132
2340
|
onCancelEdit,
|
|
2133
2341
|
onChangeEditDraft,
|
|
@@ -2137,7 +2345,7 @@ function MergeRequestCommentsView({ canComment, commenting, draft, editDraft, ed
|
|
|
2137
2345
|
});
|
|
2138
2346
|
},
|
|
2139
2347
|
onReply: (threadId, body) => commenting?.onReplyGeneralComment(threadId, body) ?? Promise.reject(/* @__PURE__ */ new Error("Replying is unavailable.")),
|
|
2140
|
-
onResolve: (threadId, resolved) => commenting?.onResolveDiscussion(threadId, resolved) ?? Promise.reject(/* @__PURE__ */ new Error("Resolving is unavailable.")),
|
|
2348
|
+
onResolve: (threadId, resolved) => onResolveDiscussion?.(threadId, resolved) ?? commenting?.onResolveDiscussion(threadId, resolved) ?? Promise.reject(/* @__PURE__ */ new Error("Resolving is unavailable.")),
|
|
2141
2349
|
onSaveEdit,
|
|
2142
2350
|
onStartEdit,
|
|
2143
2351
|
thread
|
|
@@ -2283,23 +2491,6 @@ const getReviewCommentsFromState = (state) => (state.reviewComments ?? []).flatM
|
|
|
2283
2491
|
});
|
|
2284
2492
|
const shouldDiscardReviewCommentOnEscape = (body, confirmDiscard = window.confirm) => body.trim().length === 0 || confirmDiscard("Discard this review comment?");
|
|
2285
2493
|
//#endregion
|
|
2286
|
-
//#region app/components/useCopiedState.ts
|
|
2287
|
-
function useCopiedState(timeoutMs) {
|
|
2288
|
-
const [copied, setCopied] = useState(false);
|
|
2289
|
-
const copiedTimerRef = useRef(null);
|
|
2290
|
-
useEffect(() => () => {
|
|
2291
|
-
if (copiedTimerRef.current != null) window.clearTimeout(copiedTimerRef.current);
|
|
2292
|
-
}, []);
|
|
2293
|
-
return [copied, useCallback(() => {
|
|
2294
|
-
setCopied(true);
|
|
2295
|
-
if (copiedTimerRef.current != null) window.clearTimeout(copiedTimerRef.current);
|
|
2296
|
-
copiedTimerRef.current = window.setTimeout(() => {
|
|
2297
|
-
setCopied(false);
|
|
2298
|
-
copiedTimerRef.current = null;
|
|
2299
|
-
}, timeoutMs);
|
|
2300
|
-
}, [timeoutMs])];
|
|
2301
|
-
}
|
|
2302
|
-
//#endregion
|
|
2303
2494
|
//#region app/components/Panels.tsx
|
|
2304
2495
|
const getPullRequestReviewActionStatus = (reviewStatus, event) => event === "APPROVE" ? reviewStatus?.approve : event === "COMMENT" ? reviewStatus?.comment : reviewStatus?.requestChanges;
|
|
2305
2496
|
const isPullRequestReviewActionDisabled = (reviewStatus, event) => getPullRequestReviewActionStatus(reviewStatus, event)?.disabled === true;
|
|
@@ -3347,6 +3538,7 @@ setCustomExtension("cts", "typescript");
|
|
|
3347
3538
|
setCustomExtension("mts", "typescript");
|
|
3348
3539
|
const statusLabel = {
|
|
3349
3540
|
added: "Added",
|
|
3541
|
+
conflicted: "Conflicted",
|
|
3350
3542
|
deleted: "Deleted",
|
|
3351
3543
|
modified: "Modified",
|
|
3352
3544
|
renamed: "Renamed",
|
|
@@ -3479,6 +3671,20 @@ const codeViewUnsafeCSS = `
|
|
|
3479
3671
|
cursor: progress;
|
|
3480
3672
|
}
|
|
3481
3673
|
|
|
3674
|
+
:host([data-codiff-definition-mode]) [data-codiff-identifier] {
|
|
3675
|
+
cursor: pointer;
|
|
3676
|
+
text-decoration-color: color-mix(in srgb, currentColor 38%, transparent);
|
|
3677
|
+
text-decoration-line: underline;
|
|
3678
|
+
text-decoration-style: dotted;
|
|
3679
|
+
text-decoration-thickness: 1px;
|
|
3680
|
+
text-underline-offset: 3px;
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
:host([data-codiff-definition-mode]) [data-codiff-identifier]:hover {
|
|
3684
|
+
text-decoration-color: currentColor;
|
|
3685
|
+
text-decoration-style: solid;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3482
3688
|
.codiff-search-mark {
|
|
3483
3689
|
background: var(--diffs-find-highlight-bg, rgb(255 216 92 / 0.65));
|
|
3484
3690
|
border-radius: 3px;
|
|
@@ -3505,8 +3711,179 @@ const codeViewUnsafeCSS = `
|
|
|
3505
3711
|
[data-selected-line] [data-gutter-utility-slot] {
|
|
3506
3712
|
display: none;
|
|
3507
3713
|
}
|
|
3714
|
+
|
|
3715
|
+
/* The imperative API wraps annotations in a node that resets white-space, but
|
|
3716
|
+
the React API slots them in bare, so they inherit "pre" from the shadow
|
|
3717
|
+
<pre> and prose never wraps. */
|
|
3718
|
+
[data-annotation-content] slot::slotted(*) {
|
|
3719
|
+
white-space: normal;
|
|
3720
|
+
}
|
|
3508
3721
|
`;
|
|
3509
3722
|
//#endregion
|
|
3723
|
+
//#region lib/identifier-navigation.ts
|
|
3724
|
+
const identifierPattern = /[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*/gu;
|
|
3725
|
+
const exactIdentifierPattern = /^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u;
|
|
3726
|
+
const identifierContainedPattern = /[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*/u;
|
|
3727
|
+
const commonKeywords = /* @__PURE__ */ new Set([
|
|
3728
|
+
"abstract",
|
|
3729
|
+
"as",
|
|
3730
|
+
"async",
|
|
3731
|
+
"await",
|
|
3732
|
+
"break",
|
|
3733
|
+
"case",
|
|
3734
|
+
"catch",
|
|
3735
|
+
"class",
|
|
3736
|
+
"const",
|
|
3737
|
+
"continue",
|
|
3738
|
+
"def",
|
|
3739
|
+
"default",
|
|
3740
|
+
"defer",
|
|
3741
|
+
"do",
|
|
3742
|
+
"else",
|
|
3743
|
+
"enum",
|
|
3744
|
+
"export",
|
|
3745
|
+
"extends",
|
|
3746
|
+
"false",
|
|
3747
|
+
"finally",
|
|
3748
|
+
"fn",
|
|
3749
|
+
"for",
|
|
3750
|
+
"from",
|
|
3751
|
+
"func",
|
|
3752
|
+
"function",
|
|
3753
|
+
"if",
|
|
3754
|
+
"implements",
|
|
3755
|
+
"import",
|
|
3756
|
+
"in",
|
|
3757
|
+
"interface",
|
|
3758
|
+
"let",
|
|
3759
|
+
"match",
|
|
3760
|
+
"mod",
|
|
3761
|
+
"namespace",
|
|
3762
|
+
"new",
|
|
3763
|
+
"nil",
|
|
3764
|
+
"none",
|
|
3765
|
+
"null",
|
|
3766
|
+
"package",
|
|
3767
|
+
"pass",
|
|
3768
|
+
"private",
|
|
3769
|
+
"protected",
|
|
3770
|
+
"pub",
|
|
3771
|
+
"public",
|
|
3772
|
+
"return",
|
|
3773
|
+
"self",
|
|
3774
|
+
"static",
|
|
3775
|
+
"struct",
|
|
3776
|
+
"super",
|
|
3777
|
+
"switch",
|
|
3778
|
+
"this",
|
|
3779
|
+
"throw",
|
|
3780
|
+
"throws",
|
|
3781
|
+
"trait",
|
|
3782
|
+
"true",
|
|
3783
|
+
"try",
|
|
3784
|
+
"type",
|
|
3785
|
+
"typeof",
|
|
3786
|
+
"undefined",
|
|
3787
|
+
"use",
|
|
3788
|
+
"var",
|
|
3789
|
+
"void",
|
|
3790
|
+
"while",
|
|
3791
|
+
"with",
|
|
3792
|
+
"yield"
|
|
3793
|
+
]);
|
|
3794
|
+
const identifierNavigationAttribute = "data-codiff-identifier";
|
|
3795
|
+
const definitionModifierAttribute = "data-codiff-definition-mode";
|
|
3796
|
+
const nonCodeScopePattern = /(^|\s)(comment|string)([.\s]|$)/;
|
|
3797
|
+
const nonCodeTokenColors = new Set([dunkel_default, licht_default].flatMap((theme) => theme.tokenColors.flatMap((token) => {
|
|
3798
|
+
const scopes = Array.isArray(token.scope) ? token.scope : [token.scope];
|
|
3799
|
+
const foreground = token.settings.foreground?.toLowerCase();
|
|
3800
|
+
return foreground && scopes.some((scope) => typeof scope === "string" && nonCodeScopePattern.test(scope)) ? [foreground] : [];
|
|
3801
|
+
})));
|
|
3802
|
+
const isNavigableIdentifierToken = (text) => {
|
|
3803
|
+
return exactIdentifierPattern.test(text) && !commonKeywords.has(text.toLowerCase());
|
|
3804
|
+
};
|
|
3805
|
+
const isNonCodeSyntaxToken = (element) => {
|
|
3806
|
+
const style = element?.getAttribute("style")?.toLowerCase() ?? "";
|
|
3807
|
+
return [...nonCodeTokenColors].some((color) => style.includes(color));
|
|
3808
|
+
};
|
|
3809
|
+
const clearIdentifierNavigationWrappers = (root) => {
|
|
3810
|
+
for (const wrapper of Array.from(root.querySelectorAll(`[${identifierNavigationAttribute}]`))) {
|
|
3811
|
+
const parent = wrapper.parentElement;
|
|
3812
|
+
wrapper.replaceWith(document.createTextNode(wrapper.textContent ?? ""));
|
|
3813
|
+
parent?.normalize();
|
|
3814
|
+
}
|
|
3815
|
+
};
|
|
3816
|
+
const wrapIdentifierNavigationText = (root) => {
|
|
3817
|
+
const textNodes = [];
|
|
3818
|
+
for (const line of Array.from(root.querySelectorAll("[data-line]"))) {
|
|
3819
|
+
const walker = document.createTreeWalker(line, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
|
|
3820
|
+
const parent = node.parentElement;
|
|
3821
|
+
return node.textContent && identifierContainedPattern.test(node.textContent) && !parent?.closest(`[${identifierNavigationAttribute}], mark.codiff-search-mark`) && !isNonCodeSyntaxToken(parent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
3822
|
+
} });
|
|
3823
|
+
let node = walker.nextNode();
|
|
3824
|
+
while (node) {
|
|
3825
|
+
textNodes.push(node);
|
|
3826
|
+
node = walker.nextNode();
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
for (const textNode of textNodes) {
|
|
3830
|
+
const text = textNode.textContent ?? "";
|
|
3831
|
+
const fragment = document.createDocumentFragment();
|
|
3832
|
+
let offset = 0;
|
|
3833
|
+
let wrapped = false;
|
|
3834
|
+
for (const match of text.matchAll(identifierPattern)) {
|
|
3835
|
+
const identifier = match[0];
|
|
3836
|
+
if (!isNavigableIdentifierToken(identifier)) continue;
|
|
3837
|
+
if (match.index > offset) fragment.append(document.createTextNode(text.slice(offset, match.index)));
|
|
3838
|
+
const wrapper = document.createElement("span");
|
|
3839
|
+
wrapper.setAttribute(identifierNavigationAttribute, "");
|
|
3840
|
+
wrapper.textContent = identifier;
|
|
3841
|
+
fragment.append(wrapper);
|
|
3842
|
+
offset = match.index + identifier.length;
|
|
3843
|
+
wrapped = true;
|
|
3844
|
+
}
|
|
3845
|
+
if (!wrapped) continue;
|
|
3846
|
+
if (offset < text.length) fragment.append(document.createTextNode(text.slice(offset)));
|
|
3847
|
+
textNode.replaceWith(fragment);
|
|
3848
|
+
}
|
|
3849
|
+
};
|
|
3850
|
+
const applyIdentifierNavigationState = (renderedItems, active) => {
|
|
3851
|
+
for (const { element } of renderedItems) {
|
|
3852
|
+
element.toggleAttribute(definitionModifierAttribute, active);
|
|
3853
|
+
const root = element.shadowRoot;
|
|
3854
|
+
if (!root) continue;
|
|
3855
|
+
clearIdentifierNavigationWrappers(root);
|
|
3856
|
+
if (active) wrapIdentifierNavigationText(root);
|
|
3857
|
+
}
|
|
3858
|
+
};
|
|
3859
|
+
const getIdentifierAtOffset = (text, offset) => {
|
|
3860
|
+
for (const match of text.matchAll(identifierPattern)) {
|
|
3861
|
+
const start = match.index;
|
|
3862
|
+
const end = start + match[0].length;
|
|
3863
|
+
if (offset >= start && offset < end) return {
|
|
3864
|
+
identifier: match[0],
|
|
3865
|
+
start
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3868
|
+
return null;
|
|
3869
|
+
};
|
|
3870
|
+
const getIdentifierFromPointerEvent = (event, lineElement) => {
|
|
3871
|
+
const caretRange = document.caretRangeFromPoint?.(event.clientX, event.clientY);
|
|
3872
|
+
if (caretRange && lineElement.contains(caretRange.startContainer)) {
|
|
3873
|
+
const prefix = document.createRange();
|
|
3874
|
+
prefix.selectNodeContents(lineElement);
|
|
3875
|
+
prefix.setEnd(caretRange.startContainer, caretRange.startOffset);
|
|
3876
|
+
const match = getIdentifierAtOffset(lineElement.textContent || "", prefix.toString().length);
|
|
3877
|
+
if (match) return match.identifier;
|
|
3878
|
+
}
|
|
3879
|
+
const target = event.target;
|
|
3880
|
+
if (target && lineElement.contains(target)) {
|
|
3881
|
+
const matches = [...(target.textContent || "").matchAll(identifierPattern)];
|
|
3882
|
+
if (matches.length === 1) return matches[0][0];
|
|
3883
|
+
}
|
|
3884
|
+
return null;
|
|
3885
|
+
};
|
|
3886
|
+
//#endregion
|
|
3510
3887
|
//#region lib/item-version.ts
|
|
3511
3888
|
const getItemVersion = (value) => {
|
|
3512
3889
|
let hash = 0;
|
|
@@ -3522,6 +3899,8 @@ const isNativeInputTarget = (target) => {
|
|
|
3522
3899
|
const candidate = target;
|
|
3523
3900
|
return candidate?.closest?.("input, select, textarea") != null || candidate?.isContentEditable === true;
|
|
3524
3901
|
};
|
|
3902
|
+
const isMacPlatform = (platform = navigator.platform) => platform.toLowerCase().includes("mac");
|
|
3903
|
+
const isPrimaryModifier = (event, platform = navigator.platform) => isMacPlatform(platform) ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
|
3525
3904
|
//#endregion
|
|
3526
3905
|
//#region lib/review-identity.ts
|
|
3527
3906
|
const getFileReviewIdentity = (file) => ({
|
|
@@ -3601,6 +3980,101 @@ const applySearchHighlights = (renderedItems, query, activeMatch) => {
|
|
|
3601
3980
|
}
|
|
3602
3981
|
};
|
|
3603
3982
|
//#endregion
|
|
3983
|
+
//#region lib/source.ts
|
|
3984
|
+
const rangeLabel = (source) => `${source.base}${source.symmetric ? "..." : ".."}${source.head}`;
|
|
3985
|
+
const getSourceKey = (source) => source.type === "commit" ? `commit:${source.ref}` : source.type === "branch-diff" ? `branch-diff:${source.ref}:${source.baseRef}:${source.headRef}` : source.type === "branch-working-tree" ? `branch-working-tree:${source.ref}:${source.baseRef}:${source.headRef}` : source.type === "branch" ? `branch:${source.ref}` : source.type === "range" ? `range:${rangeLabel(source)}` : source.type === "pull-request" ? `pull-request:${source.provider ?? ""}:${source.host ?? ""}:${source.projectPath ?? `${source.owner ?? ""}/${source.repo ?? ""}`}#${source.number ?? source.url}` : "working-tree";
|
|
3986
|
+
const getShortRef = (ref) => ref.slice(0, 7);
|
|
3987
|
+
const getSourceLabel = (source) => source.type === "commit" ? getShortRef(source.ref) : source.type === "branch" || source.type === "branch-diff" ? `Branch vs ${source.ref}` : source.type === "branch-working-tree" ? `Local + branch vs ${source.ref}` : source.type === "range" ? rangeLabel(source) : source.type === "pull-request" ? source.number ? `${source.provider === "gitlab" ? "MR" : "PR"} #${source.number}` : source.provider === "gitlab" ? "Merge request" : "Pull request" : "Uncommitted";
|
|
3988
|
+
//#endregion
|
|
3989
|
+
//#region app/components/DefinitionPopover.tsx
|
|
3990
|
+
function DefinitionPopover({ anchor, getDestination, identifier, onClose, onOpen, result }) {
|
|
3991
|
+
const popoverRef = useRef(null);
|
|
3992
|
+
useEffect(() => {
|
|
3993
|
+
const closeOnEscape = (event) => {
|
|
3994
|
+
if (event.key === "Escape") onClose();
|
|
3995
|
+
};
|
|
3996
|
+
const closeOutside = (event) => {
|
|
3997
|
+
const target = event.target;
|
|
3998
|
+
if (target && !popoverRef.current?.contains(target)) onClose();
|
|
3999
|
+
};
|
|
4000
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
4001
|
+
document.addEventListener("pointerdown", closeOutside);
|
|
4002
|
+
return () => {
|
|
4003
|
+
document.removeEventListener("keydown", closeOnEscape);
|
|
4004
|
+
document.removeEventListener("pointerdown", closeOutside);
|
|
4005
|
+
};
|
|
4006
|
+
}, [onClose]);
|
|
4007
|
+
const left = Math.min(anchor.x, Math.max(12, window.innerWidth - 432));
|
|
4008
|
+
const top = Math.min(anchor.y + 10, Math.max(12, window.innerHeight - 320));
|
|
4009
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
4010
|
+
"aria-label": `Definitions for ${identifier}`,
|
|
4011
|
+
className: "definition-popover",
|
|
4012
|
+
ref: popoverRef,
|
|
4013
|
+
role: "dialog",
|
|
4014
|
+
style: {
|
|
4015
|
+
left,
|
|
4016
|
+
top
|
|
4017
|
+
},
|
|
4018
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4019
|
+
className: "definition-popover-header",
|
|
4020
|
+
children: [/* @__PURE__ */ jsxs("span", { children: ["Definition of ", /* @__PURE__ */ jsx("code", { children: identifier })] }), /* @__PURE__ */ jsx("button", {
|
|
4021
|
+
"aria-label": "Close definition results",
|
|
4022
|
+
onClick: onClose,
|
|
4023
|
+
type: "button",
|
|
4024
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: 14 })
|
|
4025
|
+
})]
|
|
4026
|
+
}), !result ? /* @__PURE__ */ jsx("div", {
|
|
4027
|
+
className: "definition-popover-message",
|
|
4028
|
+
children: "Searching…"
|
|
4029
|
+
}) : result.status === "unavailable" ? /* @__PURE__ */ jsx("div", {
|
|
4030
|
+
className: "definition-popover-message",
|
|
4031
|
+
children: result.reason
|
|
4032
|
+
}) : result.candidates.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
4033
|
+
className: "definition-popover-message",
|
|
4034
|
+
children: "No likely definitions found."
|
|
4035
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
4036
|
+
className: "definition-popover-results",
|
|
4037
|
+
children: result.candidates.map((candidate) => {
|
|
4038
|
+
const destination = getDestination(candidate);
|
|
4039
|
+
const destinationLabel = destination === "diff" ? "Jump within diff" : destination === "editor" ? "Open in editor" : "Unavailable outside this historical diff";
|
|
4040
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
4041
|
+
className: "definition-popover-result",
|
|
4042
|
+
disabled: destination === "unavailable",
|
|
4043
|
+
onClick: () => onOpen(candidate),
|
|
4044
|
+
type: "button",
|
|
4045
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
4046
|
+
className: "definition-popover-location",
|
|
4047
|
+
children: [
|
|
4048
|
+
candidate.path,
|
|
4049
|
+
":",
|
|
4050
|
+
candidate.lineNumber,
|
|
4051
|
+
/* @__PURE__ */ jsxs("span", {
|
|
4052
|
+
className: "definition-popover-meta",
|
|
4053
|
+
children: [/* @__PURE__ */ jsx("span", { children: candidate.kind }), /* @__PURE__ */ jsx("span", {
|
|
4054
|
+
"aria-label": destinationLabel,
|
|
4055
|
+
className: "definition-popover-destination",
|
|
4056
|
+
role: "img",
|
|
4057
|
+
title: destinationLabel,
|
|
4058
|
+
children: destination === "diff" ? /* @__PURE__ */ jsx(CrosshairSimpleIcon, {
|
|
4059
|
+
"aria-hidden": "true",
|
|
4060
|
+
size: 13
|
|
4061
|
+
}) : destination === "editor" ? /* @__PURE__ */ jsx(ArrowSquareOutIcon, {
|
|
4062
|
+
"aria-hidden": "true",
|
|
4063
|
+
size: 13
|
|
4064
|
+
}) : /* @__PURE__ */ jsx(ProhibitIcon, {
|
|
4065
|
+
"aria-hidden": "true",
|
|
4066
|
+
size: 13
|
|
4067
|
+
})
|
|
4068
|
+
})]
|
|
4069
|
+
})
|
|
4070
|
+
]
|
|
4071
|
+
}), /* @__PURE__ */ jsx("code", { children: candidate.line })]
|
|
4072
|
+
}, `${candidate.path}:${candidate.lineNumber}`);
|
|
4073
|
+
})
|
|
4074
|
+
})]
|
|
4075
|
+
}), document.body);
|
|
4076
|
+
}
|
|
4077
|
+
//#endregion
|
|
3604
4078
|
//#region app/components/MarkdownDocumentEditor.tsx
|
|
3605
4079
|
const persistenceAdapter = { save: ({ content, document }) => window.codiff.saveMarkdownDocument({
|
|
3606
4080
|
baseVersion: document.version,
|
|
@@ -3717,12 +4191,6 @@ const RepositoryMarkdownEditor = forwardRef(function RepositoryMarkdownEditor({
|
|
|
3717
4191
|
});
|
|
3718
4192
|
});
|
|
3719
4193
|
//#endregion
|
|
3720
|
-
//#region lib/source.ts
|
|
3721
|
-
const rangeLabel = (source) => `${source.base}${source.symmetric ? "..." : ".."}${source.head}`;
|
|
3722
|
-
const getSourceKey = (source) => source.type === "commit" ? `commit:${source.ref}` : source.type === "branch-diff" ? `branch-diff:${source.ref}:${source.baseRef}:${source.headRef}` : source.type === "branch-working-tree" ? `branch-working-tree:${source.ref}:${source.baseRef}:${source.headRef}` : source.type === "branch" ? `branch:${source.ref}` : source.type === "range" ? `range:${rangeLabel(source)}` : source.type === "pull-request" ? `pull-request:${source.provider ?? ""}:${source.host ?? ""}:${source.projectPath ?? `${source.owner ?? ""}/${source.repo ?? ""}`}#${source.number ?? source.url}` : "working-tree";
|
|
3723
|
-
const getShortRef = (ref) => ref.slice(0, 7);
|
|
3724
|
-
const getSourceLabel = (source) => source.type === "commit" ? getShortRef(source.ref) : source.type === "branch" || source.type === "branch-diff" ? `Branch vs ${source.ref}` : source.type === "branch-working-tree" ? `Local + branch vs ${source.ref}` : source.type === "range" ? rangeLabel(source) : source.type === "pull-request" ? source.number ? `${source.provider === "gitlab" ? "MR" : "PR"} #${source.number}` : source.provider === "gitlab" ? "Merge request" : "Pull request" : "Uncommitted";
|
|
3725
|
-
//#endregion
|
|
3726
4194
|
//#region lib/narrative-walkthrough.ts
|
|
3727
4195
|
const walkthroughFileName = (path) => path.split("/").pop() ?? path;
|
|
3728
4196
|
const uniquePaths = (paths) => {
|
|
@@ -4145,7 +4613,8 @@ const chapterIcons = {
|
|
|
4145
4613
|
wrench: WrenchIcon
|
|
4146
4614
|
};
|
|
4147
4615
|
function ChapterIcon({ icon, size = 13 }) {
|
|
4148
|
-
|
|
4616
|
+
const Icon = chapterIcons[icon] ?? chapterIcons.path;
|
|
4617
|
+
return /* @__PURE__ */ jsx(Icon, { size });
|
|
4149
4618
|
}
|
|
4150
4619
|
function ImportancePill({ importance }) {
|
|
4151
4620
|
return /* @__PURE__ */ jsx("span", {
|
|
@@ -4430,13 +4899,14 @@ function WalkthroughProgress({ phase, responseLabelIndex, stageRevision }) {
|
|
|
4430
4899
|
}, [stageRevision]);
|
|
4431
4900
|
const elapsedSeconds = timerState.stageRevision === stageRevision ? timerState.elapsedSeconds : 0;
|
|
4432
4901
|
const showTimer = elapsedSeconds >= TIMER_THRESHOLD_SECONDS;
|
|
4902
|
+
const label = phase === "agent-generation" ? "Analyzing changes…" : phase === "response-received" ? walkthroughResponseLabels[Math.abs(responseLabelIndex) % walkthroughResponseLabels.length] : "Generating walkthrough…";
|
|
4433
4903
|
return /* @__PURE__ */ jsxs("span", {
|
|
4434
4904
|
"aria-live": "polite",
|
|
4435
4905
|
className: "walkthrough-progress",
|
|
4436
4906
|
role: "status",
|
|
4437
4907
|
children: [/* @__PURE__ */ jsx("span", {
|
|
4438
4908
|
className: "walkthrough-progress-label",
|
|
4439
|
-
children:
|
|
4909
|
+
children: label
|
|
4440
4910
|
}), /* @__PURE__ */ jsx("span", {
|
|
4441
4911
|
"aria-hidden": !showTimer,
|
|
4442
4912
|
className: `walkthrough-progress-timer${showTimer ? " visible" : ""}`,
|
|
@@ -5476,6 +5946,9 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5476
5946
|
flushDraft,
|
|
5477
5947
|
onCommentBlur
|
|
5478
5948
|
]);
|
|
5949
|
+
const handleAddComment = useCallback(() => {
|
|
5950
|
+
document.activeElement?.blur();
|
|
5951
|
+
}, []);
|
|
5479
5952
|
const handleFocus = useCallback(() => {
|
|
5480
5953
|
onCommentFocus(draftComment);
|
|
5481
5954
|
}, [draftComment, onCommentFocus]);
|
|
@@ -5491,13 +5964,21 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5491
5964
|
]);
|
|
5492
5965
|
const handleKeyDown = useCallback((event) => {
|
|
5493
5966
|
if (matchesShortcut(event, keymap, "submitComment")) {
|
|
5494
|
-
if (supportsReviewCommentActions
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5967
|
+
if (supportsReviewCommentActions) {
|
|
5968
|
+
if (commentCanSubmit) {
|
|
5969
|
+
event.preventDefault();
|
|
5970
|
+
event.stopPropagation();
|
|
5971
|
+
handleSubmitComment();
|
|
5972
|
+
}
|
|
5498
5973
|
return;
|
|
5499
5974
|
}
|
|
5500
|
-
|
|
5975
|
+
event.preventDefault();
|
|
5976
|
+
event.stopPropagation();
|
|
5977
|
+
handleAddComment();
|
|
5978
|
+
return;
|
|
5979
|
+
}
|
|
5980
|
+
if (matchesShortcut(event, keymap, "askAgent")) {
|
|
5981
|
+
if (canAskCodex) {
|
|
5501
5982
|
event.preventDefault();
|
|
5502
5983
|
event.stopPropagation();
|
|
5503
5984
|
handleAskCodex();
|
|
@@ -5515,6 +5996,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5515
5996
|
comment.id,
|
|
5516
5997
|
comment.isReadOnly,
|
|
5517
5998
|
draft,
|
|
5999
|
+
handleAddComment,
|
|
5518
6000
|
handleAskCodex,
|
|
5519
6001
|
handleSubmitComment,
|
|
5520
6002
|
keymap,
|
|
@@ -5570,7 +6052,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5570
6052
|
className: "review-comment-action",
|
|
5571
6053
|
disabled: !canAskCodex,
|
|
5572
6054
|
onClick: handleAskCodex,
|
|
5573
|
-
title: canAskCodex ? `Ask ${agentLabel}` : `Write a note before asking ${agentLabel}`,
|
|
6055
|
+
title: canAskCodex ? `Ask ${agentLabel} (${getShortcutLabel(keymap, "askAgent")})` : `Write a note before asking ${agentLabel}`,
|
|
5574
6056
|
type: "button",
|
|
5575
6057
|
children: [/* @__PURE__ */ jsx("img", {
|
|
5576
6058
|
alt: "",
|
|
@@ -5732,7 +6214,7 @@ const groupReviewCommentsByThread = (comments) => {
|
|
|
5732
6214
|
};
|
|
5733
6215
|
const noopResolveThread = () => {};
|
|
5734
6216
|
const noopLayoutReady = () => {};
|
|
5735
|
-
function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
6217
|
+
function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusCommentRequest, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
5736
6218
|
const [resolveState, setResolveState] = useState({
|
|
5737
6219
|
error: null,
|
|
5738
6220
|
submitting: false,
|
|
@@ -5741,10 +6223,13 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5741
6223
|
const lastComment = comments.at(-1);
|
|
5742
6224
|
const threadId = lastComment?.threadId;
|
|
5743
6225
|
const threadResolved = comments.some((comment) => comment.isThreadResolved === true);
|
|
6226
|
+
const hasFocusedComment = focusCommentId != null && comments.some((comment) => comment.id === focusCommentId);
|
|
6227
|
+
const [resolutionOverride, setResolutionOverride] = useState(null);
|
|
6228
|
+
const effectiveThreadResolved = resolutionOverride?.base === threadResolved ? resolutionOverride.value : threadResolved;
|
|
6229
|
+
const resolving = resolveState.threadId === threadId && resolveState.submitting;
|
|
5744
6230
|
const canResolveThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.canResolveThread === true);
|
|
5745
|
-
const canReplyToThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.isReadOnly) && !comments.some((comment) => !comment.isReadOnly) && !comments.some((comment) => comment.canReplyThread === false) && !
|
|
6231
|
+
const canReplyToThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.isReadOnly) && !comments.some((comment) => !comment.isReadOnly) && !comments.some((comment) => comment.canReplyThread === false) && !effectiveThreadResolved && !resolving;
|
|
5746
6232
|
const hasThreadActions = canReplyToThread || canResolveThread;
|
|
5747
|
-
const resolving = resolveState.threadId === threadId && resolveState.submitting;
|
|
5748
6233
|
const resolveError = resolveState.threadId === threadId ? resolveState.error : null;
|
|
5749
6234
|
const handleReply = useCallback(() => {
|
|
5750
6235
|
if (!threadId || !lastComment) return;
|
|
@@ -5756,16 +6241,22 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5756
6241
|
]);
|
|
5757
6242
|
const handleResolve = useCallback(() => {
|
|
5758
6243
|
if (!threadId || resolving) return;
|
|
6244
|
+
const nextResolved = !effectiveThreadResolved;
|
|
6245
|
+
setResolutionOverride({
|
|
6246
|
+
base: threadResolved,
|
|
6247
|
+
value: nextResolved
|
|
6248
|
+
});
|
|
5759
6249
|
setResolveState({
|
|
5760
6250
|
error: null,
|
|
5761
6251
|
submitting: true,
|
|
5762
6252
|
threadId
|
|
5763
6253
|
});
|
|
5764
|
-
Promise.resolve(onResolveThread(threadId,
|
|
6254
|
+
Promise.resolve(onResolveThread(threadId, nextResolved)).then(() => setResolveState({
|
|
5765
6255
|
error: null,
|
|
5766
6256
|
submitting: false,
|
|
5767
6257
|
threadId
|
|
5768
6258
|
})).catch((error) => {
|
|
6259
|
+
setResolutionOverride(null);
|
|
5769
6260
|
setResolveState({
|
|
5770
6261
|
error: error instanceof Error ? error.message : String(error),
|
|
5771
6262
|
submitting: false,
|
|
@@ -5773,54 +6264,64 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5773
6264
|
});
|
|
5774
6265
|
});
|
|
5775
6266
|
}, [
|
|
6267
|
+
effectiveThreadResolved,
|
|
5776
6268
|
onResolveThread,
|
|
5777
6269
|
resolving,
|
|
5778
6270
|
threadId,
|
|
5779
6271
|
threadResolved
|
|
5780
6272
|
]);
|
|
5781
|
-
|
|
6273
|
+
const threadContent = /* @__PURE__ */ jsxs(Fragment$1, { children: [comments.map((comment) => {
|
|
6274
|
+
const displayName = comment.author ? getReviewAuthorDisplayName(comment.author) : getGitIdentityDisplayName(identity);
|
|
6275
|
+
return /* @__PURE__ */ jsx(ReviewCommentEditor, {
|
|
6276
|
+
agentId,
|
|
6277
|
+
agentLabel,
|
|
6278
|
+
comment,
|
|
6279
|
+
displayName,
|
|
6280
|
+
focusCommentId,
|
|
6281
|
+
focusEditorRef,
|
|
6282
|
+
identity,
|
|
6283
|
+
keymap,
|
|
6284
|
+
onAskCodex,
|
|
6285
|
+
onCommentBlur,
|
|
6286
|
+
onCommentDraftChange,
|
|
6287
|
+
onCommentFocus,
|
|
6288
|
+
onDeleteComment,
|
|
6289
|
+
onSaveCommentEdit,
|
|
6290
|
+
onSubmitComment,
|
|
6291
|
+
onUpdateComment,
|
|
6292
|
+
supportsReviewCommentActions
|
|
6293
|
+
}, comment.id);
|
|
6294
|
+
}), hasThreadActions ? /* @__PURE__ */ jsxs("div", {
|
|
6295
|
+
className: "review-comment-thread-footer",
|
|
6296
|
+
children: [resolveError ? /* @__PURE__ */ jsx("span", {
|
|
6297
|
+
className: "review-comment-thread-error",
|
|
6298
|
+
children: resolveError
|
|
6299
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
6300
|
+
className: "review-comment-thread-actions",
|
|
6301
|
+
children: [canReplyToThread ? /* @__PURE__ */ jsx("button", {
|
|
6302
|
+
className: "review-comment-action",
|
|
6303
|
+
onClick: handleReply,
|
|
6304
|
+
type: "button",
|
|
6305
|
+
children: "Reply"
|
|
6306
|
+
}) : null, canResolveThread ? /* @__PURE__ */ jsx("button", {
|
|
6307
|
+
className: "review-comment-action",
|
|
6308
|
+
disabled: resolving,
|
|
6309
|
+
onClick: handleResolve,
|
|
6310
|
+
type: "button",
|
|
6311
|
+
children: resolving ? "Saving" : effectiveThreadResolved ? "Reopen" : "Resolve"
|
|
6312
|
+
}) : null]
|
|
6313
|
+
})]
|
|
6314
|
+
}) : null] });
|
|
6315
|
+
return effectiveThreadResolved ? /* @__PURE__ */ jsx(ResolvedThreadDisclosure, {
|
|
6316
|
+
commentCount: comments.length,
|
|
6317
|
+
focused: hasFocusedComment,
|
|
6318
|
+
focusRequest: focusCommentRequest,
|
|
6319
|
+
initialExpanded: Boolean(resolveError),
|
|
6320
|
+
saving: resolving,
|
|
6321
|
+
children: threadContent
|
|
6322
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
5782
6323
|
className: "review-comment-thread-group",
|
|
5783
|
-
children:
|
|
5784
|
-
return /* @__PURE__ */ jsx(ReviewCommentEditor, {
|
|
5785
|
-
agentId,
|
|
5786
|
-
agentLabel,
|
|
5787
|
-
comment,
|
|
5788
|
-
displayName: comment.author ? getReviewAuthorDisplayName(comment.author) : getGitIdentityDisplayName(identity),
|
|
5789
|
-
focusCommentId,
|
|
5790
|
-
focusEditorRef,
|
|
5791
|
-
identity,
|
|
5792
|
-
keymap,
|
|
5793
|
-
onAskCodex,
|
|
5794
|
-
onCommentBlur,
|
|
5795
|
-
onCommentDraftChange,
|
|
5796
|
-
onCommentFocus,
|
|
5797
|
-
onDeleteComment,
|
|
5798
|
-
onSaveCommentEdit,
|
|
5799
|
-
onSubmitComment,
|
|
5800
|
-
onUpdateComment,
|
|
5801
|
-
supportsReviewCommentActions
|
|
5802
|
-
}, comment.id);
|
|
5803
|
-
}), hasThreadActions ? /* @__PURE__ */ jsxs("div", {
|
|
5804
|
-
className: "review-comment-thread-footer",
|
|
5805
|
-
children: [resolveError ? /* @__PURE__ */ jsx("span", {
|
|
5806
|
-
className: "review-comment-thread-error",
|
|
5807
|
-
children: resolveError
|
|
5808
|
-
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
5809
|
-
className: "review-comment-thread-actions",
|
|
5810
|
-
children: [canReplyToThread ? /* @__PURE__ */ jsx("button", {
|
|
5811
|
-
className: "review-comment-action",
|
|
5812
|
-
onClick: handleReply,
|
|
5813
|
-
type: "button",
|
|
5814
|
-
children: "Reply"
|
|
5815
|
-
}) : null, canResolveThread ? /* @__PURE__ */ jsx("button", {
|
|
5816
|
-
className: "review-comment-action",
|
|
5817
|
-
disabled: resolving,
|
|
5818
|
-
onClick: handleResolve,
|
|
5819
|
-
type: "button",
|
|
5820
|
-
children: resolving ? "Saving" : threadResolved ? "Reopen" : "Resolve"
|
|
5821
|
-
}) : null]
|
|
5822
|
-
})]
|
|
5823
|
-
}) : null]
|
|
6324
|
+
children: threadContent
|
|
5824
6325
|
});
|
|
5825
6326
|
}
|
|
5826
6327
|
function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusCommentId, focusCommentRequest, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onHeightChange, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
@@ -5853,14 +6354,16 @@ function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusComm
|
|
|
5853
6354
|
return () => observer.disconnect();
|
|
5854
6355
|
}, [annotationComments.length, onHeightChange]);
|
|
5855
6356
|
if (annotationComments.length === 0) return null;
|
|
6357
|
+
const commentGroups = groupReviewCommentsByThread(annotationComments);
|
|
5856
6358
|
return /* @__PURE__ */ jsx("div", {
|
|
5857
6359
|
className: "review-comment-thread",
|
|
5858
6360
|
ref: threadRef,
|
|
5859
|
-
children:
|
|
6361
|
+
children: commentGroups.map((group) => /* @__PURE__ */ jsx(ReviewCommentThreadGroup, {
|
|
5860
6362
|
agentId,
|
|
5861
6363
|
agentLabel,
|
|
5862
6364
|
comments: group.comments,
|
|
5863
6365
|
focusCommentId,
|
|
6366
|
+
focusCommentRequest,
|
|
5864
6367
|
focusEditorRef: setFocusEditorRef,
|
|
5865
6368
|
identity,
|
|
5866
6369
|
keymap,
|
|
@@ -6062,7 +6565,7 @@ const getHunkSelectionRange = (hunk) => {
|
|
|
6062
6565
|
} : null;
|
|
6063
6566
|
};
|
|
6064
6567
|
const isSameSelection = (a, b) => a.start === b.start && a.end === b.end && a.side === b.side;
|
|
6065
|
-
function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedGenerated = emptyExpandedGenerated, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, supportsReviewCommentActions, theme = "system", viewed, walkthroughNotes, wordWrap }) {
|
|
6568
|
+
function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedGenerated = emptyExpandedGenerated, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onFindDefinitions, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenDefinition, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, supportsReviewCommentActions, theme = "system", viewed, walkthroughNotes, wordWrap }) {
|
|
6066
6569
|
const codeViewRef = useRef(null);
|
|
6067
6570
|
const markdownEditorRefs = useRef(/* @__PURE__ */ new Map());
|
|
6068
6571
|
const refreshingMarkdownSectionsRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -6070,6 +6573,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6070
6573
|
const handledScrollRequestRef = useRef(null);
|
|
6071
6574
|
const handledHunkNavRef = useRef(hunkNavigation?.request ?? null);
|
|
6072
6575
|
const emptyCommentDeleteTimersRef = useRef(/* @__PURE__ */ new Map());
|
|
6576
|
+
const definitionHighlightFrameRef = useRef(null);
|
|
6073
6577
|
const highlightFrameRef = useRef(null);
|
|
6074
6578
|
const ignoreNextLineSelectionEndRef = useRef(false);
|
|
6075
6579
|
const navigatedSelectionRef = useRef(null);
|
|
@@ -6083,6 +6587,15 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6083
6587
|
const [imagePreviewLayoutPassBySection, setImagePreviewLayoutPassBySection] = useState({});
|
|
6084
6588
|
const [commentLayoutPassByItem, setCommentLayoutPassByItem] = useState({});
|
|
6085
6589
|
const [selectedLines, setSelectedLines] = useState(null);
|
|
6590
|
+
const [definitionLookup, setDefinitionLookup] = useState(null);
|
|
6591
|
+
const definitionLookupRequestRef = useRef(0);
|
|
6592
|
+
const definitionModifierActiveRef = useRef(false);
|
|
6593
|
+
const sourceKey = getSourceKey(source);
|
|
6594
|
+
const [definitionLookupSourceKey, setDefinitionLookupSourceKey] = useState(sourceKey);
|
|
6595
|
+
if (definitionLookupSourceKey !== sourceKey) {
|
|
6596
|
+
setDefinitionLookupSourceKey(sourceKey);
|
|
6597
|
+
setDefinitionLookup(null);
|
|
6598
|
+
}
|
|
6086
6599
|
const selectedLinesRef = useRef(null);
|
|
6087
6600
|
const commitMessageMetadata = source.type === "commit" ? commitMetadata : null;
|
|
6088
6601
|
const shouldShowCommitMessage = commitMessageMetadata != null;
|
|
@@ -6132,19 +6645,22 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6132
6645
|
[itemId]: (current[itemId] ?? 0) + 1
|
|
6133
6646
|
}));
|
|
6134
6647
|
}, []);
|
|
6135
|
-
const { firstItemByBlockId, firstItemByPath, itemBlockId, itemMetadata, items, searchTargetsByBaseItemId } = useMemo(() => {
|
|
6648
|
+
const { commentTargetById, firstItemByBlockId, firstItemByPath, itemBlockId, itemMetadata, items, searchTargetsByBaseItemId, selectedHeaderItemIds } = useMemo(() => {
|
|
6136
6649
|
const nextItems = [];
|
|
6650
|
+
const nextCommentTargetById = /* @__PURE__ */ new Map();
|
|
6137
6651
|
const nextFirstItemByBlockId = /* @__PURE__ */ new Map();
|
|
6138
6652
|
const nextFirstItemByPath = /* @__PURE__ */ new Map();
|
|
6139
6653
|
const nextItemBlockId = /* @__PURE__ */ new Map();
|
|
6140
6654
|
const nextItemMetadata = /* @__PURE__ */ new Map();
|
|
6141
6655
|
const nextSearchTargetsByBaseItemId = /* @__PURE__ */ new Map();
|
|
6656
|
+
const nextSelectedHeaderItemIds = /* @__PURE__ */ new Set();
|
|
6142
6657
|
const fontLayoutKey = `line-height:${diffLineHeight}`;
|
|
6143
6658
|
for (const block of reviewBlocks) {
|
|
6144
6659
|
if (block.header) {
|
|
6145
6660
|
const headerId = getBlockHeaderItemId(block);
|
|
6146
6661
|
nextFirstItemByBlockId.set(block.id, nextFirstItemByBlockId.get(block.id) ?? headerId);
|
|
6147
6662
|
nextItemBlockId.set(headerId, block.id);
|
|
6663
|
+
if ((block.headerSelected ?? block.selected) === true) nextSelectedHeaderItemIds.add(headerId);
|
|
6148
6664
|
nextItems.push({
|
|
6149
6665
|
annotations: [{
|
|
6150
6666
|
lineNumber: 1,
|
|
@@ -6162,7 +6678,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6162
6678
|
},
|
|
6163
6679
|
id: headerId,
|
|
6164
6680
|
type: "file",
|
|
6165
|
-
version: getItemVersion(`${block.id}:walkthrough-header
|
|
6681
|
+
version: getItemVersion(`${block.id}:walkthrough-header`)
|
|
6166
6682
|
});
|
|
6167
6683
|
}
|
|
6168
6684
|
if (!block.file) continue;
|
|
@@ -6200,6 +6716,11 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6200
6716
|
const blockSectionComments = blockCommentsBySection.get(section.id) ?? [];
|
|
6201
6717
|
const sectionComments = dedupeReviewComments([...visibleGlobalComments, ...blockSectionComments]);
|
|
6202
6718
|
for (const comment of sectionComments) {
|
|
6719
|
+
if (!nextCommentTargetById.has(comment.id)) nextCommentTargetById.set(comment.id, isLineReviewComment(comment) ? {
|
|
6720
|
+
id,
|
|
6721
|
+
lineNumber: comment.lineNumber,
|
|
6722
|
+
side: comment.side
|
|
6723
|
+
} : { id });
|
|
6203
6724
|
const key = getCommentKey(comment);
|
|
6204
6725
|
const existing = annotationMap.get(key);
|
|
6205
6726
|
if (existing && existing.metadata.type === "review-comment") annotationMap.set(key, {
|
|
@@ -6311,12 +6832,14 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6311
6832
|
}
|
|
6312
6833
|
}
|
|
6313
6834
|
return {
|
|
6835
|
+
commentTargetById: nextCommentTargetById,
|
|
6314
6836
|
firstItemByBlockId: nextFirstItemByBlockId,
|
|
6315
6837
|
firstItemByPath: nextFirstItemByPath,
|
|
6316
6838
|
itemBlockId: nextItemBlockId,
|
|
6317
6839
|
itemMetadata: nextItemMetadata,
|
|
6318
6840
|
items: nextItems,
|
|
6319
|
-
searchTargetsByBaseItemId: nextSearchTargetsByBaseItemId
|
|
6841
|
+
searchTargetsByBaseItemId: nextSearchTargetsByBaseItemId,
|
|
6842
|
+
selectedHeaderItemIds: nextSelectedHeaderItemIds
|
|
6320
6843
|
};
|
|
6321
6844
|
}, [
|
|
6322
6845
|
collapsed,
|
|
@@ -6369,6 +6892,18 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6369
6892
|
itemMetadata,
|
|
6370
6893
|
searchTargetsByBaseItemId
|
|
6371
6894
|
]);
|
|
6895
|
+
const getDefinitionDiffTarget = useCallback((candidate) => {
|
|
6896
|
+
for (const item of items) {
|
|
6897
|
+
if (item.type !== "diff") continue;
|
|
6898
|
+
const metadata = itemMetadata.get(item.id);
|
|
6899
|
+
const path = candidate.side === "deletions" ? metadata?.file.oldPath ?? metadata?.file.path : metadata?.file.path;
|
|
6900
|
+
if (metadata && path === candidate.path && lineIsVisibleInFileDiff(item.fileDiff, candidate.side, candidate.lineNumber)) return {
|
|
6901
|
+
item,
|
|
6902
|
+
metadata
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
return null;
|
|
6906
|
+
}, [itemMetadata, items]);
|
|
6372
6907
|
const setCodeViewSelectedLines = useCallback((selection) => {
|
|
6373
6908
|
if (selection == null || navigatedSelectionRef.current == null || selection.id !== navigatedSelectionRef.current.id || !isSameSelection(selection.range, navigatedSelectionRef.current.range)) navigatedSelectionRef.current = null;
|
|
6374
6909
|
setSelectedLines(selection);
|
|
@@ -6482,6 +7017,50 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6482
7017
|
createCommentForRange(range, context);
|
|
6483
7018
|
},
|
|
6484
7019
|
onLineClick: (line, context) => {
|
|
7020
|
+
if (isPrimaryModifier(line.event) && onFindDefinitions) {
|
|
7021
|
+
line.event.preventDefault();
|
|
7022
|
+
line.event.stopPropagation();
|
|
7023
|
+
const lineElement = "lineElement" in line ? line.lineElement : null;
|
|
7024
|
+
const identifier = lineElement ? getIdentifierFromPointerEvent(line.event, lineElement) : null;
|
|
7025
|
+
const meta = itemMetadata.get(context.item.id);
|
|
7026
|
+
const side = "annotationSide" in line ? line.annotationSide : null;
|
|
7027
|
+
if (!identifier || !meta || !side) {
|
|
7028
|
+
setDefinitionLookup(null);
|
|
7029
|
+
return;
|
|
7030
|
+
}
|
|
7031
|
+
const requestId = ++definitionLookupRequestRef.current;
|
|
7032
|
+
setDefinitionLookup({
|
|
7033
|
+
anchor: {
|
|
7034
|
+
x: line.event.clientX,
|
|
7035
|
+
y: line.event.clientY
|
|
7036
|
+
},
|
|
7037
|
+
identifier,
|
|
7038
|
+
result: null,
|
|
7039
|
+
sourceKey
|
|
7040
|
+
});
|
|
7041
|
+
onFindDefinitions({
|
|
7042
|
+
identifier,
|
|
7043
|
+
kind: meta.section.kind,
|
|
7044
|
+
lineNumber: line.lineNumber,
|
|
7045
|
+
path: side === "deletions" ? meta.file.oldPath ?? meta.file.path : meta.file.path,
|
|
7046
|
+
side,
|
|
7047
|
+
source
|
|
7048
|
+
}).then((result) => {
|
|
7049
|
+
if (definitionLookupRequestRef.current === requestId) setDefinitionLookup((current) => current?.identifier === identifier && current.sourceKey === sourceKey ? {
|
|
7050
|
+
...current,
|
|
7051
|
+
result
|
|
7052
|
+
} : current);
|
|
7053
|
+
}).catch(() => {
|
|
7054
|
+
if (definitionLookupRequestRef.current === requestId) setDefinitionLookup((current) => current?.identifier === identifier && current.sourceKey === sourceKey ? {
|
|
7055
|
+
...current,
|
|
7056
|
+
result: {
|
|
7057
|
+
reason: "Definition search is unavailable for this repository.",
|
|
7058
|
+
status: "unavailable"
|
|
7059
|
+
}
|
|
7060
|
+
} : current);
|
|
7061
|
+
});
|
|
7062
|
+
return;
|
|
7063
|
+
}
|
|
6485
7064
|
if (isReadOnly) return;
|
|
6486
7065
|
if (isInteractiveReviewEvent(line.event)) return;
|
|
6487
7066
|
const meta = itemMetadata.get(context.item.id);
|
|
@@ -6514,11 +7093,14 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6514
7093
|
const metadata = itemMetadata.get(context.item.id);
|
|
6515
7094
|
const isWalkthroughHeaderItem = context.item.id.endsWith(":walkthrough-header");
|
|
6516
7095
|
node.classList.toggle("codiff-walkthrough-header-item", isWalkthroughHeaderItem);
|
|
6517
|
-
node.classList.toggle("codiff-selected-item", metadata?.isSelected === true);
|
|
7096
|
+
node.classList.toggle("codiff-selected-item", metadata?.isSelected === true || selectedHeaderItemIds.has(context.item.id));
|
|
6518
7097
|
node.classList.toggle("codiff-markdown-preview-item", metadata?.isMarkdownPreview === true);
|
|
6519
7098
|
node.classList.toggle("codiff-image-preview-item", context.item.type === "file" && Boolean(metadata && canRenderImagePreview(metadata.file.path, metadata.section)));
|
|
6520
7099
|
node.classList.toggle("codiff-loadable-summary-item", metadata != null && shouldLoadDiffSectionContents(metadata.section));
|
|
6521
7100
|
node.classList.toggle("codiff-loading-summary-item", Boolean(metadata && loadingSectionIds.has(metadata.section.id)));
|
|
7101
|
+
if (definitionModifierActiveRef.current) window.requestAnimationFrame(() => {
|
|
7102
|
+
if (definitionModifierActiveRef.current && node.isConnected) applyIdentifierNavigationState([{ element: node }], true);
|
|
7103
|
+
});
|
|
6522
7104
|
},
|
|
6523
7105
|
overflow: wordWrap ? "wrap" : "scroll",
|
|
6524
7106
|
stickyHeaders: true,
|
|
@@ -6539,7 +7121,11 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6539
7121
|
loadDiffFiles,
|
|
6540
7122
|
loadingSectionIds,
|
|
6541
7123
|
onCreateComment,
|
|
7124
|
+
onFindDefinitions,
|
|
6542
7125
|
onLoadSection,
|
|
7126
|
+
selectedHeaderItemIds,
|
|
7127
|
+
source,
|
|
7128
|
+
sourceKey,
|
|
6543
7129
|
theme,
|
|
6544
7130
|
wordWrap
|
|
6545
7131
|
]);
|
|
@@ -6631,11 +7217,19 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6631
7217
|
poolSize: Math.min(3, Math.max(1, navigator.hardwareConcurrency || 3)),
|
|
6632
7218
|
workerFactory: () => new Worker(new URL("@pierre/diffs/worker/worker.js", import.meta.url), { type: "module" })
|
|
6633
7219
|
}), []);
|
|
6634
|
-
const
|
|
7220
|
+
const requestScrollTargetIntoView = useCallback((itemId, behavior = "instant", commentTarget) => {
|
|
6635
7221
|
const handle = codeViewRef.current;
|
|
6636
7222
|
const viewer = handle?.getInstance();
|
|
6637
7223
|
if (!handle || !viewer || viewer.getTopForItem(itemId) == null) return false;
|
|
6638
|
-
handle.scrollTo({
|
|
7224
|
+
handle.scrollTo(commentTarget?.lineNumber != null && commentTarget.side ? {
|
|
7225
|
+
align: "center",
|
|
7226
|
+
behavior: getEffectiveScrollBehavior(behavior),
|
|
7227
|
+
id: itemId,
|
|
7228
|
+
lineNumber: commentTarget.lineNumber,
|
|
7229
|
+
offset: 11,
|
|
7230
|
+
side: commentTarget.side,
|
|
7231
|
+
type: "line"
|
|
7232
|
+
} : {
|
|
6639
7233
|
behavior: getEffectiveScrollBehavior(behavior),
|
|
6640
7234
|
id: itemId,
|
|
6641
7235
|
offset: 11,
|
|
@@ -6646,14 +7240,15 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6646
7240
|
useLayoutEffect(() => {
|
|
6647
7241
|
if (!scrollTarget || handledScrollRequestRef.current === scrollTarget.request) return;
|
|
6648
7242
|
const behavior = scrollTarget.behavior ?? "instant";
|
|
6649
|
-
const
|
|
7243
|
+
const commentTarget = scrollTarget.commentId ? commentTargetById.get(scrollTarget.commentId) : void 0;
|
|
7244
|
+
const itemId = commentTarget?.id ?? (scrollTarget.blockId ? firstItemByBlockId.get(scrollTarget.blockId) : scrollTarget.path ? firstItemByPath.get(scrollTarget.path) : null);
|
|
6650
7245
|
if (!itemId) return;
|
|
6651
7246
|
let frame = null;
|
|
6652
7247
|
let attempts = 0;
|
|
6653
7248
|
let canceled = false;
|
|
6654
7249
|
const tryScroll = () => {
|
|
6655
7250
|
if (canceled || handledScrollRequestRef.current === scrollTarget.request) return;
|
|
6656
|
-
if (
|
|
7251
|
+
if (requestScrollTargetIntoView(itemId, behavior, commentTarget)) {
|
|
6657
7252
|
handledScrollRequestRef.current = scrollTarget.request;
|
|
6658
7253
|
return;
|
|
6659
7254
|
}
|
|
@@ -6668,9 +7263,10 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6668
7263
|
if (frame != null) window.cancelAnimationFrame(frame);
|
|
6669
7264
|
};
|
|
6670
7265
|
}, [
|
|
7266
|
+
commentTargetById,
|
|
6671
7267
|
firstItemByBlockId,
|
|
6672
7268
|
firstItemByPath,
|
|
6673
|
-
|
|
7269
|
+
requestScrollTargetIntoView,
|
|
6674
7270
|
scrollTarget
|
|
6675
7271
|
]);
|
|
6676
7272
|
useEffect(() => {
|
|
@@ -6832,6 +7428,22 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6832
7428
|
applySearchHighlights(viewer.getRenderedItems(), searchQuery, resolvedActiveSearchMatch);
|
|
6833
7429
|
});
|
|
6834
7430
|
}, [resolvedActiveSearchMatch, searchQuery]);
|
|
7431
|
+
const updateRenderedIdentifierNavigation = useCallback((active, viewer) => {
|
|
7432
|
+
const nextViewer = viewer ?? codeViewRef.current?.getInstance();
|
|
7433
|
+
if (!nextViewer) return;
|
|
7434
|
+
if (definitionHighlightFrameRef.current != null) {
|
|
7435
|
+
window.cancelAnimationFrame(definitionHighlightFrameRef.current);
|
|
7436
|
+
definitionHighlightFrameRef.current = null;
|
|
7437
|
+
}
|
|
7438
|
+
if (!active) {
|
|
7439
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems(), false);
|
|
7440
|
+
return;
|
|
7441
|
+
}
|
|
7442
|
+
definitionHighlightFrameRef.current = window.requestAnimationFrame(() => {
|
|
7443
|
+
definitionHighlightFrameRef.current = null;
|
|
7444
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems(), true);
|
|
7445
|
+
});
|
|
7446
|
+
}, []);
|
|
6835
7447
|
const scheduleStickyHeaderStateUpdate = useCallback((viewer) => {
|
|
6836
7448
|
const nextViewer = viewer ?? codeViewRef.current?.getInstance();
|
|
6837
7449
|
if (!nextViewer) return;
|
|
@@ -6845,6 +7457,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6845
7457
|
for (const timer of deferredTimersRef.current) window.clearTimeout(timer);
|
|
6846
7458
|
deferredTimersRef.current.clear();
|
|
6847
7459
|
emptyCommentDeleteTimersRef.current.clear();
|
|
7460
|
+
if (definitionHighlightFrameRef.current != null) window.cancelAnimationFrame(definitionHighlightFrameRef.current);
|
|
6848
7461
|
if (highlightFrameRef.current != null) window.cancelAnimationFrame(highlightFrameRef.current);
|
|
6849
7462
|
if (stickyHeaderFrameRef.current != null) window.cancelAnimationFrame(stickyHeaderFrameRef.current);
|
|
6850
7463
|
}, []);
|
|
@@ -6856,6 +7469,38 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6856
7469
|
scheduleSearchHighlights,
|
|
6857
7470
|
scheduleStickyHeaderStateUpdate
|
|
6858
7471
|
]);
|
|
7472
|
+
useEffect(() => {
|
|
7473
|
+
if (!onFindDefinitions) return;
|
|
7474
|
+
const updateDefinitionModifierState = (active) => {
|
|
7475
|
+
if (definitionModifierActiveRef.current === active) return;
|
|
7476
|
+
definitionModifierActiveRef.current = active;
|
|
7477
|
+
updateRenderedIdentifierNavigation(active);
|
|
7478
|
+
};
|
|
7479
|
+
const handleModifierChange = (event) => {
|
|
7480
|
+
updateDefinitionModifierState(isPrimaryModifier(event));
|
|
7481
|
+
};
|
|
7482
|
+
const handleModifierPointer = (event) => {
|
|
7483
|
+
updateDefinitionModifierState(isPrimaryModifier(event));
|
|
7484
|
+
};
|
|
7485
|
+
const clearDefinitionModifierState = () => updateDefinitionModifierState(false);
|
|
7486
|
+
window.addEventListener("keydown", handleModifierChange);
|
|
7487
|
+
window.addEventListener("keyup", handleModifierChange);
|
|
7488
|
+
window.addEventListener("pointerdown", handleModifierPointer);
|
|
7489
|
+
window.addEventListener("pointermove", handleModifierPointer);
|
|
7490
|
+
window.addEventListener("blur", clearDefinitionModifierState);
|
|
7491
|
+
return () => {
|
|
7492
|
+
clearDefinitionModifierState();
|
|
7493
|
+
window.removeEventListener("keydown", handleModifierChange);
|
|
7494
|
+
window.removeEventListener("keyup", handleModifierChange);
|
|
7495
|
+
window.removeEventListener("pointerdown", handleModifierPointer);
|
|
7496
|
+
window.removeEventListener("pointermove", handleModifierPointer);
|
|
7497
|
+
window.removeEventListener("blur", clearDefinitionModifierState);
|
|
7498
|
+
};
|
|
7499
|
+
}, [onFindDefinitions, updateRenderedIdentifierNavigation]);
|
|
7500
|
+
const invalidateDefinitionLookup = useCallback(() => {
|
|
7501
|
+
definitionLookupRequestRef.current++;
|
|
7502
|
+
}, []);
|
|
7503
|
+
useEffect(() => invalidateDefinitionLookup, [invalidateDefinitionLookup]);
|
|
6859
7504
|
useEffect(() => {
|
|
6860
7505
|
const handle = codeViewRef.current;
|
|
6861
7506
|
const viewer = handle?.getInstance();
|
|
@@ -7037,15 +7682,17 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7037
7682
|
}
|
|
7038
7683
|
scheduleSearchHighlights();
|
|
7039
7684
|
scheduleStickyHeaderStateUpdate(viewer);
|
|
7685
|
+
if (definitionModifierActiveRef.current) updateRenderedIdentifierNavigation(true, viewer);
|
|
7040
7686
|
}, [
|
|
7041
7687
|
itemBlockId,
|
|
7042
7688
|
items,
|
|
7043
7689
|
onActiveBlockChange,
|
|
7044
7690
|
onSelectPathFromScroll,
|
|
7045
7691
|
scheduleSearchHighlights,
|
|
7046
|
-
scheduleStickyHeaderStateUpdate
|
|
7692
|
+
scheduleStickyHeaderStateUpdate,
|
|
7693
|
+
updateRenderedIdentifierNavigation
|
|
7047
7694
|
]);
|
|
7048
|
-
return disableWorkerPool ? /* @__PURE__ */ jsx(CodeView, {
|
|
7695
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [disableWorkerPool ? /* @__PURE__ */ jsx(CodeView, {
|
|
7049
7696
|
className: "code-view",
|
|
7050
7697
|
disableWorkerPool,
|
|
7051
7698
|
items: codeViewItems,
|
|
@@ -7073,11 +7720,77 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7073
7720
|
renderCustomHeader,
|
|
7074
7721
|
selectedLines: isReadOnly ? null : selectedLines
|
|
7075
7722
|
})
|
|
7723
|
+
}), definitionLookup?.sourceKey === sourceKey && onOpenDefinition ? /* @__PURE__ */ jsx(DefinitionPopover, {
|
|
7724
|
+
anchor: definitionLookup.anchor,
|
|
7725
|
+
getDestination: (candidate) => getDefinitionDiffTarget(candidate) ? "diff" : candidate.canOpenInEditor ? "editor" : "unavailable",
|
|
7726
|
+
identifier: definitionLookup.identifier,
|
|
7727
|
+
onClose: () => {
|
|
7728
|
+
invalidateDefinitionLookup();
|
|
7729
|
+
setDefinitionLookup(null);
|
|
7730
|
+
},
|
|
7731
|
+
onOpen: (candidate) => {
|
|
7732
|
+
const target = getDefinitionDiffTarget(candidate);
|
|
7733
|
+
if (target) {
|
|
7734
|
+
if (target.metadata.isCollapsed) onToggleCollapsed(target.metadata.file, true, target.metadata.reviewIdentity.key);
|
|
7735
|
+
const scrollToDefinition = () => codeViewRef.current?.scrollTo({
|
|
7736
|
+
align: "center",
|
|
7737
|
+
behavior: "smooth-auto",
|
|
7738
|
+
id: target.item.id,
|
|
7739
|
+
lineNumber: candidate.lineNumber,
|
|
7740
|
+
offset: 11,
|
|
7741
|
+
side: candidate.side,
|
|
7742
|
+
type: "line"
|
|
7743
|
+
});
|
|
7744
|
+
if (target.metadata.isCollapsed) window.requestAnimationFrame(() => window.requestAnimationFrame(scrollToDefinition));
|
|
7745
|
+
else scrollToDefinition();
|
|
7746
|
+
} else if (candidate.canOpenInEditor) onOpenDefinition(candidate);
|
|
7747
|
+
invalidateDefinitionLookup();
|
|
7748
|
+
setDefinitionLookup(null);
|
|
7749
|
+
},
|
|
7750
|
+
result: definitionLookup.result
|
|
7751
|
+
}) : null] });
|
|
7752
|
+
}
|
|
7753
|
+
//#endregion
|
|
7754
|
+
//#region app/components/ReviewModeControl.tsx
|
|
7755
|
+
function ReviewModeControl({ mode, modes, onModeChange }) {
|
|
7756
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7757
|
+
"aria-label": "Review mode",
|
|
7758
|
+
className: "review-mode-control",
|
|
7759
|
+
role: "tablist",
|
|
7760
|
+
children: modes.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
7761
|
+
"aria-label": item.ariaLabel,
|
|
7762
|
+
"aria-selected": mode === item.value,
|
|
7763
|
+
onClick: () => onModeChange(item.value),
|
|
7764
|
+
role: "tab",
|
|
7765
|
+
title: item.title,
|
|
7766
|
+
type: "button",
|
|
7767
|
+
children: [
|
|
7768
|
+
item.icon,
|
|
7769
|
+
/* @__PURE__ */ jsx("span", {
|
|
7770
|
+
className: "review-mode-label",
|
|
7771
|
+
children: item.label
|
|
7772
|
+
}),
|
|
7773
|
+
item.indicator
|
|
7774
|
+
]
|
|
7775
|
+
}, item.value))
|
|
7076
7776
|
});
|
|
7077
7777
|
}
|
|
7078
7778
|
//#endregion
|
|
7079
7779
|
//#region app/components/ReviewTopBar.tsx
|
|
7080
|
-
function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, sourceMenu, toggleTitle }) {
|
|
7780
|
+
function ReviewTopBar({ actions, center, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, sidebarPosition = "left", sourceMenu, toggleTitle }) {
|
|
7781
|
+
const sidebarToggle = /* @__PURE__ */ jsx("button", {
|
|
7782
|
+
"aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
7783
|
+
className: "review-top-bar-icon-button sidebar-toggle-button",
|
|
7784
|
+
onClick: onToggleSidebar,
|
|
7785
|
+
title: toggleTitle,
|
|
7786
|
+
type: "button",
|
|
7787
|
+
children: /* @__PURE__ */ jsx(SidebarSimpleIcon, {
|
|
7788
|
+
"aria-hidden": true,
|
|
7789
|
+
mirrored: sidebarPosition === "right",
|
|
7790
|
+
size: 18,
|
|
7791
|
+
weight: "bold"
|
|
7792
|
+
})
|
|
7793
|
+
});
|
|
7081
7794
|
return /* @__PURE__ */ jsxs("header", {
|
|
7082
7795
|
className: "review-top-bar workspace-top-bar",
|
|
7083
7796
|
children: [
|
|
@@ -7085,18 +7798,7 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
|
|
|
7085
7798
|
className: "review-top-bar-left",
|
|
7086
7799
|
children: [
|
|
7087
7800
|
leading,
|
|
7088
|
-
|
|
7089
|
-
"aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
7090
|
-
className: "review-top-bar-icon-button sidebar-toggle-button",
|
|
7091
|
-
onClick: onToggleSidebar,
|
|
7092
|
-
title: toggleTitle,
|
|
7093
|
-
type: "button",
|
|
7094
|
-
children: /* @__PURE__ */ jsx(SidebarSimpleIcon, {
|
|
7095
|
-
"aria-hidden": true,
|
|
7096
|
-
size: 18,
|
|
7097
|
-
weight: "bold"
|
|
7098
|
-
})
|
|
7099
|
-
}),
|
|
7801
|
+
sidebarPosition === "left" ? sidebarToggle : null,
|
|
7100
7802
|
sourceMenu,
|
|
7101
7803
|
/* @__PURE__ */ jsx("div", {
|
|
7102
7804
|
className: "review-top-bar-repository-slot",
|
|
@@ -7105,36 +7807,27 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
|
|
|
7105
7807
|
})
|
|
7106
7808
|
]
|
|
7107
7809
|
}),
|
|
7108
|
-
/* @__PURE__ */ jsx(
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
onClick: () => onModeChange(item.value),
|
|
7116
|
-
role: "tab",
|
|
7117
|
-
title: item.title,
|
|
7118
|
-
type: "button",
|
|
7119
|
-
children: [
|
|
7120
|
-
item.icon,
|
|
7121
|
-
/* @__PURE__ */ jsx("span", {
|
|
7122
|
-
className: "review-mode-label",
|
|
7123
|
-
children: item.label
|
|
7124
|
-
}),
|
|
7125
|
-
item.indicator
|
|
7126
|
-
]
|
|
7127
|
-
}, item.value))
|
|
7810
|
+
modes ? /* @__PURE__ */ jsx(ReviewModeControl, {
|
|
7811
|
+
mode,
|
|
7812
|
+
modes,
|
|
7813
|
+
onModeChange
|
|
7814
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
7815
|
+
className: "review-top-bar-center",
|
|
7816
|
+
children: center
|
|
7128
7817
|
}),
|
|
7129
7818
|
/* @__PURE__ */ jsxs("div", {
|
|
7130
7819
|
className: "review-top-bar-right",
|
|
7131
|
-
children: [
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7820
|
+
children: [
|
|
7821
|
+
context ? /* @__PURE__ */ jsx("div", {
|
|
7822
|
+
className: "review-top-bar-context",
|
|
7823
|
+
children: context
|
|
7824
|
+
}) : null,
|
|
7825
|
+
actions ? /* @__PURE__ */ jsx("div", {
|
|
7826
|
+
className: "review-top-bar-actions",
|
|
7827
|
+
children: actions
|
|
7828
|
+
}) : null,
|
|
7829
|
+
sidebarPosition === "right" ? sidebarToggle : null
|
|
7830
|
+
]
|
|
7138
7831
|
})
|
|
7139
7832
|
]
|
|
7140
7833
|
});
|
|
@@ -7286,6 +7979,7 @@ function StageFileRow({ file, on, onToggle }) {
|
|
|
7286
7979
|
function StageGroupRow({ group, onToggleFile, onToggleGroup, selected }) {
|
|
7287
7980
|
const paths = group.files.map((file) => file.path);
|
|
7288
7981
|
const onCount = paths.filter((path) => selected.has(path)).length;
|
|
7982
|
+
const state = onCount === 0 ? "off" : onCount === paths.length ? "on" : "partial";
|
|
7289
7983
|
return /* @__PURE__ */ jsxs("div", {
|
|
7290
7984
|
className: "wt-stage-group",
|
|
7291
7985
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
@@ -7293,7 +7987,7 @@ function StageGroupRow({ group, onToggleFile, onToggleGroup, selected }) {
|
|
|
7293
7987
|
onClick: () => onToggleGroup(paths),
|
|
7294
7988
|
type: "button",
|
|
7295
7989
|
children: [
|
|
7296
|
-
/* @__PURE__ */ jsx(CommitCheck, { state
|
|
7990
|
+
/* @__PURE__ */ jsx(CommitCheck, { state }),
|
|
7297
7991
|
/* @__PURE__ */ jsx("span", {
|
|
7298
7992
|
className: "wt-stage-group-icon",
|
|
7299
7993
|
children: /* @__PURE__ */ jsx(ChapterIcon, {
|
|
@@ -7629,14 +8323,14 @@ function CommitView({ branch, draft, model, onCommit, onCommitOutput, onUpdateMe
|
|
|
7629
8323
|
}
|
|
7630
8324
|
//#endregion
|
|
7631
8325
|
//#region app/components/walkthrough/NarrativeWalkthroughView.tsx
|
|
7632
|
-
const getWalkthroughBlockScrollTarget = ({ activeBlockId, firstSupportBlockId,
|
|
8326
|
+
const getWalkthroughBlockScrollTarget = ({ activeBlockId, firstSupportBlockId, scrollTarget }) => scrollTarget.kind === "support" && firstSupportBlockId ? {
|
|
7633
8327
|
behavior: "smooth",
|
|
7634
8328
|
blockId: firstSupportBlockId,
|
|
7635
|
-
request:
|
|
7636
|
-
} :
|
|
8329
|
+
request: scrollTarget.nonce
|
|
8330
|
+
} : scrollTarget.kind === "stop" && activeBlockId && scrollTarget.nonce > 0 ? {
|
|
7637
8331
|
behavior: "smooth",
|
|
7638
8332
|
blockId: activeBlockId,
|
|
7639
|
-
request:
|
|
8333
|
+
request: scrollTarget.nonce
|
|
7640
8334
|
} : null;
|
|
7641
8335
|
const getFocusedRunDiffs = (item, files) => resolveWalkthroughHunkRuns(item, files).flatMap((run) => {
|
|
7642
8336
|
const focused = focusChangedFileForHunks(run.resolved.file, run.resolved.section, run.hunks);
|
|
@@ -7659,14 +8353,15 @@ const getWalkthroughNavigationKeyDirection = (event) => {
|
|
|
7659
8353
|
if (isCtrlOnly && event.key === "ArrowUp") return -1;
|
|
7660
8354
|
return 0;
|
|
7661
8355
|
};
|
|
8356
|
+
const getWalkthroughKeyboardForwardIndex = ({ index, scrollRequest }) => index === 0 && scrollRequest === 0 ? 0 : index + 1;
|
|
7662
8357
|
const emptyWalkthroughBlockSet = {
|
|
7663
8358
|
blocks: [],
|
|
7664
8359
|
firstBlockIdByStop: [],
|
|
7665
8360
|
stopIndexByBlockId: /* @__PURE__ */ new Map()
|
|
7666
8361
|
};
|
|
7667
|
-
function StopHeader({
|
|
8362
|
+
function StopHeader({ stop }) {
|
|
7668
8363
|
return /* @__PURE__ */ jsxs("div", {
|
|
7669
|
-
className:
|
|
8364
|
+
className: "wt-stop-block wt-stop-block-header",
|
|
7670
8365
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
7671
8366
|
className: "wt-stage-title-row",
|
|
7672
8367
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
@@ -7676,9 +8371,9 @@ function StopHeader({ current, stop }) {
|
|
|
7676
8371
|
}), /* @__PURE__ */ jsx(Narration, { prose: stop.prose })]
|
|
7677
8372
|
});
|
|
7678
8373
|
}
|
|
7679
|
-
function SupportHeader(
|
|
8374
|
+
function SupportHeader() {
|
|
7680
8375
|
return /* @__PURE__ */ jsxs("div", {
|
|
7681
|
-
className:
|
|
8376
|
+
className: "wt-stop-block wt-stop-block-header",
|
|
7682
8377
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
7683
8378
|
className: "wt-stage-title-row",
|
|
7684
8379
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
@@ -7699,10 +8394,7 @@ const createWalkthroughBlocks = (files, walkthroughView, currentIndex) => {
|
|
|
7699
8394
|
firstBlockIdByStop[stop.index] = blockId;
|
|
7700
8395
|
stopIndexByBlockId.set(blockId, stop.index);
|
|
7701
8396
|
blocks.push({
|
|
7702
|
-
header: /* @__PURE__ */ jsx(StopHeader, {
|
|
7703
|
-
current: stop.index === currentIndex,
|
|
7704
|
-
stop
|
|
7705
|
-
}),
|
|
8397
|
+
header: /* @__PURE__ */ jsx(StopHeader, { stop }),
|
|
7706
8398
|
headerSelected: stop.index === currentIndex,
|
|
7707
8399
|
id: blockId
|
|
7708
8400
|
});
|
|
@@ -7714,10 +8406,7 @@ const createWalkthroughBlocks = (files, walkthroughView, currentIndex) => {
|
|
|
7714
8406
|
stopIndexByBlockId.set(blockId, stop.index);
|
|
7715
8407
|
blocks.push({
|
|
7716
8408
|
file,
|
|
7717
|
-
header: runIndex === 0 ? /* @__PURE__ */ jsx(StopHeader, {
|
|
7718
|
-
current: stop.index === currentIndex,
|
|
7719
|
-
stop
|
|
7720
|
-
}) : null,
|
|
8409
|
+
header: runIndex === 0 ? /* @__PURE__ */ jsx(StopHeader, { stop }) : null,
|
|
7721
8410
|
headerSelected: stop.index === currentIndex,
|
|
7722
8411
|
id: blockId,
|
|
7723
8412
|
itemIdPrefix: blockId,
|
|
@@ -7746,7 +8435,7 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace) =
|
|
|
7746
8435
|
const isFirstBlock = blocks.length === 0;
|
|
7747
8436
|
blocks.push({
|
|
7748
8437
|
file,
|
|
7749
|
-
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {
|
|
8438
|
+
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {}) : null,
|
|
7750
8439
|
headerSelected: selected,
|
|
7751
8440
|
id: blockId,
|
|
7752
8441
|
itemIdPrefix: blockId,
|
|
@@ -7760,7 +8449,7 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace) =
|
|
|
7760
8449
|
const isFirstBlock = blocks.length === 0;
|
|
7761
8450
|
blocks.push({
|
|
7762
8451
|
file,
|
|
7763
|
-
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {
|
|
8452
|
+
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {}) : null,
|
|
7764
8453
|
headerSelected: selected,
|
|
7765
8454
|
id: blockId,
|
|
7766
8455
|
itemIdPrefix: blockId,
|
|
@@ -7795,8 +8484,10 @@ function Arc({ committable, navigation, onShareWalkthrough, shareWalkthroughDisa
|
|
|
7795
8484
|
const goArcPrev = useCallback(() => {
|
|
7796
8485
|
if (navigation.mode === "stop") navigation.goPrev();
|
|
7797
8486
|
else if (navigation.mode === "support") navigation.goStop(walkthroughView.sequence.length - 1);
|
|
7798
|
-
else if (navigation.mode === "commit")
|
|
7799
|
-
|
|
8487
|
+
else if (navigation.mode === "commit") {
|
|
8488
|
+
if (supportAvailable) navigation.openSupport();
|
|
8489
|
+
else navigation.goStop(walkthroughView.sequence.length - 1);
|
|
8490
|
+
}
|
|
7800
8491
|
}, [
|
|
7801
8492
|
navigation,
|
|
7802
8493
|
supportAvailable,
|
|
@@ -7955,9 +8646,7 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
7955
8646
|
const reviewBlockScrollTarget = getWalkthroughBlockScrollTarget({
|
|
7956
8647
|
activeBlockId,
|
|
7957
8648
|
firstSupportBlockId,
|
|
7958
|
-
|
|
7959
|
-
stopScrollRequest: navigation.scrollTarget.nonce,
|
|
7960
|
-
supportScrollRequest: navigation.supportScrollRequest
|
|
8649
|
+
scrollTarget: navigation.scrollTarget
|
|
7961
8650
|
});
|
|
7962
8651
|
const handleActiveBlockChange = useCallback((blockId) => {
|
|
7963
8652
|
onActiveReviewTargetChange(getBlockReviewTarget(reviewBlocks, blockId));
|
|
@@ -7993,7 +8682,11 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
7993
8682
|
if (direction === 1) {
|
|
7994
8683
|
event.preventDefault();
|
|
7995
8684
|
if (navigation.mode === "stop") {
|
|
7996
|
-
|
|
8685
|
+
const targetIndex = getWalkthroughKeyboardForwardIndex({
|
|
8686
|
+
index: navigation.index,
|
|
8687
|
+
scrollRequest: navigation.scrollTarget.nonce
|
|
8688
|
+
});
|
|
8689
|
+
if (targetIndex < walkthroughView.sequence.length) navigation.goStop(targetIndex);
|
|
7997
8690
|
else if (supportAvailable) navigation.openSupport();
|
|
7998
8691
|
else if (committable) navigation.enterCommit();
|
|
7999
8692
|
} else if (navigation.mode === "support" && committable) navigation.enterCommit();
|
|
@@ -8001,8 +8694,10 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
8001
8694
|
event.preventDefault();
|
|
8002
8695
|
if (navigation.mode === "stop") navigation.goPrev();
|
|
8003
8696
|
else if (navigation.mode === "support") navigation.goStop(navigation.index);
|
|
8004
|
-
else if (navigation.mode === "commit")
|
|
8005
|
-
|
|
8697
|
+
else if (navigation.mode === "commit") {
|
|
8698
|
+
if (supportAvailable) navigation.openSupport();
|
|
8699
|
+
else navigation.goStop(navigation.index);
|
|
8700
|
+
}
|
|
8006
8701
|
}
|
|
8007
8702
|
}, [
|
|
8008
8703
|
committable,
|
|
@@ -8235,9 +8930,9 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8235
8930
|
const [index, setIndex] = useState(0);
|
|
8236
8931
|
const [scrollTarget, setScrollTarget] = useState({
|
|
8237
8932
|
index: 0,
|
|
8933
|
+
kind: "stop",
|
|
8238
8934
|
nonce: 0
|
|
8239
8935
|
});
|
|
8240
|
-
const [supportScrollRequest, setSupportScrollRequest] = useState(0);
|
|
8241
8936
|
const [supportVisited, setSupportVisited] = useState(false);
|
|
8242
8937
|
const [visited, setVisited] = useState(() => {
|
|
8243
8938
|
const stopId = firstStopId(walkthrough);
|
|
@@ -8268,9 +8963,9 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8268
8963
|
setIndex(0);
|
|
8269
8964
|
setScrollTarget({
|
|
8270
8965
|
index: 0,
|
|
8966
|
+
kind: "stop",
|
|
8271
8967
|
nonce: 0
|
|
8272
8968
|
});
|
|
8273
|
-
setSupportScrollRequest(0);
|
|
8274
8969
|
setSupportVisited(false);
|
|
8275
8970
|
const stopId = firstStopId(walkthrough);
|
|
8276
8971
|
setVisited(new Set(stopId ? [stopId] : []));
|
|
@@ -8332,6 +9027,7 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8332
9027
|
pendingSupportScrollRef.current = null;
|
|
8333
9028
|
setScrollTarget((current) => ({
|
|
8334
9029
|
index: clamped,
|
|
9030
|
+
kind: "stop",
|
|
8335
9031
|
nonce: current.nonce + 1
|
|
8336
9032
|
}));
|
|
8337
9033
|
}, [
|
|
@@ -8376,7 +9072,11 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8376
9072
|
if (walkthroughView?.sequence.length) setIndex(walkthroughView.sequence.length - 1);
|
|
8377
9073
|
setMode("support");
|
|
8378
9074
|
pendingSupportScrollRef.current = { walkthrough };
|
|
8379
|
-
|
|
9075
|
+
setScrollTarget((current) => ({
|
|
9076
|
+
...current,
|
|
9077
|
+
kind: "support",
|
|
9078
|
+
nonce: current.nonce + 1
|
|
9079
|
+
}));
|
|
8380
9080
|
setSupportVisited(true);
|
|
8381
9081
|
}, [
|
|
8382
9082
|
walkthrough,
|
|
@@ -8384,11 +9084,15 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8384
9084
|
leaveStopMode
|
|
8385
9085
|
]);
|
|
8386
9086
|
const syncSupportFromScroll = useCallback(() => {
|
|
9087
|
+
const pendingStop = pendingStopScrollRef.current;
|
|
9088
|
+
if (pendingStop) {
|
|
9089
|
+
if (pendingStop.walkthrough === walkthrough) return;
|
|
9090
|
+
pendingStopScrollRef.current = null;
|
|
9091
|
+
}
|
|
8387
9092
|
pendingSupportScrollRef.current = null;
|
|
8388
|
-
pendingStopScrollRef.current = null;
|
|
8389
9093
|
setMode("support");
|
|
8390
9094
|
setSupportVisited(true);
|
|
8391
|
-
}, []);
|
|
9095
|
+
}, [walkthrough]);
|
|
8392
9096
|
return {
|
|
8393
9097
|
commitBody,
|
|
8394
9098
|
commitSelected,
|
|
@@ -8407,7 +9111,6 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8407
9111
|
scrollTarget,
|
|
8408
9112
|
setCommitBody,
|
|
8409
9113
|
setCommitSubject,
|
|
8410
|
-
supportScrollRequest,
|
|
8411
9114
|
supportVisited,
|
|
8412
9115
|
syncIndexFromScroll,
|
|
8413
9116
|
syncSupportFromScroll,
|
|
@@ -8506,7 +9209,7 @@ const writeSidebarWidth = (width, storage = localStorage) => {
|
|
|
8506
9209
|
};
|
|
8507
9210
|
//#endregion
|
|
8508
9211
|
//#region app/hooks/useResizableSidebar.ts
|
|
8509
|
-
function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, readWidth }) {
|
|
9212
|
+
function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, position, readWidth }) {
|
|
8510
9213
|
const [sidebarWidth, setSidebarWidth] = useState(readWidth);
|
|
8511
9214
|
return {
|
|
8512
9215
|
resizeSidebar: useCallback((event) => {
|
|
@@ -8515,7 +9218,7 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8515
9218
|
const handle = event.currentTarget;
|
|
8516
9219
|
const shell = handle.parentElement;
|
|
8517
9220
|
if (!shell) return;
|
|
8518
|
-
const
|
|
9221
|
+
const shellRect = shell.getBoundingClientRect();
|
|
8519
9222
|
handle.setPointerCapture(event.pointerId);
|
|
8520
9223
|
handle.classList.add("dragging");
|
|
8521
9224
|
document.body.style.cursor = "col-resize";
|
|
@@ -8529,7 +9232,7 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8529
9232
|
document.body.style.cursor = "";
|
|
8530
9233
|
};
|
|
8531
9234
|
const handleMove = (moveEvent) => {
|
|
8532
|
-
const rawWidth = moveEvent.clientX -
|
|
9235
|
+
const rawWidth = position === "right" ? shellRect.right - moveEvent.clientX : moveEvent.clientX - shellRect.left;
|
|
8533
9236
|
if (collapseThreshold != null && rawWidth < collapseThreshold) {
|
|
8534
9237
|
collapsed = true;
|
|
8535
9238
|
onCollapse?.();
|
|
@@ -8551,7 +9254,8 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8551
9254
|
}, [
|
|
8552
9255
|
collapseThreshold,
|
|
8553
9256
|
onCollapse,
|
|
8554
|
-
onWidthCommit
|
|
9257
|
+
onWidthCommit,
|
|
9258
|
+
position
|
|
8555
9259
|
]),
|
|
8556
9260
|
sidebarWidth
|
|
8557
9261
|
};
|
|
@@ -8657,6 +9361,7 @@ function useReviewCommentDrafts({ canCreateComment = true, comments, onCommentFi
|
|
|
8657
9361
|
setComments,
|
|
8658
9362
|
updateActiveReviewCommentDraft
|
|
8659
9363
|
]),
|
|
9364
|
+
focusComment,
|
|
8660
9365
|
focusCommentId,
|
|
8661
9366
|
focusCommentRequest,
|
|
8662
9367
|
resetCommentFocus,
|
|
@@ -8739,6 +9444,7 @@ const defaultConfigTemplate = {
|
|
|
8739
9444
|
"opencodeModel": "opencode-default",
|
|
8740
9445
|
"piModel": "pi-default",
|
|
8741
9446
|
"reviewCommentsPrefix": "# Address these Review Comments",
|
|
9447
|
+
"sidebarPosition": "left",
|
|
8742
9448
|
"showOutdated": false,
|
|
8743
9449
|
"showWhitespace": false,
|
|
8744
9450
|
"theme": "system",
|
|
@@ -8746,6 +9452,7 @@ const defaultConfigTemplate = {
|
|
|
8746
9452
|
"wordWrap": false
|
|
8747
9453
|
},
|
|
8748
9454
|
keymap: {
|
|
9455
|
+
"askAgent": "Mod+Alt+Enter",
|
|
8749
9456
|
"closeSearch": "Escape",
|
|
8750
9457
|
"commandBar": "Mod+Shift+p",
|
|
8751
9458
|
"diffSearch": "Mod+f",
|
|
@@ -8803,10 +9510,35 @@ const emptyReviewComments = [];
|
|
|
8803
9510
|
const emptyGeneralCommentThreads = [];
|
|
8804
9511
|
const emptyPaths = /* @__PURE__ */ new Set();
|
|
8805
9512
|
const emptyWalkthroughNotes = /* @__PURE__ */ new Map();
|
|
9513
|
+
const reviewSurfacePreferencesKey = "codiff:web-review-surface-preferences:v1";
|
|
9514
|
+
const getLocationHashTarget = () => {
|
|
9515
|
+
const hash = window.location.hash.slice(1);
|
|
9516
|
+
if (!hash) return null;
|
|
9517
|
+
try {
|
|
9518
|
+
return decodeURIComponent(hash);
|
|
9519
|
+
} catch {
|
|
9520
|
+
return hash;
|
|
9521
|
+
}
|
|
9522
|
+
};
|
|
9523
|
+
const commentMatchesHashTarget = (comment, target) => comment.id === target || comment.threadId === target || comment.url?.slice(comment.url.lastIndexOf("#") + 1) === target;
|
|
8806
9524
|
const readSharedSidebarWidth = () => typeof localStorage === "undefined" ? 292 : readSidebarWidth();
|
|
8807
9525
|
const writeSharedSidebarWidth = (width) => {
|
|
8808
9526
|
if (typeof localStorage !== "undefined") writeSidebarWidth(width);
|
|
8809
9527
|
};
|
|
9528
|
+
const readStoredSidebarCollapsed = () => {
|
|
9529
|
+
if (typeof localStorage === "undefined") return null;
|
|
9530
|
+
try {
|
|
9531
|
+
const stored = JSON.parse(localStorage.getItem(reviewSurfacePreferencesKey) ?? "{}");
|
|
9532
|
+
if (stored && typeof stored === "object" && "sidebarCollapsed" in stored && typeof stored.sidebarCollapsed === "boolean") return stored.sidebarCollapsed;
|
|
9533
|
+
} catch {}
|
|
9534
|
+
return null;
|
|
9535
|
+
};
|
|
9536
|
+
const writeStoredSidebarCollapsed = (sidebarCollapsed) => {
|
|
9537
|
+
if (typeof localStorage === "undefined") return;
|
|
9538
|
+
try {
|
|
9539
|
+
localStorage.setItem(reviewSurfacePreferencesKey, JSON.stringify({ sidebarCollapsed }));
|
|
9540
|
+
} catch {}
|
|
9541
|
+
};
|
|
8810
9542
|
const getSnapshotReviewComments = (snapshot) => {
|
|
8811
9543
|
if (!snapshot.reviewComments?.length) return emptyReviewComments;
|
|
8812
9544
|
return getReviewCommentsFromState({
|
|
@@ -8828,7 +9560,9 @@ const disabledCommitMessage = async () => ({
|
|
|
8828
9560
|
reason: "Shared walkthroughs are read-only.",
|
|
8829
9561
|
status: "unavailable"
|
|
8830
9562
|
});
|
|
8831
|
-
|
|
9563
|
+
const mobileSidebarMediaQuery = "(max-width: 720px)";
|
|
9564
|
+
const shouldCollapseSidebarInitially = () => readStoredSidebarCollapsed() ?? (typeof window !== "undefined" && window.matchMedia(mobileSidebarMediaQuery).matches);
|
|
9565
|
+
function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMode, interactive, onDeleteShare, onModeChange, providerLabel = "provider", repositoryUrl, settingsBar, sidebarPosition = "left", signInLabel = "Sign in to comment", snapshot, sourceDescriptionFooterAside, title }) {
|
|
8832
9566
|
const canComment = commenting?.canComment ?? Boolean(interactive);
|
|
8833
9567
|
const deleteShare = useCallback(async () => {
|
|
8834
9568
|
if (!onDeleteShare || !window.confirm("Delete this shared walkthrough? This cannot be undone.")) return;
|
|
@@ -8851,23 +9585,44 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8851
9585
|
const keymap = useMemo(() => createDefaultConfig().keymap, []);
|
|
8852
9586
|
const [fileSearchQuery, setFileSearchQuery] = useState("");
|
|
8853
9587
|
const [uncontrolledSidebarMode, setUncontrolledSidebarMode] = useState(() => initialMode ?? (interactive ? "tree" : "walkthrough"));
|
|
8854
|
-
const [sidebarCollapsed, setSidebarCollapsed] = useState(
|
|
9588
|
+
const [sidebarCollapsed, setSidebarCollapsed] = useState(null);
|
|
9589
|
+
const sidebarCollapsedRef = useRef(null);
|
|
9590
|
+
const sidebarInteractedRef = useRef(false);
|
|
9591
|
+
const updateSidebarCollapsed = useCallback((value, persist) => {
|
|
9592
|
+
sidebarCollapsedRef.current = value;
|
|
9593
|
+
setSidebarCollapsed(value);
|
|
9594
|
+
if (persist) writeStoredSidebarCollapsed(value);
|
|
9595
|
+
}, []);
|
|
9596
|
+
const toggleSidebar = useCallback(() => {
|
|
9597
|
+
sidebarInteractedRef.current = true;
|
|
9598
|
+
updateSidebarCollapsed(!(sidebarCollapsedRef.current ?? shouldCollapseSidebarInitially()), true);
|
|
9599
|
+
}, [updateSidebarCollapsed]);
|
|
9600
|
+
useEffect(() => {
|
|
9601
|
+
const timeout = window.setTimeout(() => {
|
|
9602
|
+
if (!sidebarInteractedRef.current) updateSidebarCollapsed(shouldCollapseSidebarInitially(), false);
|
|
9603
|
+
}, 0);
|
|
9604
|
+
return () => window.clearTimeout(timeout);
|
|
9605
|
+
}, [updateSidebarCollapsed]);
|
|
8855
9606
|
useEffect(() => {
|
|
8856
9607
|
const handleKeyDown = (event) => {
|
|
8857
9608
|
if (event.repeat || !matchesShortcut(event, keymap, "toggleSidebar")) return;
|
|
8858
9609
|
event.preventDefault();
|
|
8859
|
-
|
|
9610
|
+
toggleSidebar();
|
|
8860
9611
|
};
|
|
8861
9612
|
window.addEventListener("keydown", handleKeyDown);
|
|
8862
9613
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
8863
|
-
}, [keymap]);
|
|
9614
|
+
}, [keymap, toggleSidebar]);
|
|
8864
9615
|
const sidebarMode = Boolean(initialMode && onModeChange) && initialMode ? initialMode : uncontrolledSidebarMode;
|
|
8865
9616
|
const [treeScrollTarget, setTreeScrollTarget] = useState(null);
|
|
8866
9617
|
const { bumpItemVersion, collapsed, expandedGenerated, itemVersionByKey, selectedPath, setSelectedPath, toggleCollapsed, toggleViewed, viewed } = useReviewFileState({ initialSelectedPath: snapshot.files[0]?.path ?? null });
|
|
8867
9618
|
const { resizeSidebar, sidebarWidth } = useResizableSidebar({
|
|
8868
9619
|
collapseThreshold: 80,
|
|
8869
|
-
onCollapse: () =>
|
|
9620
|
+
onCollapse: () => {
|
|
9621
|
+
sidebarInteractedRef.current = true;
|
|
9622
|
+
updateSidebarCollapsed(true, true);
|
|
9623
|
+
},
|
|
8870
9624
|
onWidthCommit: writeSharedSidebarWidth,
|
|
9625
|
+
position: sidebarPosition,
|
|
8871
9626
|
readWidth: readSharedSidebarWidth
|
|
8872
9627
|
});
|
|
8873
9628
|
const snapshotReviewComments = useMemo(() => getSnapshotReviewComments(snapshot), [snapshot]);
|
|
@@ -8878,7 +9633,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8878
9633
|
} : comment), [editedReviewCommentBodies, snapshotReviewComments]);
|
|
8879
9634
|
const [localReviewComments, setLocalReviewComments] = useState(emptyReviewComments);
|
|
8880
9635
|
const reviewComments = useMemo(() => mergeReviewComments(visibleSnapshotReviewComments, localReviewComments), [localReviewComments, visibleSnapshotReviewComments]);
|
|
8881
|
-
const { activeReviewCommentDraftRef, activeReviewCommentDraftState, clearCommentFocus, createComment, deleteComment: deleteLocalComment, focusCommentId, focusCommentRequest, reviewCommentsRef, updateActiveReviewCommentDraft, updateComment } = useReviewCommentDrafts({
|
|
9636
|
+
const { activeReviewCommentDraftRef, activeReviewCommentDraftState, clearCommentFocus, createComment, deleteComment: deleteLocalComment, focusComment, focusCommentId, focusCommentRequest, reviewCommentsRef, updateActiveReviewCommentDraft, updateComment } = useReviewCommentDrafts({
|
|
8882
9637
|
canCreateComment: canComment,
|
|
8883
9638
|
comments: reviewComments,
|
|
8884
9639
|
onCommentFileChange: bumpItemVersion,
|
|
@@ -8904,6 +9659,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8904
9659
|
const walkthroughRequestPendingRef = useRef(false);
|
|
8905
9660
|
const [walkthroughRequestId, setWalkthroughRequestId] = useState(0);
|
|
8906
9661
|
const interactiveRef = useRef(interactive);
|
|
9662
|
+
const handledHashTargetRef = useRef(null);
|
|
8907
9663
|
const visibleFiles = useMemo(() => sortFiles(snapshot.files).filter((file) => fuzzyMatches(file.path, fileSearchQuery) && fileHasVisibleDiff(file, snapshot.preferences.showWhitespace)), [
|
|
8908
9664
|
fileSearchQuery,
|
|
8909
9665
|
snapshot.files,
|
|
@@ -9150,6 +9906,56 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9150
9906
|
request: (current?.request ?? 0) + 1
|
|
9151
9907
|
}));
|
|
9152
9908
|
}, [setSelectedPath]);
|
|
9909
|
+
const activateReviewComment = useCallback((comment) => {
|
|
9910
|
+
changeSidebarMode("tree");
|
|
9911
|
+
setSelectedPath(comment.filePath);
|
|
9912
|
+
focusComment(comment.id);
|
|
9913
|
+
setTreeScrollTarget((current) => ({
|
|
9914
|
+
behavior: "smooth",
|
|
9915
|
+
commentId: comment.id,
|
|
9916
|
+
path: comment.filePath,
|
|
9917
|
+
request: (current?.request ?? 0) + 1
|
|
9918
|
+
}));
|
|
9919
|
+
}, [
|
|
9920
|
+
changeSidebarMode,
|
|
9921
|
+
focusComment,
|
|
9922
|
+
setSelectedPath
|
|
9923
|
+
]);
|
|
9924
|
+
const activateHashTarget = useCallback(() => {
|
|
9925
|
+
const target = getLocationHashTarget();
|
|
9926
|
+
if (!target || handledHashTargetRef.current === target) return;
|
|
9927
|
+
const reviewComment = reviewComments.find((comment) => commentMatchesHashTarget(comment, target));
|
|
9928
|
+
if (reviewComment) {
|
|
9929
|
+
handledHashTargetRef.current = target;
|
|
9930
|
+
activateReviewComment(reviewComment);
|
|
9931
|
+
return;
|
|
9932
|
+
}
|
|
9933
|
+
for (const thread of generalCommentThreads) {
|
|
9934
|
+
const generalComment = thread.comments.find((comment) => commentMatchesHashTarget(comment, target)) ?? (thread.id === target ? thread.comments[0] : void 0);
|
|
9935
|
+
if (generalComment) {
|
|
9936
|
+
handledHashTargetRef.current = target;
|
|
9937
|
+
activateGeneralComment(generalComment.id);
|
|
9938
|
+
return;
|
|
9939
|
+
}
|
|
9940
|
+
}
|
|
9941
|
+
}, [
|
|
9942
|
+
activateGeneralComment,
|
|
9943
|
+
activateReviewComment,
|
|
9944
|
+
generalCommentThreads,
|
|
9945
|
+
reviewComments
|
|
9946
|
+
]);
|
|
9947
|
+
useEffect(() => {
|
|
9948
|
+
const timeout = window.setTimeout(activateHashTarget, 0);
|
|
9949
|
+
return () => window.clearTimeout(timeout);
|
|
9950
|
+
}, [activateHashTarget]);
|
|
9951
|
+
useEffect(() => {
|
|
9952
|
+
const handleHashChange = () => {
|
|
9953
|
+
handledHashTargetRef.current = null;
|
|
9954
|
+
activateHashTarget();
|
|
9955
|
+
};
|
|
9956
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
9957
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
9958
|
+
}, [activateHashTarget]);
|
|
9153
9959
|
const updateSelectedPathFromScroll = useCallback((viewer) => {
|
|
9154
9960
|
const nextPath = getSelectedPathFromScroll(viewer, visibleFiles, snapshot.preferences.showWhitespace);
|
|
9155
9961
|
if (nextPath) setSelectedPath((current) => current === nextPath ? current : nextPath);
|
|
@@ -9328,9 +10134,10 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9328
10134
|
children: settingsBar
|
|
9329
10135
|
}) : null] }) : void 0;
|
|
9330
10136
|
return /* @__PURE__ */ jsxs("div", {
|
|
9331
|
-
className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}${sidebarCollapsed ? " sidebar-collapsed" : ""}`,
|
|
10137
|
+
className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}${sidebarCollapsed === null ? " sidebar-auto" : sidebarCollapsed ? " sidebar-collapsed" : ""}`,
|
|
10138
|
+
"data-sidebar-position": sidebarPosition,
|
|
9332
10139
|
"data-theme": shellTheme,
|
|
9333
|
-
style: sidebarCollapsed ? void 0 : { gridTemplateColumns: `${sidebarWidth}px 0 minmax(0, 1fr)` },
|
|
10140
|
+
style: sidebarCollapsed !== false ? void 0 : { gridTemplateColumns: sidebarPosition === "right" ? `minmax(0, 1fr) 0 ${sidebarWidth}px` : `${sidebarWidth}px 0 minmax(0, 1fr)` },
|
|
9334
10141
|
children: [
|
|
9335
10142
|
/* @__PURE__ */ jsx(ReviewTopBar, {
|
|
9336
10143
|
actions: topBarActions,
|
|
@@ -9371,7 +10178,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9371
10178
|
mode: sidebarMode,
|
|
9372
10179
|
modes: reviewModes,
|
|
9373
10180
|
onModeChange: changeSidebarMode,
|
|
9374
|
-
onToggleSidebar:
|
|
10181
|
+
onToggleSidebar: toggleSidebar,
|
|
9375
10182
|
repository: repositoryLinkUrl ? /* @__PURE__ */ jsx("a", {
|
|
9376
10183
|
className: "review-top-bar-repository",
|
|
9377
10184
|
href: repositoryLinkUrl,
|
|
@@ -9383,7 +10190,8 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9383
10190
|
children: rootLabel
|
|
9384
10191
|
}),
|
|
9385
10192
|
repositoryTooltip: snapshot.repository.root,
|
|
9386
|
-
sidebarCollapsed,
|
|
10193
|
+
sidebarCollapsed: sidebarCollapsed ?? false,
|
|
10194
|
+
sidebarPosition,
|
|
9387
10195
|
toggleTitle: `${sidebarCollapsed ? "Expand" : "Collapse"} sidebar`
|
|
9388
10196
|
}),
|
|
9389
10197
|
/* @__PURE__ */ jsxs("aside", {
|
|
@@ -9461,6 +10269,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9461
10269
|
onCancelEdit: cancelEditGeneralComment,
|
|
9462
10270
|
onChangeDraft: setGeneralCommentDraft,
|
|
9463
10271
|
onChangeEditDraft: setGeneralCommentEditDraft,
|
|
10272
|
+
onResolveDiscussion: resolveDiscussion,
|
|
9464
10273
|
onSaveEdit: saveGeneralCommentEdit,
|
|
9465
10274
|
onStartEdit: startEditGeneralComment,
|
|
9466
10275
|
onSubmit: submitGeneralComment,
|