@nkzw/codiff-core 1.10.0 → 1.11.1
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 +3 -1
- package/dist/app/components/DefinitionPopover.d.ts +12 -0
- package/dist/app/components/Panels.d.ts +5 -3
- 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 +1105 -265
- package/dist/share.mjs +8 -7
- package/dist/style.css +286 -45
- package/dist/types.d.ts +28 -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;
|
|
@@ -2442,14 +2633,16 @@ function PullRequestReviewAction({ disabled, event, hasPendingComments = false,
|
|
|
2442
2633
|
}) : null]
|
|
2443
2634
|
});
|
|
2444
2635
|
}
|
|
2445
|
-
function PullRequestReviewButtons({ children, disabled, hasPendingComments, onClosePullRequest, onSubmitReview, reviewStatus, showCommentReview = false }) {
|
|
2636
|
+
function PullRequestReviewButtons({ children, disabled, hasPendingComments, onClosePullRequest, onMarkPullRequestReady, onSubmitReview, reviewStatus, showCommentReview = false }) {
|
|
2446
2637
|
const approveBlocked = isPullRequestReviewActionDisabled(reviewStatus, "APPROVE");
|
|
2447
2638
|
const commentBlocked = isPullRequestReviewActionDisabled(reviewStatus, "COMMENT");
|
|
2448
2639
|
const requestChangesBlocked = isPullRequestReviewActionDisabled(reviewStatus, "REQUEST_CHANGES");
|
|
2449
2640
|
const closeStatus = reviewStatus?.close;
|
|
2450
2641
|
const closeVisible = onClosePullRequest && closeStatus && closeStatus.disabled !== true;
|
|
2642
|
+
const markReadyStatus = reviewStatus?.markReady;
|
|
2643
|
+
const markReadyVisible = onMarkPullRequestReady && markReadyStatus && markReadyStatus.disabled !== true;
|
|
2451
2644
|
const commentVisible = showCommentReview && !commentBlocked;
|
|
2452
|
-
const hasReviewActions = commentVisible || !approveBlocked || !requestChangesBlocked || closeVisible;
|
|
2645
|
+
const hasReviewActions = commentVisible || !approveBlocked || !requestChangesBlocked || markReadyVisible || closeVisible;
|
|
2453
2646
|
if (!hasReviewActions && !children) return null;
|
|
2454
2647
|
return /* @__PURE__ */ jsxs("div", {
|
|
2455
2648
|
"aria-label": hasReviewActions ? "Pull request review actions" : "Pull request status",
|
|
@@ -2496,6 +2689,21 @@ function PullRequestReviewButtons({ children, disabled, hasPendingComments, onCl
|
|
|
2496
2689
|
onSubmitReview,
|
|
2497
2690
|
title: getPullRequestReviewActionTitle(reviewStatus, "REQUEST_CHANGES", "Request changes")
|
|
2498
2691
|
}) : null,
|
|
2692
|
+
markReadyVisible ? /* @__PURE__ */ jsxs(Button, {
|
|
2693
|
+
action: onMarkPullRequestReady,
|
|
2694
|
+
"aria-label": "Mark merge request as ready",
|
|
2695
|
+
className: "review-submit-button ready",
|
|
2696
|
+
disabled,
|
|
2697
|
+
pendingPlaceholder: "Marking ready…",
|
|
2698
|
+
title: markReadyStatus.reason ?? "Mark merge request as ready",
|
|
2699
|
+
type: "button",
|
|
2700
|
+
children: [/* @__PURE__ */ jsx(CheckCircleIcon, {
|
|
2701
|
+
"aria-hidden": true,
|
|
2702
|
+
className: "review-submit-icon ready",
|
|
2703
|
+
size: 15,
|
|
2704
|
+
weight: "bold"
|
|
2705
|
+
}), /* @__PURE__ */ jsx("span", { children: "Mark ready" })]
|
|
2706
|
+
}) : null,
|
|
2499
2707
|
closeVisible ? /* @__PURE__ */ jsxs(Button, {
|
|
2500
2708
|
action: onClosePullRequest,
|
|
2501
2709
|
"aria-label": "Close merge request",
|
|
@@ -3347,6 +3555,7 @@ setCustomExtension("cts", "typescript");
|
|
|
3347
3555
|
setCustomExtension("mts", "typescript");
|
|
3348
3556
|
const statusLabel = {
|
|
3349
3557
|
added: "Added",
|
|
3558
|
+
conflicted: "Conflicted",
|
|
3350
3559
|
deleted: "Deleted",
|
|
3351
3560
|
modified: "Modified",
|
|
3352
3561
|
renamed: "Renamed",
|
|
@@ -3479,6 +3688,20 @@ const codeViewUnsafeCSS = `
|
|
|
3479
3688
|
cursor: progress;
|
|
3480
3689
|
}
|
|
3481
3690
|
|
|
3691
|
+
:host([data-codiff-definition-mode]) [data-codiff-identifier] {
|
|
3692
|
+
cursor: pointer;
|
|
3693
|
+
text-decoration-color: color-mix(in srgb, currentColor 38%, transparent);
|
|
3694
|
+
text-decoration-line: underline;
|
|
3695
|
+
text-decoration-style: dotted;
|
|
3696
|
+
text-decoration-thickness: 1px;
|
|
3697
|
+
text-underline-offset: 3px;
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
:host([data-codiff-definition-mode]) [data-codiff-identifier]:hover {
|
|
3701
|
+
text-decoration-color: currentColor;
|
|
3702
|
+
text-decoration-style: solid;
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3482
3705
|
.codiff-search-mark {
|
|
3483
3706
|
background: var(--diffs-find-highlight-bg, rgb(255 216 92 / 0.65));
|
|
3484
3707
|
border-radius: 3px;
|
|
@@ -3505,8 +3728,179 @@ const codeViewUnsafeCSS = `
|
|
|
3505
3728
|
[data-selected-line] [data-gutter-utility-slot] {
|
|
3506
3729
|
display: none;
|
|
3507
3730
|
}
|
|
3731
|
+
|
|
3732
|
+
/* The imperative API wraps annotations in a node that resets white-space, but
|
|
3733
|
+
the React API slots them in bare, so they inherit "pre" from the shadow
|
|
3734
|
+
<pre> and prose never wraps. */
|
|
3735
|
+
[data-annotation-content] slot::slotted(*) {
|
|
3736
|
+
white-space: normal;
|
|
3737
|
+
}
|
|
3508
3738
|
`;
|
|
3509
3739
|
//#endregion
|
|
3740
|
+
//#region lib/identifier-navigation.ts
|
|
3741
|
+
const identifierPattern = /[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*/gu;
|
|
3742
|
+
const exactIdentifierPattern = /^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u;
|
|
3743
|
+
const identifierContainedPattern = /[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*/u;
|
|
3744
|
+
const commonKeywords = /* @__PURE__ */ new Set([
|
|
3745
|
+
"abstract",
|
|
3746
|
+
"as",
|
|
3747
|
+
"async",
|
|
3748
|
+
"await",
|
|
3749
|
+
"break",
|
|
3750
|
+
"case",
|
|
3751
|
+
"catch",
|
|
3752
|
+
"class",
|
|
3753
|
+
"const",
|
|
3754
|
+
"continue",
|
|
3755
|
+
"def",
|
|
3756
|
+
"default",
|
|
3757
|
+
"defer",
|
|
3758
|
+
"do",
|
|
3759
|
+
"else",
|
|
3760
|
+
"enum",
|
|
3761
|
+
"export",
|
|
3762
|
+
"extends",
|
|
3763
|
+
"false",
|
|
3764
|
+
"finally",
|
|
3765
|
+
"fn",
|
|
3766
|
+
"for",
|
|
3767
|
+
"from",
|
|
3768
|
+
"func",
|
|
3769
|
+
"function",
|
|
3770
|
+
"if",
|
|
3771
|
+
"implements",
|
|
3772
|
+
"import",
|
|
3773
|
+
"in",
|
|
3774
|
+
"interface",
|
|
3775
|
+
"let",
|
|
3776
|
+
"match",
|
|
3777
|
+
"mod",
|
|
3778
|
+
"namespace",
|
|
3779
|
+
"new",
|
|
3780
|
+
"nil",
|
|
3781
|
+
"none",
|
|
3782
|
+
"null",
|
|
3783
|
+
"package",
|
|
3784
|
+
"pass",
|
|
3785
|
+
"private",
|
|
3786
|
+
"protected",
|
|
3787
|
+
"pub",
|
|
3788
|
+
"public",
|
|
3789
|
+
"return",
|
|
3790
|
+
"self",
|
|
3791
|
+
"static",
|
|
3792
|
+
"struct",
|
|
3793
|
+
"super",
|
|
3794
|
+
"switch",
|
|
3795
|
+
"this",
|
|
3796
|
+
"throw",
|
|
3797
|
+
"throws",
|
|
3798
|
+
"trait",
|
|
3799
|
+
"true",
|
|
3800
|
+
"try",
|
|
3801
|
+
"type",
|
|
3802
|
+
"typeof",
|
|
3803
|
+
"undefined",
|
|
3804
|
+
"use",
|
|
3805
|
+
"var",
|
|
3806
|
+
"void",
|
|
3807
|
+
"while",
|
|
3808
|
+
"with",
|
|
3809
|
+
"yield"
|
|
3810
|
+
]);
|
|
3811
|
+
const identifierNavigationAttribute = "data-codiff-identifier";
|
|
3812
|
+
const definitionModifierAttribute = "data-codiff-definition-mode";
|
|
3813
|
+
const nonCodeScopePattern = /(^|\s)(comment|string)([.\s]|$)/;
|
|
3814
|
+
const nonCodeTokenColors = new Set([dunkel_default, licht_default].flatMap((theme) => theme.tokenColors.flatMap((token) => {
|
|
3815
|
+
const scopes = Array.isArray(token.scope) ? token.scope : [token.scope];
|
|
3816
|
+
const foreground = token.settings.foreground?.toLowerCase();
|
|
3817
|
+
return foreground && scopes.some((scope) => typeof scope === "string" && nonCodeScopePattern.test(scope)) ? [foreground] : [];
|
|
3818
|
+
})));
|
|
3819
|
+
const isNavigableIdentifierToken = (text) => {
|
|
3820
|
+
return exactIdentifierPattern.test(text) && !commonKeywords.has(text.toLowerCase());
|
|
3821
|
+
};
|
|
3822
|
+
const isNonCodeSyntaxToken = (element) => {
|
|
3823
|
+
const style = element?.getAttribute("style")?.toLowerCase() ?? "";
|
|
3824
|
+
return [...nonCodeTokenColors].some((color) => style.includes(color));
|
|
3825
|
+
};
|
|
3826
|
+
const clearIdentifierNavigationWrappers = (root) => {
|
|
3827
|
+
for (const wrapper of Array.from(root.querySelectorAll(`[${identifierNavigationAttribute}]`))) {
|
|
3828
|
+
const parent = wrapper.parentElement;
|
|
3829
|
+
wrapper.replaceWith(document.createTextNode(wrapper.textContent ?? ""));
|
|
3830
|
+
parent?.normalize();
|
|
3831
|
+
}
|
|
3832
|
+
};
|
|
3833
|
+
const wrapIdentifierNavigationText = (root) => {
|
|
3834
|
+
const textNodes = [];
|
|
3835
|
+
for (const line of Array.from(root.querySelectorAll("[data-line]"))) {
|
|
3836
|
+
const walker = document.createTreeWalker(line, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
|
|
3837
|
+
const parent = node.parentElement;
|
|
3838
|
+
return node.textContent && identifierContainedPattern.test(node.textContent) && !parent?.closest(`[${identifierNavigationAttribute}], mark.codiff-search-mark`) && !isNonCodeSyntaxToken(parent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
3839
|
+
} });
|
|
3840
|
+
let node = walker.nextNode();
|
|
3841
|
+
while (node) {
|
|
3842
|
+
textNodes.push(node);
|
|
3843
|
+
node = walker.nextNode();
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
for (const textNode of textNodes) {
|
|
3847
|
+
const text = textNode.textContent ?? "";
|
|
3848
|
+
const fragment = document.createDocumentFragment();
|
|
3849
|
+
let offset = 0;
|
|
3850
|
+
let wrapped = false;
|
|
3851
|
+
for (const match of text.matchAll(identifierPattern)) {
|
|
3852
|
+
const identifier = match[0];
|
|
3853
|
+
if (!isNavigableIdentifierToken(identifier)) continue;
|
|
3854
|
+
if (match.index > offset) fragment.append(document.createTextNode(text.slice(offset, match.index)));
|
|
3855
|
+
const wrapper = document.createElement("span");
|
|
3856
|
+
wrapper.setAttribute(identifierNavigationAttribute, "");
|
|
3857
|
+
wrapper.textContent = identifier;
|
|
3858
|
+
fragment.append(wrapper);
|
|
3859
|
+
offset = match.index + identifier.length;
|
|
3860
|
+
wrapped = true;
|
|
3861
|
+
}
|
|
3862
|
+
if (!wrapped) continue;
|
|
3863
|
+
if (offset < text.length) fragment.append(document.createTextNode(text.slice(offset)));
|
|
3864
|
+
textNode.replaceWith(fragment);
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3867
|
+
const applyIdentifierNavigationState = (renderedItems, active) => {
|
|
3868
|
+
for (const { element } of renderedItems) {
|
|
3869
|
+
element.toggleAttribute(definitionModifierAttribute, active);
|
|
3870
|
+
const root = element.shadowRoot;
|
|
3871
|
+
if (!root) continue;
|
|
3872
|
+
clearIdentifierNavigationWrappers(root);
|
|
3873
|
+
if (active) wrapIdentifierNavigationText(root);
|
|
3874
|
+
}
|
|
3875
|
+
};
|
|
3876
|
+
const getIdentifierAtOffset = (text, offset) => {
|
|
3877
|
+
for (const match of text.matchAll(identifierPattern)) {
|
|
3878
|
+
const start = match.index;
|
|
3879
|
+
const end = start + match[0].length;
|
|
3880
|
+
if (offset >= start && offset < end) return {
|
|
3881
|
+
identifier: match[0],
|
|
3882
|
+
start
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
return null;
|
|
3886
|
+
};
|
|
3887
|
+
const getIdentifierFromPointerEvent = (event, lineElement) => {
|
|
3888
|
+
const caretRange = document.caretRangeFromPoint?.(event.clientX, event.clientY);
|
|
3889
|
+
if (caretRange && lineElement.contains(caretRange.startContainer)) {
|
|
3890
|
+
const prefix = document.createRange();
|
|
3891
|
+
prefix.selectNodeContents(lineElement);
|
|
3892
|
+
prefix.setEnd(caretRange.startContainer, caretRange.startOffset);
|
|
3893
|
+
const match = getIdentifierAtOffset(lineElement.textContent || "", prefix.toString().length);
|
|
3894
|
+
if (match) return match.identifier;
|
|
3895
|
+
}
|
|
3896
|
+
const target = event.target;
|
|
3897
|
+
if (target && lineElement.contains(target)) {
|
|
3898
|
+
const matches = [...(target.textContent || "").matchAll(identifierPattern)];
|
|
3899
|
+
if (matches.length === 1) return matches[0][0];
|
|
3900
|
+
}
|
|
3901
|
+
return null;
|
|
3902
|
+
};
|
|
3903
|
+
//#endregion
|
|
3510
3904
|
//#region lib/item-version.ts
|
|
3511
3905
|
const getItemVersion = (value) => {
|
|
3512
3906
|
let hash = 0;
|
|
@@ -3522,6 +3916,8 @@ const isNativeInputTarget = (target) => {
|
|
|
3522
3916
|
const candidate = target;
|
|
3523
3917
|
return candidate?.closest?.("input, select, textarea") != null || candidate?.isContentEditable === true;
|
|
3524
3918
|
};
|
|
3919
|
+
const isMacPlatform = (platform = navigator.platform) => platform.toLowerCase().includes("mac");
|
|
3920
|
+
const isPrimaryModifier = (event, platform = navigator.platform) => isMacPlatform(platform) ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
|
3525
3921
|
//#endregion
|
|
3526
3922
|
//#region lib/review-identity.ts
|
|
3527
3923
|
const getFileReviewIdentity = (file) => ({
|
|
@@ -3601,6 +3997,101 @@ const applySearchHighlights = (renderedItems, query, activeMatch) => {
|
|
|
3601
3997
|
}
|
|
3602
3998
|
};
|
|
3603
3999
|
//#endregion
|
|
4000
|
+
//#region lib/source.ts
|
|
4001
|
+
const rangeLabel = (source) => `${source.base}${source.symmetric ? "..." : ".."}${source.head}`;
|
|
4002
|
+
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";
|
|
4003
|
+
const getShortRef = (ref) => ref.slice(0, 7);
|
|
4004
|
+
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";
|
|
4005
|
+
//#endregion
|
|
4006
|
+
//#region app/components/DefinitionPopover.tsx
|
|
4007
|
+
function DefinitionPopover({ anchor, getDestination, identifier, onClose, onOpen, result }) {
|
|
4008
|
+
const popoverRef = useRef(null);
|
|
4009
|
+
useEffect(() => {
|
|
4010
|
+
const closeOnEscape = (event) => {
|
|
4011
|
+
if (event.key === "Escape") onClose();
|
|
4012
|
+
};
|
|
4013
|
+
const closeOutside = (event) => {
|
|
4014
|
+
const target = event.target;
|
|
4015
|
+
if (target && !popoverRef.current?.contains(target)) onClose();
|
|
4016
|
+
};
|
|
4017
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
4018
|
+
document.addEventListener("pointerdown", closeOutside);
|
|
4019
|
+
return () => {
|
|
4020
|
+
document.removeEventListener("keydown", closeOnEscape);
|
|
4021
|
+
document.removeEventListener("pointerdown", closeOutside);
|
|
4022
|
+
};
|
|
4023
|
+
}, [onClose]);
|
|
4024
|
+
const left = Math.min(anchor.x, Math.max(12, window.innerWidth - 432));
|
|
4025
|
+
const top = Math.min(anchor.y + 10, Math.max(12, window.innerHeight - 320));
|
|
4026
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
4027
|
+
"aria-label": `Definitions for ${identifier}`,
|
|
4028
|
+
className: "definition-popover",
|
|
4029
|
+
ref: popoverRef,
|
|
4030
|
+
role: "dialog",
|
|
4031
|
+
style: {
|
|
4032
|
+
left,
|
|
4033
|
+
top
|
|
4034
|
+
},
|
|
4035
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4036
|
+
className: "definition-popover-header",
|
|
4037
|
+
children: [/* @__PURE__ */ jsxs("span", { children: ["Definition of ", /* @__PURE__ */ jsx("code", { children: identifier })] }), /* @__PURE__ */ jsx("button", {
|
|
4038
|
+
"aria-label": "Close definition results",
|
|
4039
|
+
onClick: onClose,
|
|
4040
|
+
type: "button",
|
|
4041
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: 14 })
|
|
4042
|
+
})]
|
|
4043
|
+
}), !result ? /* @__PURE__ */ jsx("div", {
|
|
4044
|
+
className: "definition-popover-message",
|
|
4045
|
+
children: "Searching…"
|
|
4046
|
+
}) : result.status === "unavailable" ? /* @__PURE__ */ jsx("div", {
|
|
4047
|
+
className: "definition-popover-message",
|
|
4048
|
+
children: result.reason
|
|
4049
|
+
}) : result.candidates.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
4050
|
+
className: "definition-popover-message",
|
|
4051
|
+
children: "No likely definitions found."
|
|
4052
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
4053
|
+
className: "definition-popover-results",
|
|
4054
|
+
children: result.candidates.map((candidate) => {
|
|
4055
|
+
const destination = getDestination(candidate);
|
|
4056
|
+
const destinationLabel = destination === "diff" ? "Jump within diff" : destination === "editor" ? "Open in editor" : "Unavailable outside this historical diff";
|
|
4057
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
4058
|
+
className: "definition-popover-result",
|
|
4059
|
+
disabled: destination === "unavailable",
|
|
4060
|
+
onClick: () => onOpen(candidate),
|
|
4061
|
+
type: "button",
|
|
4062
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
4063
|
+
className: "definition-popover-location",
|
|
4064
|
+
children: [
|
|
4065
|
+
candidate.path,
|
|
4066
|
+
":",
|
|
4067
|
+
candidate.lineNumber,
|
|
4068
|
+
/* @__PURE__ */ jsxs("span", {
|
|
4069
|
+
className: "definition-popover-meta",
|
|
4070
|
+
children: [/* @__PURE__ */ jsx("span", { children: candidate.kind }), /* @__PURE__ */ jsx("span", {
|
|
4071
|
+
"aria-label": destinationLabel,
|
|
4072
|
+
className: "definition-popover-destination",
|
|
4073
|
+
role: "img",
|
|
4074
|
+
title: destinationLabel,
|
|
4075
|
+
children: destination === "diff" ? /* @__PURE__ */ jsx(CrosshairSimpleIcon, {
|
|
4076
|
+
"aria-hidden": "true",
|
|
4077
|
+
size: 13
|
|
4078
|
+
}) : destination === "editor" ? /* @__PURE__ */ jsx(ArrowSquareOutIcon, {
|
|
4079
|
+
"aria-hidden": "true",
|
|
4080
|
+
size: 13
|
|
4081
|
+
}) : /* @__PURE__ */ jsx(ProhibitIcon, {
|
|
4082
|
+
"aria-hidden": "true",
|
|
4083
|
+
size: 13
|
|
4084
|
+
})
|
|
4085
|
+
})]
|
|
4086
|
+
})
|
|
4087
|
+
]
|
|
4088
|
+
}), /* @__PURE__ */ jsx("code", { children: candidate.line })]
|
|
4089
|
+
}, `${candidate.path}:${candidate.lineNumber}`);
|
|
4090
|
+
})
|
|
4091
|
+
})]
|
|
4092
|
+
}), document.body);
|
|
4093
|
+
}
|
|
4094
|
+
//#endregion
|
|
3604
4095
|
//#region app/components/MarkdownDocumentEditor.tsx
|
|
3605
4096
|
const persistenceAdapter = { save: ({ content, document }) => window.codiff.saveMarkdownDocument({
|
|
3606
4097
|
baseVersion: document.version,
|
|
@@ -3717,12 +4208,6 @@ const RepositoryMarkdownEditor = forwardRef(function RepositoryMarkdownEditor({
|
|
|
3717
4208
|
});
|
|
3718
4209
|
});
|
|
3719
4210
|
//#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
4211
|
//#region lib/narrative-walkthrough.ts
|
|
3727
4212
|
const walkthroughFileName = (path) => path.split("/").pop() ?? path;
|
|
3728
4213
|
const uniquePaths = (paths) => {
|
|
@@ -4145,7 +4630,8 @@ const chapterIcons = {
|
|
|
4145
4630
|
wrench: WrenchIcon
|
|
4146
4631
|
};
|
|
4147
4632
|
function ChapterIcon({ icon, size = 13 }) {
|
|
4148
|
-
|
|
4633
|
+
const Icon = chapterIcons[icon] ?? chapterIcons.path;
|
|
4634
|
+
return /* @__PURE__ */ jsx(Icon, { size });
|
|
4149
4635
|
}
|
|
4150
4636
|
function ImportancePill({ importance }) {
|
|
4151
4637
|
return /* @__PURE__ */ jsx("span", {
|
|
@@ -4430,13 +4916,14 @@ function WalkthroughProgress({ phase, responseLabelIndex, stageRevision }) {
|
|
|
4430
4916
|
}, [stageRevision]);
|
|
4431
4917
|
const elapsedSeconds = timerState.stageRevision === stageRevision ? timerState.elapsedSeconds : 0;
|
|
4432
4918
|
const showTimer = elapsedSeconds >= TIMER_THRESHOLD_SECONDS;
|
|
4919
|
+
const label = phase === "agent-generation" ? "Analyzing changes…" : phase === "response-received" ? walkthroughResponseLabels[Math.abs(responseLabelIndex) % walkthroughResponseLabels.length] : "Generating walkthrough…";
|
|
4433
4920
|
return /* @__PURE__ */ jsxs("span", {
|
|
4434
4921
|
"aria-live": "polite",
|
|
4435
4922
|
className: "walkthrough-progress",
|
|
4436
4923
|
role: "status",
|
|
4437
4924
|
children: [/* @__PURE__ */ jsx("span", {
|
|
4438
4925
|
className: "walkthrough-progress-label",
|
|
4439
|
-
children:
|
|
4926
|
+
children: label
|
|
4440
4927
|
}), /* @__PURE__ */ jsx("span", {
|
|
4441
4928
|
"aria-hidden": !showTimer,
|
|
4442
4929
|
className: `walkthrough-progress-timer${showTimer ? " visible" : ""}`,
|
|
@@ -5476,6 +5963,9 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5476
5963
|
flushDraft,
|
|
5477
5964
|
onCommentBlur
|
|
5478
5965
|
]);
|
|
5966
|
+
const handleAddComment = useCallback(() => {
|
|
5967
|
+
document.activeElement?.blur();
|
|
5968
|
+
}, []);
|
|
5479
5969
|
const handleFocus = useCallback(() => {
|
|
5480
5970
|
onCommentFocus(draftComment);
|
|
5481
5971
|
}, [draftComment, onCommentFocus]);
|
|
@@ -5491,13 +5981,21 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5491
5981
|
]);
|
|
5492
5982
|
const handleKeyDown = useCallback((event) => {
|
|
5493
5983
|
if (matchesShortcut(event, keymap, "submitComment")) {
|
|
5494
|
-
if (supportsReviewCommentActions
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5984
|
+
if (supportsReviewCommentActions) {
|
|
5985
|
+
if (commentCanSubmit) {
|
|
5986
|
+
event.preventDefault();
|
|
5987
|
+
event.stopPropagation();
|
|
5988
|
+
handleSubmitComment();
|
|
5989
|
+
}
|
|
5498
5990
|
return;
|
|
5499
5991
|
}
|
|
5500
|
-
|
|
5992
|
+
event.preventDefault();
|
|
5993
|
+
event.stopPropagation();
|
|
5994
|
+
handleAddComment();
|
|
5995
|
+
return;
|
|
5996
|
+
}
|
|
5997
|
+
if (matchesShortcut(event, keymap, "askAgent")) {
|
|
5998
|
+
if (canAskCodex) {
|
|
5501
5999
|
event.preventDefault();
|
|
5502
6000
|
event.stopPropagation();
|
|
5503
6001
|
handleAskCodex();
|
|
@@ -5515,6 +6013,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5515
6013
|
comment.id,
|
|
5516
6014
|
comment.isReadOnly,
|
|
5517
6015
|
draft,
|
|
6016
|
+
handleAddComment,
|
|
5518
6017
|
handleAskCodex,
|
|
5519
6018
|
handleSubmitComment,
|
|
5520
6019
|
keymap,
|
|
@@ -5570,7 +6069,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5570
6069
|
className: "review-comment-action",
|
|
5571
6070
|
disabled: !canAskCodex,
|
|
5572
6071
|
onClick: handleAskCodex,
|
|
5573
|
-
title: canAskCodex ? `Ask ${agentLabel}` : `Write a note before asking ${agentLabel}`,
|
|
6072
|
+
title: canAskCodex ? `Ask ${agentLabel} (${getShortcutLabel(keymap, "askAgent")})` : `Write a note before asking ${agentLabel}`,
|
|
5574
6073
|
type: "button",
|
|
5575
6074
|
children: [/* @__PURE__ */ jsx("img", {
|
|
5576
6075
|
alt: "",
|
|
@@ -5732,7 +6231,7 @@ const groupReviewCommentsByThread = (comments) => {
|
|
|
5732
6231
|
};
|
|
5733
6232
|
const noopResolveThread = () => {};
|
|
5734
6233
|
const noopLayoutReady = () => {};
|
|
5735
|
-
function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
6234
|
+
function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusCommentRequest, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
5736
6235
|
const [resolveState, setResolveState] = useState({
|
|
5737
6236
|
error: null,
|
|
5738
6237
|
submitting: false,
|
|
@@ -5741,10 +6240,13 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5741
6240
|
const lastComment = comments.at(-1);
|
|
5742
6241
|
const threadId = lastComment?.threadId;
|
|
5743
6242
|
const threadResolved = comments.some((comment) => comment.isThreadResolved === true);
|
|
6243
|
+
const hasFocusedComment = focusCommentId != null && comments.some((comment) => comment.id === focusCommentId);
|
|
6244
|
+
const [resolutionOverride, setResolutionOverride] = useState(null);
|
|
6245
|
+
const effectiveThreadResolved = resolutionOverride?.base === threadResolved ? resolutionOverride.value : threadResolved;
|
|
6246
|
+
const resolving = resolveState.threadId === threadId && resolveState.submitting;
|
|
5744
6247
|
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) && !
|
|
6248
|
+
const canReplyToThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.isReadOnly) && !comments.some((comment) => !comment.isReadOnly) && !comments.some((comment) => comment.canReplyThread === false) && !effectiveThreadResolved && !resolving;
|
|
5746
6249
|
const hasThreadActions = canReplyToThread || canResolveThread;
|
|
5747
|
-
const resolving = resolveState.threadId === threadId && resolveState.submitting;
|
|
5748
6250
|
const resolveError = resolveState.threadId === threadId ? resolveState.error : null;
|
|
5749
6251
|
const handleReply = useCallback(() => {
|
|
5750
6252
|
if (!threadId || !lastComment) return;
|
|
@@ -5756,16 +6258,22 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5756
6258
|
]);
|
|
5757
6259
|
const handleResolve = useCallback(() => {
|
|
5758
6260
|
if (!threadId || resolving) return;
|
|
6261
|
+
const nextResolved = !effectiveThreadResolved;
|
|
6262
|
+
setResolutionOverride({
|
|
6263
|
+
base: threadResolved,
|
|
6264
|
+
value: nextResolved
|
|
6265
|
+
});
|
|
5759
6266
|
setResolveState({
|
|
5760
6267
|
error: null,
|
|
5761
6268
|
submitting: true,
|
|
5762
6269
|
threadId
|
|
5763
6270
|
});
|
|
5764
|
-
Promise.resolve(onResolveThread(threadId,
|
|
6271
|
+
Promise.resolve(onResolveThread(threadId, nextResolved)).then(() => setResolveState({
|
|
5765
6272
|
error: null,
|
|
5766
6273
|
submitting: false,
|
|
5767
6274
|
threadId
|
|
5768
6275
|
})).catch((error) => {
|
|
6276
|
+
setResolutionOverride(null);
|
|
5769
6277
|
setResolveState({
|
|
5770
6278
|
error: error instanceof Error ? error.message : String(error),
|
|
5771
6279
|
submitting: false,
|
|
@@ -5773,54 +6281,64 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5773
6281
|
});
|
|
5774
6282
|
});
|
|
5775
6283
|
}, [
|
|
6284
|
+
effectiveThreadResolved,
|
|
5776
6285
|
onResolveThread,
|
|
5777
6286
|
resolving,
|
|
5778
6287
|
threadId,
|
|
5779
6288
|
threadResolved
|
|
5780
6289
|
]);
|
|
5781
|
-
|
|
6290
|
+
const threadContent = /* @__PURE__ */ jsxs(Fragment$1, { children: [comments.map((comment) => {
|
|
6291
|
+
const displayName = comment.author ? getReviewAuthorDisplayName(comment.author) : getGitIdentityDisplayName(identity);
|
|
6292
|
+
return /* @__PURE__ */ jsx(ReviewCommentEditor, {
|
|
6293
|
+
agentId,
|
|
6294
|
+
agentLabel,
|
|
6295
|
+
comment,
|
|
6296
|
+
displayName,
|
|
6297
|
+
focusCommentId,
|
|
6298
|
+
focusEditorRef,
|
|
6299
|
+
identity,
|
|
6300
|
+
keymap,
|
|
6301
|
+
onAskCodex,
|
|
6302
|
+
onCommentBlur,
|
|
6303
|
+
onCommentDraftChange,
|
|
6304
|
+
onCommentFocus,
|
|
6305
|
+
onDeleteComment,
|
|
6306
|
+
onSaveCommentEdit,
|
|
6307
|
+
onSubmitComment,
|
|
6308
|
+
onUpdateComment,
|
|
6309
|
+
supportsReviewCommentActions
|
|
6310
|
+
}, comment.id);
|
|
6311
|
+
}), hasThreadActions ? /* @__PURE__ */ jsxs("div", {
|
|
6312
|
+
className: "review-comment-thread-footer",
|
|
6313
|
+
children: [resolveError ? /* @__PURE__ */ jsx("span", {
|
|
6314
|
+
className: "review-comment-thread-error",
|
|
6315
|
+
children: resolveError
|
|
6316
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
6317
|
+
className: "review-comment-thread-actions",
|
|
6318
|
+
children: [canReplyToThread ? /* @__PURE__ */ jsx("button", {
|
|
6319
|
+
className: "review-comment-action",
|
|
6320
|
+
onClick: handleReply,
|
|
6321
|
+
type: "button",
|
|
6322
|
+
children: "Reply"
|
|
6323
|
+
}) : null, canResolveThread ? /* @__PURE__ */ jsx("button", {
|
|
6324
|
+
className: "review-comment-action",
|
|
6325
|
+
disabled: resolving,
|
|
6326
|
+
onClick: handleResolve,
|
|
6327
|
+
type: "button",
|
|
6328
|
+
children: resolving ? "Saving" : effectiveThreadResolved ? "Reopen" : "Resolve"
|
|
6329
|
+
}) : null]
|
|
6330
|
+
})]
|
|
6331
|
+
}) : null] });
|
|
6332
|
+
return effectiveThreadResolved ? /* @__PURE__ */ jsx(ResolvedThreadDisclosure, {
|
|
6333
|
+
commentCount: comments.length,
|
|
6334
|
+
focused: hasFocusedComment,
|
|
6335
|
+
focusRequest: focusCommentRequest,
|
|
6336
|
+
initialExpanded: Boolean(resolveError),
|
|
6337
|
+
saving: resolving,
|
|
6338
|
+
children: threadContent
|
|
6339
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
5782
6340
|
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]
|
|
6341
|
+
children: threadContent
|
|
5824
6342
|
});
|
|
5825
6343
|
}
|
|
5826
6344
|
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 +6371,16 @@ function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusComm
|
|
|
5853
6371
|
return () => observer.disconnect();
|
|
5854
6372
|
}, [annotationComments.length, onHeightChange]);
|
|
5855
6373
|
if (annotationComments.length === 0) return null;
|
|
6374
|
+
const commentGroups = groupReviewCommentsByThread(annotationComments);
|
|
5856
6375
|
return /* @__PURE__ */ jsx("div", {
|
|
5857
6376
|
className: "review-comment-thread",
|
|
5858
6377
|
ref: threadRef,
|
|
5859
|
-
children:
|
|
6378
|
+
children: commentGroups.map((group) => /* @__PURE__ */ jsx(ReviewCommentThreadGroup, {
|
|
5860
6379
|
agentId,
|
|
5861
6380
|
agentLabel,
|
|
5862
6381
|
comments: group.comments,
|
|
5863
6382
|
focusCommentId,
|
|
6383
|
+
focusCommentRequest,
|
|
5864
6384
|
focusEditorRef: setFocusEditorRef,
|
|
5865
6385
|
identity,
|
|
5866
6386
|
keymap,
|
|
@@ -6062,7 +6582,7 @@ const getHunkSelectionRange = (hunk) => {
|
|
|
6062
6582
|
} : null;
|
|
6063
6583
|
};
|
|
6064
6584
|
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 }) {
|
|
6585
|
+
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
6586
|
const codeViewRef = useRef(null);
|
|
6067
6587
|
const markdownEditorRefs = useRef(/* @__PURE__ */ new Map());
|
|
6068
6588
|
const refreshingMarkdownSectionsRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -6070,6 +6590,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6070
6590
|
const handledScrollRequestRef = useRef(null);
|
|
6071
6591
|
const handledHunkNavRef = useRef(hunkNavigation?.request ?? null);
|
|
6072
6592
|
const emptyCommentDeleteTimersRef = useRef(/* @__PURE__ */ new Map());
|
|
6593
|
+
const definitionHighlightFrameRef = useRef(null);
|
|
6073
6594
|
const highlightFrameRef = useRef(null);
|
|
6074
6595
|
const ignoreNextLineSelectionEndRef = useRef(false);
|
|
6075
6596
|
const navigatedSelectionRef = useRef(null);
|
|
@@ -6083,6 +6604,15 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6083
6604
|
const [imagePreviewLayoutPassBySection, setImagePreviewLayoutPassBySection] = useState({});
|
|
6084
6605
|
const [commentLayoutPassByItem, setCommentLayoutPassByItem] = useState({});
|
|
6085
6606
|
const [selectedLines, setSelectedLines] = useState(null);
|
|
6607
|
+
const [definitionLookup, setDefinitionLookup] = useState(null);
|
|
6608
|
+
const definitionLookupRequestRef = useRef(0);
|
|
6609
|
+
const definitionModifierActiveRef = useRef(false);
|
|
6610
|
+
const sourceKey = getSourceKey(source);
|
|
6611
|
+
const [definitionLookupSourceKey, setDefinitionLookupSourceKey] = useState(sourceKey);
|
|
6612
|
+
if (definitionLookupSourceKey !== sourceKey) {
|
|
6613
|
+
setDefinitionLookupSourceKey(sourceKey);
|
|
6614
|
+
setDefinitionLookup(null);
|
|
6615
|
+
}
|
|
6086
6616
|
const selectedLinesRef = useRef(null);
|
|
6087
6617
|
const commitMessageMetadata = source.type === "commit" ? commitMetadata : null;
|
|
6088
6618
|
const shouldShowCommitMessage = commitMessageMetadata != null;
|
|
@@ -6132,19 +6662,22 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6132
6662
|
[itemId]: (current[itemId] ?? 0) + 1
|
|
6133
6663
|
}));
|
|
6134
6664
|
}, []);
|
|
6135
|
-
const { firstItemByBlockId, firstItemByPath, itemBlockId, itemMetadata, items, searchTargetsByBaseItemId } = useMemo(() => {
|
|
6665
|
+
const { commentTargetById, firstItemByBlockId, firstItemByPath, itemBlockId, itemMetadata, items, searchTargetsByBaseItemId, selectedHeaderItemIds } = useMemo(() => {
|
|
6136
6666
|
const nextItems = [];
|
|
6667
|
+
const nextCommentTargetById = /* @__PURE__ */ new Map();
|
|
6137
6668
|
const nextFirstItemByBlockId = /* @__PURE__ */ new Map();
|
|
6138
6669
|
const nextFirstItemByPath = /* @__PURE__ */ new Map();
|
|
6139
6670
|
const nextItemBlockId = /* @__PURE__ */ new Map();
|
|
6140
6671
|
const nextItemMetadata = /* @__PURE__ */ new Map();
|
|
6141
6672
|
const nextSearchTargetsByBaseItemId = /* @__PURE__ */ new Map();
|
|
6673
|
+
const nextSelectedHeaderItemIds = /* @__PURE__ */ new Set();
|
|
6142
6674
|
const fontLayoutKey = `line-height:${diffLineHeight}`;
|
|
6143
6675
|
for (const block of reviewBlocks) {
|
|
6144
6676
|
if (block.header) {
|
|
6145
6677
|
const headerId = getBlockHeaderItemId(block);
|
|
6146
6678
|
nextFirstItemByBlockId.set(block.id, nextFirstItemByBlockId.get(block.id) ?? headerId);
|
|
6147
6679
|
nextItemBlockId.set(headerId, block.id);
|
|
6680
|
+
if ((block.headerSelected ?? block.selected) === true) nextSelectedHeaderItemIds.add(headerId);
|
|
6148
6681
|
nextItems.push({
|
|
6149
6682
|
annotations: [{
|
|
6150
6683
|
lineNumber: 1,
|
|
@@ -6162,7 +6695,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6162
6695
|
},
|
|
6163
6696
|
id: headerId,
|
|
6164
6697
|
type: "file",
|
|
6165
|
-
version: getItemVersion(`${block.id}:walkthrough-header
|
|
6698
|
+
version: getItemVersion(`${block.id}:walkthrough-header`)
|
|
6166
6699
|
});
|
|
6167
6700
|
}
|
|
6168
6701
|
if (!block.file) continue;
|
|
@@ -6200,6 +6733,11 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6200
6733
|
const blockSectionComments = blockCommentsBySection.get(section.id) ?? [];
|
|
6201
6734
|
const sectionComments = dedupeReviewComments([...visibleGlobalComments, ...blockSectionComments]);
|
|
6202
6735
|
for (const comment of sectionComments) {
|
|
6736
|
+
if (!nextCommentTargetById.has(comment.id)) nextCommentTargetById.set(comment.id, isLineReviewComment(comment) ? {
|
|
6737
|
+
id,
|
|
6738
|
+
lineNumber: comment.lineNumber,
|
|
6739
|
+
side: comment.side
|
|
6740
|
+
} : { id });
|
|
6203
6741
|
const key = getCommentKey(comment);
|
|
6204
6742
|
const existing = annotationMap.get(key);
|
|
6205
6743
|
if (existing && existing.metadata.type === "review-comment") annotationMap.set(key, {
|
|
@@ -6311,12 +6849,14 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6311
6849
|
}
|
|
6312
6850
|
}
|
|
6313
6851
|
return {
|
|
6852
|
+
commentTargetById: nextCommentTargetById,
|
|
6314
6853
|
firstItemByBlockId: nextFirstItemByBlockId,
|
|
6315
6854
|
firstItemByPath: nextFirstItemByPath,
|
|
6316
6855
|
itemBlockId: nextItemBlockId,
|
|
6317
6856
|
itemMetadata: nextItemMetadata,
|
|
6318
6857
|
items: nextItems,
|
|
6319
|
-
searchTargetsByBaseItemId: nextSearchTargetsByBaseItemId
|
|
6858
|
+
searchTargetsByBaseItemId: nextSearchTargetsByBaseItemId,
|
|
6859
|
+
selectedHeaderItemIds: nextSelectedHeaderItemIds
|
|
6320
6860
|
};
|
|
6321
6861
|
}, [
|
|
6322
6862
|
collapsed,
|
|
@@ -6369,6 +6909,18 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6369
6909
|
itemMetadata,
|
|
6370
6910
|
searchTargetsByBaseItemId
|
|
6371
6911
|
]);
|
|
6912
|
+
const getDefinitionDiffTarget = useCallback((candidate) => {
|
|
6913
|
+
for (const item of items) {
|
|
6914
|
+
if (item.type !== "diff") continue;
|
|
6915
|
+
const metadata = itemMetadata.get(item.id);
|
|
6916
|
+
const path = candidate.side === "deletions" ? metadata?.file.oldPath ?? metadata?.file.path : metadata?.file.path;
|
|
6917
|
+
if (metadata && path === candidate.path && lineIsVisibleInFileDiff(item.fileDiff, candidate.side, candidate.lineNumber)) return {
|
|
6918
|
+
item,
|
|
6919
|
+
metadata
|
|
6920
|
+
};
|
|
6921
|
+
}
|
|
6922
|
+
return null;
|
|
6923
|
+
}, [itemMetadata, items]);
|
|
6372
6924
|
const setCodeViewSelectedLines = useCallback((selection) => {
|
|
6373
6925
|
if (selection == null || navigatedSelectionRef.current == null || selection.id !== navigatedSelectionRef.current.id || !isSameSelection(selection.range, navigatedSelectionRef.current.range)) navigatedSelectionRef.current = null;
|
|
6374
6926
|
setSelectedLines(selection);
|
|
@@ -6482,6 +7034,50 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6482
7034
|
createCommentForRange(range, context);
|
|
6483
7035
|
},
|
|
6484
7036
|
onLineClick: (line, context) => {
|
|
7037
|
+
if (isPrimaryModifier(line.event) && onFindDefinitions) {
|
|
7038
|
+
line.event.preventDefault();
|
|
7039
|
+
line.event.stopPropagation();
|
|
7040
|
+
const lineElement = "lineElement" in line ? line.lineElement : null;
|
|
7041
|
+
const identifier = lineElement ? getIdentifierFromPointerEvent(line.event, lineElement) : null;
|
|
7042
|
+
const meta = itemMetadata.get(context.item.id);
|
|
7043
|
+
const side = "annotationSide" in line ? line.annotationSide : null;
|
|
7044
|
+
if (!identifier || !meta || !side) {
|
|
7045
|
+
setDefinitionLookup(null);
|
|
7046
|
+
return;
|
|
7047
|
+
}
|
|
7048
|
+
const requestId = ++definitionLookupRequestRef.current;
|
|
7049
|
+
setDefinitionLookup({
|
|
7050
|
+
anchor: {
|
|
7051
|
+
x: line.event.clientX,
|
|
7052
|
+
y: line.event.clientY
|
|
7053
|
+
},
|
|
7054
|
+
identifier,
|
|
7055
|
+
result: null,
|
|
7056
|
+
sourceKey
|
|
7057
|
+
});
|
|
7058
|
+
onFindDefinitions({
|
|
7059
|
+
identifier,
|
|
7060
|
+
kind: meta.section.kind,
|
|
7061
|
+
lineNumber: line.lineNumber,
|
|
7062
|
+
path: side === "deletions" ? meta.file.oldPath ?? meta.file.path : meta.file.path,
|
|
7063
|
+
side,
|
|
7064
|
+
source
|
|
7065
|
+
}).then((result) => {
|
|
7066
|
+
if (definitionLookupRequestRef.current === requestId) setDefinitionLookup((current) => current?.identifier === identifier && current.sourceKey === sourceKey ? {
|
|
7067
|
+
...current,
|
|
7068
|
+
result
|
|
7069
|
+
} : current);
|
|
7070
|
+
}).catch(() => {
|
|
7071
|
+
if (definitionLookupRequestRef.current === requestId) setDefinitionLookup((current) => current?.identifier === identifier && current.sourceKey === sourceKey ? {
|
|
7072
|
+
...current,
|
|
7073
|
+
result: {
|
|
7074
|
+
reason: "Definition search is unavailable for this repository.",
|
|
7075
|
+
status: "unavailable"
|
|
7076
|
+
}
|
|
7077
|
+
} : current);
|
|
7078
|
+
});
|
|
7079
|
+
return;
|
|
7080
|
+
}
|
|
6485
7081
|
if (isReadOnly) return;
|
|
6486
7082
|
if (isInteractiveReviewEvent(line.event)) return;
|
|
6487
7083
|
const meta = itemMetadata.get(context.item.id);
|
|
@@ -6514,11 +7110,14 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6514
7110
|
const metadata = itemMetadata.get(context.item.id);
|
|
6515
7111
|
const isWalkthroughHeaderItem = context.item.id.endsWith(":walkthrough-header");
|
|
6516
7112
|
node.classList.toggle("codiff-walkthrough-header-item", isWalkthroughHeaderItem);
|
|
6517
|
-
node.classList.toggle("codiff-selected-item", metadata?.isSelected === true);
|
|
7113
|
+
node.classList.toggle("codiff-selected-item", metadata?.isSelected === true || selectedHeaderItemIds.has(context.item.id));
|
|
6518
7114
|
node.classList.toggle("codiff-markdown-preview-item", metadata?.isMarkdownPreview === true);
|
|
6519
7115
|
node.classList.toggle("codiff-image-preview-item", context.item.type === "file" && Boolean(metadata && canRenderImagePreview(metadata.file.path, metadata.section)));
|
|
6520
7116
|
node.classList.toggle("codiff-loadable-summary-item", metadata != null && shouldLoadDiffSectionContents(metadata.section));
|
|
6521
7117
|
node.classList.toggle("codiff-loading-summary-item", Boolean(metadata && loadingSectionIds.has(metadata.section.id)));
|
|
7118
|
+
if (definitionModifierActiveRef.current) window.requestAnimationFrame(() => {
|
|
7119
|
+
if (definitionModifierActiveRef.current && node.isConnected) applyIdentifierNavigationState([{ element: node }], true);
|
|
7120
|
+
});
|
|
6522
7121
|
},
|
|
6523
7122
|
overflow: wordWrap ? "wrap" : "scroll",
|
|
6524
7123
|
stickyHeaders: true,
|
|
@@ -6539,7 +7138,11 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6539
7138
|
loadDiffFiles,
|
|
6540
7139
|
loadingSectionIds,
|
|
6541
7140
|
onCreateComment,
|
|
7141
|
+
onFindDefinitions,
|
|
6542
7142
|
onLoadSection,
|
|
7143
|
+
selectedHeaderItemIds,
|
|
7144
|
+
source,
|
|
7145
|
+
sourceKey,
|
|
6543
7146
|
theme,
|
|
6544
7147
|
wordWrap
|
|
6545
7148
|
]);
|
|
@@ -6631,11 +7234,19 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6631
7234
|
poolSize: Math.min(3, Math.max(1, navigator.hardwareConcurrency || 3)),
|
|
6632
7235
|
workerFactory: () => new Worker(new URL("@pierre/diffs/worker/worker.js", import.meta.url), { type: "module" })
|
|
6633
7236
|
}), []);
|
|
6634
|
-
const
|
|
7237
|
+
const requestScrollTargetIntoView = useCallback((itemId, behavior = "instant", commentTarget) => {
|
|
6635
7238
|
const handle = codeViewRef.current;
|
|
6636
7239
|
const viewer = handle?.getInstance();
|
|
6637
7240
|
if (!handle || !viewer || viewer.getTopForItem(itemId) == null) return false;
|
|
6638
|
-
handle.scrollTo({
|
|
7241
|
+
handle.scrollTo(commentTarget?.lineNumber != null && commentTarget.side ? {
|
|
7242
|
+
align: "center",
|
|
7243
|
+
behavior: getEffectiveScrollBehavior(behavior),
|
|
7244
|
+
id: itemId,
|
|
7245
|
+
lineNumber: commentTarget.lineNumber,
|
|
7246
|
+
offset: 11,
|
|
7247
|
+
side: commentTarget.side,
|
|
7248
|
+
type: "line"
|
|
7249
|
+
} : {
|
|
6639
7250
|
behavior: getEffectiveScrollBehavior(behavior),
|
|
6640
7251
|
id: itemId,
|
|
6641
7252
|
offset: 11,
|
|
@@ -6646,14 +7257,15 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6646
7257
|
useLayoutEffect(() => {
|
|
6647
7258
|
if (!scrollTarget || handledScrollRequestRef.current === scrollTarget.request) return;
|
|
6648
7259
|
const behavior = scrollTarget.behavior ?? "instant";
|
|
6649
|
-
const
|
|
7260
|
+
const commentTarget = scrollTarget.commentId ? commentTargetById.get(scrollTarget.commentId) : void 0;
|
|
7261
|
+
const itemId = commentTarget?.id ?? (scrollTarget.blockId ? firstItemByBlockId.get(scrollTarget.blockId) : scrollTarget.path ? firstItemByPath.get(scrollTarget.path) : null);
|
|
6650
7262
|
if (!itemId) return;
|
|
6651
7263
|
let frame = null;
|
|
6652
7264
|
let attempts = 0;
|
|
6653
7265
|
let canceled = false;
|
|
6654
7266
|
const tryScroll = () => {
|
|
6655
7267
|
if (canceled || handledScrollRequestRef.current === scrollTarget.request) return;
|
|
6656
|
-
if (
|
|
7268
|
+
if (requestScrollTargetIntoView(itemId, behavior, commentTarget)) {
|
|
6657
7269
|
handledScrollRequestRef.current = scrollTarget.request;
|
|
6658
7270
|
return;
|
|
6659
7271
|
}
|
|
@@ -6668,9 +7280,10 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6668
7280
|
if (frame != null) window.cancelAnimationFrame(frame);
|
|
6669
7281
|
};
|
|
6670
7282
|
}, [
|
|
7283
|
+
commentTargetById,
|
|
6671
7284
|
firstItemByBlockId,
|
|
6672
7285
|
firstItemByPath,
|
|
6673
|
-
|
|
7286
|
+
requestScrollTargetIntoView,
|
|
6674
7287
|
scrollTarget
|
|
6675
7288
|
]);
|
|
6676
7289
|
useEffect(() => {
|
|
@@ -6832,6 +7445,22 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6832
7445
|
applySearchHighlights(viewer.getRenderedItems(), searchQuery, resolvedActiveSearchMatch);
|
|
6833
7446
|
});
|
|
6834
7447
|
}, [resolvedActiveSearchMatch, searchQuery]);
|
|
7448
|
+
const updateRenderedIdentifierNavigation = useCallback((active, viewer) => {
|
|
7449
|
+
const nextViewer = viewer ?? codeViewRef.current?.getInstance();
|
|
7450
|
+
if (!nextViewer) return;
|
|
7451
|
+
if (definitionHighlightFrameRef.current != null) {
|
|
7452
|
+
window.cancelAnimationFrame(definitionHighlightFrameRef.current);
|
|
7453
|
+
definitionHighlightFrameRef.current = null;
|
|
7454
|
+
}
|
|
7455
|
+
if (!active) {
|
|
7456
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems(), false);
|
|
7457
|
+
return;
|
|
7458
|
+
}
|
|
7459
|
+
definitionHighlightFrameRef.current = window.requestAnimationFrame(() => {
|
|
7460
|
+
definitionHighlightFrameRef.current = null;
|
|
7461
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems(), true);
|
|
7462
|
+
});
|
|
7463
|
+
}, []);
|
|
6835
7464
|
const scheduleStickyHeaderStateUpdate = useCallback((viewer) => {
|
|
6836
7465
|
const nextViewer = viewer ?? codeViewRef.current?.getInstance();
|
|
6837
7466
|
if (!nextViewer) return;
|
|
@@ -6845,6 +7474,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6845
7474
|
for (const timer of deferredTimersRef.current) window.clearTimeout(timer);
|
|
6846
7475
|
deferredTimersRef.current.clear();
|
|
6847
7476
|
emptyCommentDeleteTimersRef.current.clear();
|
|
7477
|
+
if (definitionHighlightFrameRef.current != null) window.cancelAnimationFrame(definitionHighlightFrameRef.current);
|
|
6848
7478
|
if (highlightFrameRef.current != null) window.cancelAnimationFrame(highlightFrameRef.current);
|
|
6849
7479
|
if (stickyHeaderFrameRef.current != null) window.cancelAnimationFrame(stickyHeaderFrameRef.current);
|
|
6850
7480
|
}, []);
|
|
@@ -6856,6 +7486,38 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6856
7486
|
scheduleSearchHighlights,
|
|
6857
7487
|
scheduleStickyHeaderStateUpdate
|
|
6858
7488
|
]);
|
|
7489
|
+
useEffect(() => {
|
|
7490
|
+
if (!onFindDefinitions) return;
|
|
7491
|
+
const updateDefinitionModifierState = (active) => {
|
|
7492
|
+
if (definitionModifierActiveRef.current === active) return;
|
|
7493
|
+
definitionModifierActiveRef.current = active;
|
|
7494
|
+
updateRenderedIdentifierNavigation(active);
|
|
7495
|
+
};
|
|
7496
|
+
const handleModifierChange = (event) => {
|
|
7497
|
+
updateDefinitionModifierState(isPrimaryModifier(event));
|
|
7498
|
+
};
|
|
7499
|
+
const handleModifierPointer = (event) => {
|
|
7500
|
+
updateDefinitionModifierState(isPrimaryModifier(event));
|
|
7501
|
+
};
|
|
7502
|
+
const clearDefinitionModifierState = () => updateDefinitionModifierState(false);
|
|
7503
|
+
window.addEventListener("keydown", handleModifierChange);
|
|
7504
|
+
window.addEventListener("keyup", handleModifierChange);
|
|
7505
|
+
window.addEventListener("pointerdown", handleModifierPointer);
|
|
7506
|
+
window.addEventListener("pointermove", handleModifierPointer);
|
|
7507
|
+
window.addEventListener("blur", clearDefinitionModifierState);
|
|
7508
|
+
return () => {
|
|
7509
|
+
clearDefinitionModifierState();
|
|
7510
|
+
window.removeEventListener("keydown", handleModifierChange);
|
|
7511
|
+
window.removeEventListener("keyup", handleModifierChange);
|
|
7512
|
+
window.removeEventListener("pointerdown", handleModifierPointer);
|
|
7513
|
+
window.removeEventListener("pointermove", handleModifierPointer);
|
|
7514
|
+
window.removeEventListener("blur", clearDefinitionModifierState);
|
|
7515
|
+
};
|
|
7516
|
+
}, [onFindDefinitions, updateRenderedIdentifierNavigation]);
|
|
7517
|
+
const invalidateDefinitionLookup = useCallback(() => {
|
|
7518
|
+
definitionLookupRequestRef.current++;
|
|
7519
|
+
}, []);
|
|
7520
|
+
useEffect(() => invalidateDefinitionLookup, [invalidateDefinitionLookup]);
|
|
6859
7521
|
useEffect(() => {
|
|
6860
7522
|
const handle = codeViewRef.current;
|
|
6861
7523
|
const viewer = handle?.getInstance();
|
|
@@ -7037,15 +7699,17 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7037
7699
|
}
|
|
7038
7700
|
scheduleSearchHighlights();
|
|
7039
7701
|
scheduleStickyHeaderStateUpdate(viewer);
|
|
7702
|
+
if (definitionModifierActiveRef.current) updateRenderedIdentifierNavigation(true, viewer);
|
|
7040
7703
|
}, [
|
|
7041
7704
|
itemBlockId,
|
|
7042
7705
|
items,
|
|
7043
7706
|
onActiveBlockChange,
|
|
7044
7707
|
onSelectPathFromScroll,
|
|
7045
7708
|
scheduleSearchHighlights,
|
|
7046
|
-
scheduleStickyHeaderStateUpdate
|
|
7709
|
+
scheduleStickyHeaderStateUpdate,
|
|
7710
|
+
updateRenderedIdentifierNavigation
|
|
7047
7711
|
]);
|
|
7048
|
-
return disableWorkerPool ? /* @__PURE__ */ jsx(CodeView, {
|
|
7712
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [disableWorkerPool ? /* @__PURE__ */ jsx(CodeView, {
|
|
7049
7713
|
className: "code-view",
|
|
7050
7714
|
disableWorkerPool,
|
|
7051
7715
|
items: codeViewItems,
|
|
@@ -7073,11 +7737,77 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7073
7737
|
renderCustomHeader,
|
|
7074
7738
|
selectedLines: isReadOnly ? null : selectedLines
|
|
7075
7739
|
})
|
|
7740
|
+
}), definitionLookup?.sourceKey === sourceKey && onOpenDefinition ? /* @__PURE__ */ jsx(DefinitionPopover, {
|
|
7741
|
+
anchor: definitionLookup.anchor,
|
|
7742
|
+
getDestination: (candidate) => getDefinitionDiffTarget(candidate) ? "diff" : candidate.canOpenInEditor ? "editor" : "unavailable",
|
|
7743
|
+
identifier: definitionLookup.identifier,
|
|
7744
|
+
onClose: () => {
|
|
7745
|
+
invalidateDefinitionLookup();
|
|
7746
|
+
setDefinitionLookup(null);
|
|
7747
|
+
},
|
|
7748
|
+
onOpen: (candidate) => {
|
|
7749
|
+
const target = getDefinitionDiffTarget(candidate);
|
|
7750
|
+
if (target) {
|
|
7751
|
+
if (target.metadata.isCollapsed) onToggleCollapsed(target.metadata.file, true, target.metadata.reviewIdentity.key);
|
|
7752
|
+
const scrollToDefinition = () => codeViewRef.current?.scrollTo({
|
|
7753
|
+
align: "center",
|
|
7754
|
+
behavior: "smooth-auto",
|
|
7755
|
+
id: target.item.id,
|
|
7756
|
+
lineNumber: candidate.lineNumber,
|
|
7757
|
+
offset: 11,
|
|
7758
|
+
side: candidate.side,
|
|
7759
|
+
type: "line"
|
|
7760
|
+
});
|
|
7761
|
+
if (target.metadata.isCollapsed) window.requestAnimationFrame(() => window.requestAnimationFrame(scrollToDefinition));
|
|
7762
|
+
else scrollToDefinition();
|
|
7763
|
+
} else if (candidate.canOpenInEditor) onOpenDefinition(candidate);
|
|
7764
|
+
invalidateDefinitionLookup();
|
|
7765
|
+
setDefinitionLookup(null);
|
|
7766
|
+
},
|
|
7767
|
+
result: definitionLookup.result
|
|
7768
|
+
}) : null] });
|
|
7769
|
+
}
|
|
7770
|
+
//#endregion
|
|
7771
|
+
//#region app/components/ReviewModeControl.tsx
|
|
7772
|
+
function ReviewModeControl({ mode, modes, onModeChange }) {
|
|
7773
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7774
|
+
"aria-label": "Review mode",
|
|
7775
|
+
className: "review-mode-control",
|
|
7776
|
+
role: "tablist",
|
|
7777
|
+
children: modes.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
7778
|
+
"aria-label": item.ariaLabel,
|
|
7779
|
+
"aria-selected": mode === item.value,
|
|
7780
|
+
onClick: () => onModeChange(item.value),
|
|
7781
|
+
role: "tab",
|
|
7782
|
+
title: item.title,
|
|
7783
|
+
type: "button",
|
|
7784
|
+
children: [
|
|
7785
|
+
item.icon,
|
|
7786
|
+
/* @__PURE__ */ jsx("span", {
|
|
7787
|
+
className: "review-mode-label",
|
|
7788
|
+
children: item.label
|
|
7789
|
+
}),
|
|
7790
|
+
item.indicator
|
|
7791
|
+
]
|
|
7792
|
+
}, item.value))
|
|
7076
7793
|
});
|
|
7077
7794
|
}
|
|
7078
7795
|
//#endregion
|
|
7079
7796
|
//#region app/components/ReviewTopBar.tsx
|
|
7080
|
-
function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, sourceMenu, toggleTitle }) {
|
|
7797
|
+
function ReviewTopBar({ actions, center, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, sidebarPosition = "left", sourceMenu, toggleTitle }) {
|
|
7798
|
+
const sidebarToggle = /* @__PURE__ */ jsx("button", {
|
|
7799
|
+
"aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
7800
|
+
className: "review-top-bar-icon-button sidebar-toggle-button",
|
|
7801
|
+
onClick: onToggleSidebar,
|
|
7802
|
+
title: toggleTitle,
|
|
7803
|
+
type: "button",
|
|
7804
|
+
children: /* @__PURE__ */ jsx(SidebarSimpleIcon, {
|
|
7805
|
+
"aria-hidden": true,
|
|
7806
|
+
mirrored: sidebarPosition === "right",
|
|
7807
|
+
size: 18,
|
|
7808
|
+
weight: "bold"
|
|
7809
|
+
})
|
|
7810
|
+
});
|
|
7081
7811
|
return /* @__PURE__ */ jsxs("header", {
|
|
7082
7812
|
className: "review-top-bar workspace-top-bar",
|
|
7083
7813
|
children: [
|
|
@@ -7085,18 +7815,7 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
|
|
|
7085
7815
|
className: "review-top-bar-left",
|
|
7086
7816
|
children: [
|
|
7087
7817
|
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
|
-
}),
|
|
7818
|
+
sidebarPosition === "left" ? sidebarToggle : null,
|
|
7100
7819
|
sourceMenu,
|
|
7101
7820
|
/* @__PURE__ */ jsx("div", {
|
|
7102
7821
|
className: "review-top-bar-repository-slot",
|
|
@@ -7105,36 +7824,27 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
|
|
|
7105
7824
|
})
|
|
7106
7825
|
]
|
|
7107
7826
|
}),
|
|
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))
|
|
7827
|
+
modes ? /* @__PURE__ */ jsx(ReviewModeControl, {
|
|
7828
|
+
mode,
|
|
7829
|
+
modes,
|
|
7830
|
+
onModeChange
|
|
7831
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
7832
|
+
className: "review-top-bar-center",
|
|
7833
|
+
children: center
|
|
7128
7834
|
}),
|
|
7129
7835
|
/* @__PURE__ */ jsxs("div", {
|
|
7130
7836
|
className: "review-top-bar-right",
|
|
7131
|
-
children: [
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7837
|
+
children: [
|
|
7838
|
+
context ? /* @__PURE__ */ jsx("div", {
|
|
7839
|
+
className: "review-top-bar-context",
|
|
7840
|
+
children: context
|
|
7841
|
+
}) : null,
|
|
7842
|
+
actions ? /* @__PURE__ */ jsx("div", {
|
|
7843
|
+
className: "review-top-bar-actions",
|
|
7844
|
+
children: actions
|
|
7845
|
+
}) : null,
|
|
7846
|
+
sidebarPosition === "right" ? sidebarToggle : null
|
|
7847
|
+
]
|
|
7138
7848
|
})
|
|
7139
7849
|
]
|
|
7140
7850
|
});
|
|
@@ -7286,6 +7996,7 @@ function StageFileRow({ file, on, onToggle }) {
|
|
|
7286
7996
|
function StageGroupRow({ group, onToggleFile, onToggleGroup, selected }) {
|
|
7287
7997
|
const paths = group.files.map((file) => file.path);
|
|
7288
7998
|
const onCount = paths.filter((path) => selected.has(path)).length;
|
|
7999
|
+
const state = onCount === 0 ? "off" : onCount === paths.length ? "on" : "partial";
|
|
7289
8000
|
return /* @__PURE__ */ jsxs("div", {
|
|
7290
8001
|
className: "wt-stage-group",
|
|
7291
8002
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
@@ -7293,7 +8004,7 @@ function StageGroupRow({ group, onToggleFile, onToggleGroup, selected }) {
|
|
|
7293
8004
|
onClick: () => onToggleGroup(paths),
|
|
7294
8005
|
type: "button",
|
|
7295
8006
|
children: [
|
|
7296
|
-
/* @__PURE__ */ jsx(CommitCheck, { state
|
|
8007
|
+
/* @__PURE__ */ jsx(CommitCheck, { state }),
|
|
7297
8008
|
/* @__PURE__ */ jsx("span", {
|
|
7298
8009
|
className: "wt-stage-group-icon",
|
|
7299
8010
|
children: /* @__PURE__ */ jsx(ChapterIcon, {
|
|
@@ -7629,14 +8340,14 @@ function CommitView({ branch, draft, model, onCommit, onCommitOutput, onUpdateMe
|
|
|
7629
8340
|
}
|
|
7630
8341
|
//#endregion
|
|
7631
8342
|
//#region app/components/walkthrough/NarrativeWalkthroughView.tsx
|
|
7632
|
-
const getWalkthroughBlockScrollTarget = ({ activeBlockId, firstSupportBlockId,
|
|
8343
|
+
const getWalkthroughBlockScrollTarget = ({ activeBlockId, firstSupportBlockId, scrollTarget }) => scrollTarget.kind === "support" && firstSupportBlockId ? {
|
|
7633
8344
|
behavior: "smooth",
|
|
7634
8345
|
blockId: firstSupportBlockId,
|
|
7635
|
-
request:
|
|
7636
|
-
} :
|
|
8346
|
+
request: scrollTarget.nonce
|
|
8347
|
+
} : scrollTarget.kind === "stop" && activeBlockId && scrollTarget.nonce > 0 ? {
|
|
7637
8348
|
behavior: "smooth",
|
|
7638
8349
|
blockId: activeBlockId,
|
|
7639
|
-
request:
|
|
8350
|
+
request: scrollTarget.nonce
|
|
7640
8351
|
} : null;
|
|
7641
8352
|
const getFocusedRunDiffs = (item, files) => resolveWalkthroughHunkRuns(item, files).flatMap((run) => {
|
|
7642
8353
|
const focused = focusChangedFileForHunks(run.resolved.file, run.resolved.section, run.hunks);
|
|
@@ -7659,14 +8370,15 @@ const getWalkthroughNavigationKeyDirection = (event) => {
|
|
|
7659
8370
|
if (isCtrlOnly && event.key === "ArrowUp") return -1;
|
|
7660
8371
|
return 0;
|
|
7661
8372
|
};
|
|
8373
|
+
const getWalkthroughKeyboardForwardIndex = ({ index, scrollRequest }) => index === 0 && scrollRequest === 0 ? 0 : index + 1;
|
|
7662
8374
|
const emptyWalkthroughBlockSet = {
|
|
7663
8375
|
blocks: [],
|
|
7664
8376
|
firstBlockIdByStop: [],
|
|
7665
8377
|
stopIndexByBlockId: /* @__PURE__ */ new Map()
|
|
7666
8378
|
};
|
|
7667
|
-
function StopHeader({
|
|
8379
|
+
function StopHeader({ stop }) {
|
|
7668
8380
|
return /* @__PURE__ */ jsxs("div", {
|
|
7669
|
-
className:
|
|
8381
|
+
className: "wt-stop-block wt-stop-block-header",
|
|
7670
8382
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
7671
8383
|
className: "wt-stage-title-row",
|
|
7672
8384
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
@@ -7676,9 +8388,9 @@ function StopHeader({ current, stop }) {
|
|
|
7676
8388
|
}), /* @__PURE__ */ jsx(Narration, { prose: stop.prose })]
|
|
7677
8389
|
});
|
|
7678
8390
|
}
|
|
7679
|
-
function SupportHeader(
|
|
8391
|
+
function SupportHeader() {
|
|
7680
8392
|
return /* @__PURE__ */ jsxs("div", {
|
|
7681
|
-
className:
|
|
8393
|
+
className: "wt-stop-block wt-stop-block-header",
|
|
7682
8394
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
7683
8395
|
className: "wt-stage-title-row",
|
|
7684
8396
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
@@ -7699,10 +8411,7 @@ const createWalkthroughBlocks = (files, walkthroughView, currentIndex) => {
|
|
|
7699
8411
|
firstBlockIdByStop[stop.index] = blockId;
|
|
7700
8412
|
stopIndexByBlockId.set(blockId, stop.index);
|
|
7701
8413
|
blocks.push({
|
|
7702
|
-
header: /* @__PURE__ */ jsx(StopHeader, {
|
|
7703
|
-
current: stop.index === currentIndex,
|
|
7704
|
-
stop
|
|
7705
|
-
}),
|
|
8414
|
+
header: /* @__PURE__ */ jsx(StopHeader, { stop }),
|
|
7706
8415
|
headerSelected: stop.index === currentIndex,
|
|
7707
8416
|
id: blockId
|
|
7708
8417
|
});
|
|
@@ -7714,10 +8423,7 @@ const createWalkthroughBlocks = (files, walkthroughView, currentIndex) => {
|
|
|
7714
8423
|
stopIndexByBlockId.set(blockId, stop.index);
|
|
7715
8424
|
blocks.push({
|
|
7716
8425
|
file,
|
|
7717
|
-
header: runIndex === 0 ? /* @__PURE__ */ jsx(StopHeader, {
|
|
7718
|
-
current: stop.index === currentIndex,
|
|
7719
|
-
stop
|
|
7720
|
-
}) : null,
|
|
8426
|
+
header: runIndex === 0 ? /* @__PURE__ */ jsx(StopHeader, { stop }) : null,
|
|
7721
8427
|
headerSelected: stop.index === currentIndex,
|
|
7722
8428
|
id: blockId,
|
|
7723
8429
|
itemIdPrefix: blockId,
|
|
@@ -7746,7 +8452,7 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace) =
|
|
|
7746
8452
|
const isFirstBlock = blocks.length === 0;
|
|
7747
8453
|
blocks.push({
|
|
7748
8454
|
file,
|
|
7749
|
-
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {
|
|
8455
|
+
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {}) : null,
|
|
7750
8456
|
headerSelected: selected,
|
|
7751
8457
|
id: blockId,
|
|
7752
8458
|
itemIdPrefix: blockId,
|
|
@@ -7760,7 +8466,7 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace) =
|
|
|
7760
8466
|
const isFirstBlock = blocks.length === 0;
|
|
7761
8467
|
blocks.push({
|
|
7762
8468
|
file,
|
|
7763
|
-
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {
|
|
8469
|
+
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {}) : null,
|
|
7764
8470
|
headerSelected: selected,
|
|
7765
8471
|
id: blockId,
|
|
7766
8472
|
itemIdPrefix: blockId,
|
|
@@ -7795,8 +8501,10 @@ function Arc({ committable, navigation, onShareWalkthrough, shareWalkthroughDisa
|
|
|
7795
8501
|
const goArcPrev = useCallback(() => {
|
|
7796
8502
|
if (navigation.mode === "stop") navigation.goPrev();
|
|
7797
8503
|
else if (navigation.mode === "support") navigation.goStop(walkthroughView.sequence.length - 1);
|
|
7798
|
-
else if (navigation.mode === "commit")
|
|
7799
|
-
|
|
8504
|
+
else if (navigation.mode === "commit") {
|
|
8505
|
+
if (supportAvailable) navigation.openSupport();
|
|
8506
|
+
else navigation.goStop(walkthroughView.sequence.length - 1);
|
|
8507
|
+
}
|
|
7800
8508
|
}, [
|
|
7801
8509
|
navigation,
|
|
7802
8510
|
supportAvailable,
|
|
@@ -7955,9 +8663,7 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
7955
8663
|
const reviewBlockScrollTarget = getWalkthroughBlockScrollTarget({
|
|
7956
8664
|
activeBlockId,
|
|
7957
8665
|
firstSupportBlockId,
|
|
7958
|
-
|
|
7959
|
-
stopScrollRequest: navigation.scrollTarget.nonce,
|
|
7960
|
-
supportScrollRequest: navigation.supportScrollRequest
|
|
8666
|
+
scrollTarget: navigation.scrollTarget
|
|
7961
8667
|
});
|
|
7962
8668
|
const handleActiveBlockChange = useCallback((blockId) => {
|
|
7963
8669
|
onActiveReviewTargetChange(getBlockReviewTarget(reviewBlocks, blockId));
|
|
@@ -7993,7 +8699,11 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
7993
8699
|
if (direction === 1) {
|
|
7994
8700
|
event.preventDefault();
|
|
7995
8701
|
if (navigation.mode === "stop") {
|
|
7996
|
-
|
|
8702
|
+
const targetIndex = getWalkthroughKeyboardForwardIndex({
|
|
8703
|
+
index: navigation.index,
|
|
8704
|
+
scrollRequest: navigation.scrollTarget.nonce
|
|
8705
|
+
});
|
|
8706
|
+
if (targetIndex < walkthroughView.sequence.length) navigation.goStop(targetIndex);
|
|
7997
8707
|
else if (supportAvailable) navigation.openSupport();
|
|
7998
8708
|
else if (committable) navigation.enterCommit();
|
|
7999
8709
|
} else if (navigation.mode === "support" && committable) navigation.enterCommit();
|
|
@@ -8001,8 +8711,10 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
8001
8711
|
event.preventDefault();
|
|
8002
8712
|
if (navigation.mode === "stop") navigation.goPrev();
|
|
8003
8713
|
else if (navigation.mode === "support") navigation.goStop(navigation.index);
|
|
8004
|
-
else if (navigation.mode === "commit")
|
|
8005
|
-
|
|
8714
|
+
else if (navigation.mode === "commit") {
|
|
8715
|
+
if (supportAvailable) navigation.openSupport();
|
|
8716
|
+
else navigation.goStop(navigation.index);
|
|
8717
|
+
}
|
|
8006
8718
|
}
|
|
8007
8719
|
}, [
|
|
8008
8720
|
committable,
|
|
@@ -8235,9 +8947,9 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8235
8947
|
const [index, setIndex] = useState(0);
|
|
8236
8948
|
const [scrollTarget, setScrollTarget] = useState({
|
|
8237
8949
|
index: 0,
|
|
8950
|
+
kind: "stop",
|
|
8238
8951
|
nonce: 0
|
|
8239
8952
|
});
|
|
8240
|
-
const [supportScrollRequest, setSupportScrollRequest] = useState(0);
|
|
8241
8953
|
const [supportVisited, setSupportVisited] = useState(false);
|
|
8242
8954
|
const [visited, setVisited] = useState(() => {
|
|
8243
8955
|
const stopId = firstStopId(walkthrough);
|
|
@@ -8268,9 +8980,9 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8268
8980
|
setIndex(0);
|
|
8269
8981
|
setScrollTarget({
|
|
8270
8982
|
index: 0,
|
|
8983
|
+
kind: "stop",
|
|
8271
8984
|
nonce: 0
|
|
8272
8985
|
});
|
|
8273
|
-
setSupportScrollRequest(0);
|
|
8274
8986
|
setSupportVisited(false);
|
|
8275
8987
|
const stopId = firstStopId(walkthrough);
|
|
8276
8988
|
setVisited(new Set(stopId ? [stopId] : []));
|
|
@@ -8332,6 +9044,7 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8332
9044
|
pendingSupportScrollRef.current = null;
|
|
8333
9045
|
setScrollTarget((current) => ({
|
|
8334
9046
|
index: clamped,
|
|
9047
|
+
kind: "stop",
|
|
8335
9048
|
nonce: current.nonce + 1
|
|
8336
9049
|
}));
|
|
8337
9050
|
}, [
|
|
@@ -8376,7 +9089,11 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8376
9089
|
if (walkthroughView?.sequence.length) setIndex(walkthroughView.sequence.length - 1);
|
|
8377
9090
|
setMode("support");
|
|
8378
9091
|
pendingSupportScrollRef.current = { walkthrough };
|
|
8379
|
-
|
|
9092
|
+
setScrollTarget((current) => ({
|
|
9093
|
+
...current,
|
|
9094
|
+
kind: "support",
|
|
9095
|
+
nonce: current.nonce + 1
|
|
9096
|
+
}));
|
|
8380
9097
|
setSupportVisited(true);
|
|
8381
9098
|
}, [
|
|
8382
9099
|
walkthrough,
|
|
@@ -8384,11 +9101,15 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8384
9101
|
leaveStopMode
|
|
8385
9102
|
]);
|
|
8386
9103
|
const syncSupportFromScroll = useCallback(() => {
|
|
9104
|
+
const pendingStop = pendingStopScrollRef.current;
|
|
9105
|
+
if (pendingStop) {
|
|
9106
|
+
if (pendingStop.walkthrough === walkthrough) return;
|
|
9107
|
+
pendingStopScrollRef.current = null;
|
|
9108
|
+
}
|
|
8387
9109
|
pendingSupportScrollRef.current = null;
|
|
8388
|
-
pendingStopScrollRef.current = null;
|
|
8389
9110
|
setMode("support");
|
|
8390
9111
|
setSupportVisited(true);
|
|
8391
|
-
}, []);
|
|
9112
|
+
}, [walkthrough]);
|
|
8392
9113
|
return {
|
|
8393
9114
|
commitBody,
|
|
8394
9115
|
commitSelected,
|
|
@@ -8407,7 +9128,6 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8407
9128
|
scrollTarget,
|
|
8408
9129
|
setCommitBody,
|
|
8409
9130
|
setCommitSubject,
|
|
8410
|
-
supportScrollRequest,
|
|
8411
9131
|
supportVisited,
|
|
8412
9132
|
syncIndexFromScroll,
|
|
8413
9133
|
syncSupportFromScroll,
|
|
@@ -8506,7 +9226,7 @@ const writeSidebarWidth = (width, storage = localStorage) => {
|
|
|
8506
9226
|
};
|
|
8507
9227
|
//#endregion
|
|
8508
9228
|
//#region app/hooks/useResizableSidebar.ts
|
|
8509
|
-
function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, readWidth }) {
|
|
9229
|
+
function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, position, readWidth }) {
|
|
8510
9230
|
const [sidebarWidth, setSidebarWidth] = useState(readWidth);
|
|
8511
9231
|
return {
|
|
8512
9232
|
resizeSidebar: useCallback((event) => {
|
|
@@ -8515,7 +9235,7 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8515
9235
|
const handle = event.currentTarget;
|
|
8516
9236
|
const shell = handle.parentElement;
|
|
8517
9237
|
if (!shell) return;
|
|
8518
|
-
const
|
|
9238
|
+
const shellRect = shell.getBoundingClientRect();
|
|
8519
9239
|
handle.setPointerCapture(event.pointerId);
|
|
8520
9240
|
handle.classList.add("dragging");
|
|
8521
9241
|
document.body.style.cursor = "col-resize";
|
|
@@ -8529,7 +9249,7 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8529
9249
|
document.body.style.cursor = "";
|
|
8530
9250
|
};
|
|
8531
9251
|
const handleMove = (moveEvent) => {
|
|
8532
|
-
const rawWidth = moveEvent.clientX -
|
|
9252
|
+
const rawWidth = position === "right" ? shellRect.right - moveEvent.clientX : moveEvent.clientX - shellRect.left;
|
|
8533
9253
|
if (collapseThreshold != null && rawWidth < collapseThreshold) {
|
|
8534
9254
|
collapsed = true;
|
|
8535
9255
|
onCollapse?.();
|
|
@@ -8551,7 +9271,8 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8551
9271
|
}, [
|
|
8552
9272
|
collapseThreshold,
|
|
8553
9273
|
onCollapse,
|
|
8554
|
-
onWidthCommit
|
|
9274
|
+
onWidthCommit,
|
|
9275
|
+
position
|
|
8555
9276
|
]),
|
|
8556
9277
|
sidebarWidth
|
|
8557
9278
|
};
|
|
@@ -8657,6 +9378,7 @@ function useReviewCommentDrafts({ canCreateComment = true, comments, onCommentFi
|
|
|
8657
9378
|
setComments,
|
|
8658
9379
|
updateActiveReviewCommentDraft
|
|
8659
9380
|
]),
|
|
9381
|
+
focusComment,
|
|
8660
9382
|
focusCommentId,
|
|
8661
9383
|
focusCommentRequest,
|
|
8662
9384
|
resetCommentFocus,
|
|
@@ -8739,6 +9461,7 @@ const defaultConfigTemplate = {
|
|
|
8739
9461
|
"opencodeModel": "opencode-default",
|
|
8740
9462
|
"piModel": "pi-default",
|
|
8741
9463
|
"reviewCommentsPrefix": "# Address these Review Comments",
|
|
9464
|
+
"sidebarPosition": "left",
|
|
8742
9465
|
"showOutdated": false,
|
|
8743
9466
|
"showWhitespace": false,
|
|
8744
9467
|
"theme": "system",
|
|
@@ -8746,6 +9469,7 @@ const defaultConfigTemplate = {
|
|
|
8746
9469
|
"wordWrap": false
|
|
8747
9470
|
},
|
|
8748
9471
|
keymap: {
|
|
9472
|
+
"askAgent": "Mod+Alt+Enter",
|
|
8749
9473
|
"closeSearch": "Escape",
|
|
8750
9474
|
"commandBar": "Mod+Shift+p",
|
|
8751
9475
|
"diffSearch": "Mod+f",
|
|
@@ -8803,10 +9527,35 @@ const emptyReviewComments = [];
|
|
|
8803
9527
|
const emptyGeneralCommentThreads = [];
|
|
8804
9528
|
const emptyPaths = /* @__PURE__ */ new Set();
|
|
8805
9529
|
const emptyWalkthroughNotes = /* @__PURE__ */ new Map();
|
|
9530
|
+
const reviewSurfacePreferencesKey = "codiff:web-review-surface-preferences:v1";
|
|
9531
|
+
const getLocationHashTarget = () => {
|
|
9532
|
+
const hash = window.location.hash.slice(1);
|
|
9533
|
+
if (!hash) return null;
|
|
9534
|
+
try {
|
|
9535
|
+
return decodeURIComponent(hash);
|
|
9536
|
+
} catch {
|
|
9537
|
+
return hash;
|
|
9538
|
+
}
|
|
9539
|
+
};
|
|
9540
|
+
const commentMatchesHashTarget = (comment, target) => comment.id === target || comment.threadId === target || comment.url?.slice(comment.url.lastIndexOf("#") + 1) === target;
|
|
8806
9541
|
const readSharedSidebarWidth = () => typeof localStorage === "undefined" ? 292 : readSidebarWidth();
|
|
8807
9542
|
const writeSharedSidebarWidth = (width) => {
|
|
8808
9543
|
if (typeof localStorage !== "undefined") writeSidebarWidth(width);
|
|
8809
9544
|
};
|
|
9545
|
+
const readStoredSidebarCollapsed = () => {
|
|
9546
|
+
if (typeof localStorage === "undefined") return null;
|
|
9547
|
+
try {
|
|
9548
|
+
const stored = JSON.parse(localStorage.getItem(reviewSurfacePreferencesKey) ?? "{}");
|
|
9549
|
+
if (stored && typeof stored === "object" && "sidebarCollapsed" in stored && typeof stored.sidebarCollapsed === "boolean") return stored.sidebarCollapsed;
|
|
9550
|
+
} catch {}
|
|
9551
|
+
return null;
|
|
9552
|
+
};
|
|
9553
|
+
const writeStoredSidebarCollapsed = (sidebarCollapsed) => {
|
|
9554
|
+
if (typeof localStorage === "undefined") return;
|
|
9555
|
+
try {
|
|
9556
|
+
localStorage.setItem(reviewSurfacePreferencesKey, JSON.stringify({ sidebarCollapsed }));
|
|
9557
|
+
} catch {}
|
|
9558
|
+
};
|
|
8810
9559
|
const getSnapshotReviewComments = (snapshot) => {
|
|
8811
9560
|
if (!snapshot.reviewComments?.length) return emptyReviewComments;
|
|
8812
9561
|
return getReviewCommentsFromState({
|
|
@@ -8828,7 +9577,9 @@ const disabledCommitMessage = async () => ({
|
|
|
8828
9577
|
reason: "Shared walkthroughs are read-only.",
|
|
8829
9578
|
status: "unavailable"
|
|
8830
9579
|
});
|
|
8831
|
-
|
|
9580
|
+
const mobileSidebarMediaQuery = "(max-width: 720px)";
|
|
9581
|
+
const shouldCollapseSidebarInitially = () => readStoredSidebarCollapsed() ?? (typeof window !== "undefined" && window.matchMedia(mobileSidebarMediaQuery).matches);
|
|
9582
|
+
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
9583
|
const canComment = commenting?.canComment ?? Boolean(interactive);
|
|
8833
9584
|
const deleteShare = useCallback(async () => {
|
|
8834
9585
|
if (!onDeleteShare || !window.confirm("Delete this shared walkthrough? This cannot be undone.")) return;
|
|
@@ -8851,23 +9602,44 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8851
9602
|
const keymap = useMemo(() => createDefaultConfig().keymap, []);
|
|
8852
9603
|
const [fileSearchQuery, setFileSearchQuery] = useState("");
|
|
8853
9604
|
const [uncontrolledSidebarMode, setUncontrolledSidebarMode] = useState(() => initialMode ?? (interactive ? "tree" : "walkthrough"));
|
|
8854
|
-
const [sidebarCollapsed, setSidebarCollapsed] = useState(
|
|
9605
|
+
const [sidebarCollapsed, setSidebarCollapsed] = useState(null);
|
|
9606
|
+
const sidebarCollapsedRef = useRef(null);
|
|
9607
|
+
const sidebarInteractedRef = useRef(false);
|
|
9608
|
+
const updateSidebarCollapsed = useCallback((value, persist) => {
|
|
9609
|
+
sidebarCollapsedRef.current = value;
|
|
9610
|
+
setSidebarCollapsed(value);
|
|
9611
|
+
if (persist) writeStoredSidebarCollapsed(value);
|
|
9612
|
+
}, []);
|
|
9613
|
+
const toggleSidebar = useCallback(() => {
|
|
9614
|
+
sidebarInteractedRef.current = true;
|
|
9615
|
+
updateSidebarCollapsed(!(sidebarCollapsedRef.current ?? shouldCollapseSidebarInitially()), true);
|
|
9616
|
+
}, [updateSidebarCollapsed]);
|
|
9617
|
+
useEffect(() => {
|
|
9618
|
+
const timeout = window.setTimeout(() => {
|
|
9619
|
+
if (!sidebarInteractedRef.current) updateSidebarCollapsed(shouldCollapseSidebarInitially(), false);
|
|
9620
|
+
}, 0);
|
|
9621
|
+
return () => window.clearTimeout(timeout);
|
|
9622
|
+
}, [updateSidebarCollapsed]);
|
|
8855
9623
|
useEffect(() => {
|
|
8856
9624
|
const handleKeyDown = (event) => {
|
|
8857
9625
|
if (event.repeat || !matchesShortcut(event, keymap, "toggleSidebar")) return;
|
|
8858
9626
|
event.preventDefault();
|
|
8859
|
-
|
|
9627
|
+
toggleSidebar();
|
|
8860
9628
|
};
|
|
8861
9629
|
window.addEventListener("keydown", handleKeyDown);
|
|
8862
9630
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
8863
|
-
}, [keymap]);
|
|
9631
|
+
}, [keymap, toggleSidebar]);
|
|
8864
9632
|
const sidebarMode = Boolean(initialMode && onModeChange) && initialMode ? initialMode : uncontrolledSidebarMode;
|
|
8865
9633
|
const [treeScrollTarget, setTreeScrollTarget] = useState(null);
|
|
8866
9634
|
const { bumpItemVersion, collapsed, expandedGenerated, itemVersionByKey, selectedPath, setSelectedPath, toggleCollapsed, toggleViewed, viewed } = useReviewFileState({ initialSelectedPath: snapshot.files[0]?.path ?? null });
|
|
8867
9635
|
const { resizeSidebar, sidebarWidth } = useResizableSidebar({
|
|
8868
9636
|
collapseThreshold: 80,
|
|
8869
|
-
onCollapse: () =>
|
|
9637
|
+
onCollapse: () => {
|
|
9638
|
+
sidebarInteractedRef.current = true;
|
|
9639
|
+
updateSidebarCollapsed(true, true);
|
|
9640
|
+
},
|
|
8870
9641
|
onWidthCommit: writeSharedSidebarWidth,
|
|
9642
|
+
position: sidebarPosition,
|
|
8871
9643
|
readWidth: readSharedSidebarWidth
|
|
8872
9644
|
});
|
|
8873
9645
|
const snapshotReviewComments = useMemo(() => getSnapshotReviewComments(snapshot), [snapshot]);
|
|
@@ -8878,7 +9650,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8878
9650
|
} : comment), [editedReviewCommentBodies, snapshotReviewComments]);
|
|
8879
9651
|
const [localReviewComments, setLocalReviewComments] = useState(emptyReviewComments);
|
|
8880
9652
|
const reviewComments = useMemo(() => mergeReviewComments(visibleSnapshotReviewComments, localReviewComments), [localReviewComments, visibleSnapshotReviewComments]);
|
|
8881
|
-
const { activeReviewCommentDraftRef, activeReviewCommentDraftState, clearCommentFocus, createComment, deleteComment: deleteLocalComment, focusCommentId, focusCommentRequest, reviewCommentsRef, updateActiveReviewCommentDraft, updateComment } = useReviewCommentDrafts({
|
|
9653
|
+
const { activeReviewCommentDraftRef, activeReviewCommentDraftState, clearCommentFocus, createComment, deleteComment: deleteLocalComment, focusComment, focusCommentId, focusCommentRequest, reviewCommentsRef, updateActiveReviewCommentDraft, updateComment } = useReviewCommentDrafts({
|
|
8882
9654
|
canCreateComment: canComment,
|
|
8883
9655
|
comments: reviewComments,
|
|
8884
9656
|
onCommentFileChange: bumpItemVersion,
|
|
@@ -8899,11 +9671,13 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8899
9671
|
const [generalCommentSubmitting, setGeneralCommentSubmitting] = useState(false);
|
|
8900
9672
|
const [pullRequestReviewSubmitting, setPullRequestReviewSubmitting] = useState(null);
|
|
8901
9673
|
const [pullRequestCloseSubmitting, setPullRequestCloseSubmitting] = useState(false);
|
|
9674
|
+
const [pullRequestReadySubmitting, setPullRequestReadySubmitting] = useState(false);
|
|
8902
9675
|
const [pullRequestMergeSubmitting, setPullRequestMergeSubmitting] = useState(false);
|
|
8903
9676
|
const [walkthroughRequestPending, setWalkthroughRequestPending] = useState(false);
|
|
8904
9677
|
const walkthroughRequestPendingRef = useRef(false);
|
|
8905
9678
|
const [walkthroughRequestId, setWalkthroughRequestId] = useState(0);
|
|
8906
9679
|
const interactiveRef = useRef(interactive);
|
|
9680
|
+
const handledHashTargetRef = useRef(null);
|
|
8907
9681
|
const visibleFiles = useMemo(() => sortFiles(snapshot.files).filter((file) => fuzzyMatches(file.path, fileSearchQuery) && fileHasVisibleDiff(file, snapshot.preferences.showWhitespace)), [
|
|
8908
9682
|
fileSearchQuery,
|
|
8909
9683
|
snapshot.files,
|
|
@@ -9041,6 +9815,18 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9041
9815
|
pullRequestCloseSubmitting,
|
|
9042
9816
|
snapshot.repository.source
|
|
9043
9817
|
]);
|
|
9818
|
+
const markPullRequestReady = useCallback(() => {
|
|
9819
|
+
const source = snapshot.repository.source;
|
|
9820
|
+
if (!interactive?.onMarkPullRequestReady || pullRequestReadySubmitting || source.type !== "pull-request" || source.reviewStatus?.markReady?.disabled === true || !source.reviewStatus?.markReady) return;
|
|
9821
|
+
setPullRequestReadySubmitting(true);
|
|
9822
|
+
return Promise.resolve(interactive.onMarkPullRequestReady()).catch((error) => {
|
|
9823
|
+
window.alert(error instanceof Error ? error.message : String(error));
|
|
9824
|
+
}).finally(() => setPullRequestReadySubmitting(false));
|
|
9825
|
+
}, [
|
|
9826
|
+
interactive,
|
|
9827
|
+
pullRequestReadySubmitting,
|
|
9828
|
+
snapshot.repository.source
|
|
9829
|
+
]);
|
|
9044
9830
|
const mergePullRequest = useCallback((options) => {
|
|
9045
9831
|
if (!interactive?.onMergePullRequest || pullRequestMergeSubmitting) return;
|
|
9046
9832
|
setPullRequestMergeSubmitting(true);
|
|
@@ -9150,6 +9936,56 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9150
9936
|
request: (current?.request ?? 0) + 1
|
|
9151
9937
|
}));
|
|
9152
9938
|
}, [setSelectedPath]);
|
|
9939
|
+
const activateReviewComment = useCallback((comment) => {
|
|
9940
|
+
changeSidebarMode("tree");
|
|
9941
|
+
setSelectedPath(comment.filePath);
|
|
9942
|
+
focusComment(comment.id);
|
|
9943
|
+
setTreeScrollTarget((current) => ({
|
|
9944
|
+
behavior: "smooth",
|
|
9945
|
+
commentId: comment.id,
|
|
9946
|
+
path: comment.filePath,
|
|
9947
|
+
request: (current?.request ?? 0) + 1
|
|
9948
|
+
}));
|
|
9949
|
+
}, [
|
|
9950
|
+
changeSidebarMode,
|
|
9951
|
+
focusComment,
|
|
9952
|
+
setSelectedPath
|
|
9953
|
+
]);
|
|
9954
|
+
const activateHashTarget = useCallback(() => {
|
|
9955
|
+
const target = getLocationHashTarget();
|
|
9956
|
+
if (!target || handledHashTargetRef.current === target) return;
|
|
9957
|
+
const reviewComment = reviewComments.find((comment) => commentMatchesHashTarget(comment, target));
|
|
9958
|
+
if (reviewComment) {
|
|
9959
|
+
handledHashTargetRef.current = target;
|
|
9960
|
+
activateReviewComment(reviewComment);
|
|
9961
|
+
return;
|
|
9962
|
+
}
|
|
9963
|
+
for (const thread of generalCommentThreads) {
|
|
9964
|
+
const generalComment = thread.comments.find((comment) => commentMatchesHashTarget(comment, target)) ?? (thread.id === target ? thread.comments[0] : void 0);
|
|
9965
|
+
if (generalComment) {
|
|
9966
|
+
handledHashTargetRef.current = target;
|
|
9967
|
+
activateGeneralComment(generalComment.id);
|
|
9968
|
+
return;
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
}, [
|
|
9972
|
+
activateGeneralComment,
|
|
9973
|
+
activateReviewComment,
|
|
9974
|
+
generalCommentThreads,
|
|
9975
|
+
reviewComments
|
|
9976
|
+
]);
|
|
9977
|
+
useEffect(() => {
|
|
9978
|
+
const timeout = window.setTimeout(activateHashTarget, 0);
|
|
9979
|
+
return () => window.clearTimeout(timeout);
|
|
9980
|
+
}, [activateHashTarget]);
|
|
9981
|
+
useEffect(() => {
|
|
9982
|
+
const handleHashChange = () => {
|
|
9983
|
+
handledHashTargetRef.current = null;
|
|
9984
|
+
activateHashTarget();
|
|
9985
|
+
};
|
|
9986
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
9987
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
9988
|
+
}, [activateHashTarget]);
|
|
9153
9989
|
const updateSelectedPathFromScroll = useCallback((viewer) => {
|
|
9154
9990
|
const nextPath = getSelectedPathFromScroll(viewer, visibleFiles, snapshot.preferences.showWhitespace);
|
|
9155
9991
|
if (nextPath) setSelectedPath((current) => current === nextPath ? current : nextPath);
|
|
@@ -9207,9 +10043,10 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9207
10043
|
const isTerminalMergeState = sourceMergeState ? isTerminalPullRequestMergeState(sourceMergeState) : false;
|
|
9208
10044
|
const sourceMergeStatusBadge = sourceMergeState && isTerminalMergeState ? /* @__PURE__ */ jsx(PullRequestMergeStatusBadge, { mergeState: sourceMergeState }) : null;
|
|
9209
10045
|
const sourceDescriptionActions = interactive && source.type === "pull-request" ? /* @__PURE__ */ jsx(PullRequestReviewButtons, {
|
|
9210
|
-
disabled: pullRequestReviewSubmitting != null || pullRequestCloseSubmitting,
|
|
10046
|
+
disabled: pullRequestReviewSubmitting != null || pullRequestCloseSubmitting || pullRequestReadySubmitting,
|
|
9211
10047
|
hasPendingComments: getPendingPullRequestReviewComments(localReviewComments, activeReviewCommentDraftState).length > 0,
|
|
9212
10048
|
onClosePullRequest: closePullRequest,
|
|
10049
|
+
onMarkPullRequestReady: markPullRequestReady,
|
|
9213
10050
|
onSubmitReview: submitReview,
|
|
9214
10051
|
reviewStatus: source.reviewStatus,
|
|
9215
10052
|
showCommentReview: source.provider === "github" || source.host === "github.com",
|
|
@@ -9328,9 +10165,10 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9328
10165
|
children: settingsBar
|
|
9329
10166
|
}) : null] }) : void 0;
|
|
9330
10167
|
return /* @__PURE__ */ jsxs("div", {
|
|
9331
|
-
className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}${sidebarCollapsed ? " sidebar-collapsed" : ""}`,
|
|
10168
|
+
className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}${sidebarCollapsed === null ? " sidebar-auto" : sidebarCollapsed ? " sidebar-collapsed" : ""}`,
|
|
10169
|
+
"data-sidebar-position": sidebarPosition,
|
|
9332
10170
|
"data-theme": shellTheme,
|
|
9333
|
-
style: sidebarCollapsed ? void 0 : { gridTemplateColumns: `${sidebarWidth}px 0 minmax(0, 1fr)` },
|
|
10171
|
+
style: sidebarCollapsed !== false ? void 0 : { gridTemplateColumns: sidebarPosition === "right" ? `minmax(0, 1fr) 0 ${sidebarWidth}px` : `${sidebarWidth}px 0 minmax(0, 1fr)` },
|
|
9334
10172
|
children: [
|
|
9335
10173
|
/* @__PURE__ */ jsx(ReviewTopBar, {
|
|
9336
10174
|
actions: topBarActions,
|
|
@@ -9371,7 +10209,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9371
10209
|
mode: sidebarMode,
|
|
9372
10210
|
modes: reviewModes,
|
|
9373
10211
|
onModeChange: changeSidebarMode,
|
|
9374
|
-
onToggleSidebar:
|
|
10212
|
+
onToggleSidebar: toggleSidebar,
|
|
9375
10213
|
repository: repositoryLinkUrl ? /* @__PURE__ */ jsx("a", {
|
|
9376
10214
|
className: "review-top-bar-repository",
|
|
9377
10215
|
href: repositoryLinkUrl,
|
|
@@ -9383,7 +10221,8 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9383
10221
|
children: rootLabel
|
|
9384
10222
|
}),
|
|
9385
10223
|
repositoryTooltip: snapshot.repository.root,
|
|
9386
|
-
sidebarCollapsed,
|
|
10224
|
+
sidebarCollapsed: sidebarCollapsed ?? false,
|
|
10225
|
+
sidebarPosition,
|
|
9387
10226
|
toggleTitle: `${sidebarCollapsed ? "Expand" : "Collapse"} sidebar`
|
|
9388
10227
|
}),
|
|
9389
10228
|
/* @__PURE__ */ jsxs("aside", {
|
|
@@ -9461,6 +10300,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9461
10300
|
onCancelEdit: cancelEditGeneralComment,
|
|
9462
10301
|
onChangeDraft: setGeneralCommentDraft,
|
|
9463
10302
|
onChangeEditDraft: setGeneralCommentEditDraft,
|
|
10303
|
+
onResolveDiscussion: resolveDiscussion,
|
|
9464
10304
|
onSaveEdit: saveGeneralCommentEdit,
|
|
9465
10305
|
onStartEdit: startEditGeneralComment,
|
|
9466
10306
|
onSubmit: submitGeneralComment,
|