@nkzw/codiff-core 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SharedWalkthroughApp.d.ts +2 -1
- package/dist/app/components/DefinitionPopover.d.ts +12 -0
- package/dist/app/components/OpenReviewSourceDialog.d.ts +6 -0
- package/dist/app/components/OpenReviewSourceMenu.d.ts +5 -0
- package/dist/app/components/Panels.d.ts +8 -2
- package/dist/app/components/ReadOnlyMarkdownView.d.ts +1 -0
- package/dist/app/components/ResolvedThreadDisclosure.d.ts +9 -0
- package/dist/app/components/ReviewCodeView.d.ts +4 -2
- package/dist/app/components/ReviewModeControl.d.ts +14 -0
- package/dist/app/components/ReviewTopBar.d.ts +14 -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/useAppCommands.d.ts +3 -2
- 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/keyboard-layout.d.ts +13 -0
- package/dist/config/keymap.d.ts +3 -1
- package/dist/config/types.d.ts +3 -0
- package/dist/lib/app-types.d.ts +1 -0
- package/dist/lib/code-view-options.d.ts +1 -1
- package/dist/lib/files.d.ts +10 -1
- package/dist/lib/identifier-navigation.d.ts +10 -0
- package/dist/lib/keyboard.d.ts +1 -0
- package/dist/react.mjs +1163 -278
- package/dist/share.mjs +8 -7
- package/dist/style.css +392 -52
- package/dist/types.d.ts +37 -1
- package/package.json +6 -5
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,8 +18,8 @@ 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
|
+
import "@phosphor-icons/react/CircleNotch";
|
|
22
23
|
import { PowerIcon } from "@phosphor-icons/react/Power";
|
|
23
24
|
import { SealQuestionIcon } from "@phosphor-icons/react/SealQuestion";
|
|
24
25
|
import { WarningOctagonIcon } from "@phosphor-icons/react/WarningOctagon";
|
|
@@ -27,6 +28,8 @@ import { ColumnsIcon } from "@phosphor-icons/react/Columns";
|
|
|
27
28
|
import { ImageBrokenIcon } from "@phosphor-icons/react/ImageBroken";
|
|
28
29
|
import { SquareSplitVerticalIcon } from "@phosphor-icons/react/SquareSplitVertical";
|
|
29
30
|
import { CodeView, WorkerPoolContextProvider } from "@pierre/diffs/react";
|
|
31
|
+
import { CrosshairSimpleIcon } from "@phosphor-icons/react/CrosshairSimple";
|
|
32
|
+
import { ProhibitIcon } from "@phosphor-icons/react/Prohibit";
|
|
30
33
|
import { PersistentMarkdownEditor } from "@nkzw/mdx-editor/persistence";
|
|
31
34
|
import { GitBranchIcon } from "@phosphor-icons/react/GitBranch";
|
|
32
35
|
import { ShareNetworkIcon } from "@phosphor-icons/react/ShareNetwork";
|
|
@@ -293,13 +296,40 @@ const renderInlineMarkdown = (text) => {
|
|
|
293
296
|
return nodes.length > 0 ? nodes : text;
|
|
294
297
|
};
|
|
295
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
|
|
296
316
|
//#region app/components/ReadOnlyMarkdownView.tsx
|
|
297
|
-
const detailsBlockPattern = /<details\b([^>]*)>\s*<summary\b[^>]*>([\s\S]*?)<\/summary>([\s\S]*?)<\/details>/gi;
|
|
298
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;
|
|
299
322
|
const hasOpenAttribute = (attributes) => /(?:^|\s)open(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?(?:\s|$)/i.test(attributes);
|
|
300
323
|
const stripHtmlComments$1 = (value) => value.replaceAll(htmlCommentPattern$1, "");
|
|
301
|
-
const
|
|
302
|
-
|
|
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;
|
|
303
333
|
};
|
|
304
334
|
const normalizeReadOnlyMarkdownValue = (value) => {
|
|
305
335
|
const normalizedLineEndings = value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
@@ -309,10 +339,9 @@ const normalizeReadOnlyMarkdownValue = (value) => {
|
|
|
309
339
|
let pendingBlankLine = false;
|
|
310
340
|
let fenceMarker = null;
|
|
311
341
|
for (const line of lines) {
|
|
312
|
-
const currentFenceMarker = getFenceMarker(line);
|
|
313
342
|
if (fenceMarker) {
|
|
314
343
|
normalizedLines.push(line);
|
|
315
|
-
if (
|
|
344
|
+
if (isClosingFence(fenceMarker, line)) fenceMarker = null;
|
|
316
345
|
continue;
|
|
317
346
|
}
|
|
318
347
|
if (!line.trim()) {
|
|
@@ -322,27 +351,83 @@ const normalizeReadOnlyMarkdownValue = (value) => {
|
|
|
322
351
|
if (pendingBlankLine && normalizedLines.length > 0) normalizedLines.push("");
|
|
323
352
|
pendingBlankLine = false;
|
|
324
353
|
normalizedLines.push(line);
|
|
325
|
-
|
|
354
|
+
const openingFenceMarker = getOpeningFenceMarker(line);
|
|
355
|
+
if (openingFenceMarker) fenceMarker = openingFenceMarker;
|
|
326
356
|
}
|
|
327
357
|
const normalizedValue = normalizedLines.join("\n");
|
|
328
358
|
return preserveSingleTrailingNewline && normalizedValue ? `${normalizedValue}\n` : normalizedValue;
|
|
329
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
|
+
};
|
|
330
382
|
const parseMarkdownDetails = (value) => {
|
|
331
383
|
const parts = [];
|
|
332
384
|
let lastIndex = 0;
|
|
333
|
-
let
|
|
334
|
-
while (
|
|
335
|
-
|
|
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({
|
|
336
420
|
type: "markdown",
|
|
337
|
-
value: value.slice(lastIndex,
|
|
421
|
+
value: value.slice(lastIndex, detailsOpen.index)
|
|
338
422
|
});
|
|
339
423
|
parts.push({
|
|
340
|
-
body:
|
|
341
|
-
open: hasOpenAttribute(
|
|
342
|
-
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(),
|
|
343
427
|
type: "details"
|
|
344
428
|
});
|
|
345
|
-
lastIndex =
|
|
429
|
+
lastIndex = detailsTagPattern.lastIndex;
|
|
430
|
+
searchIndex = lastIndex;
|
|
346
431
|
}
|
|
347
432
|
if (lastIndex < value.length) parts.push({
|
|
348
433
|
type: "markdown",
|
|
@@ -351,6 +436,42 @@ const parseMarkdownDetails = (value) => {
|
|
|
351
436
|
return parts;
|
|
352
437
|
};
|
|
353
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
|
+
}
|
|
354
475
|
function MarkdownSegment({ additionalPlugins, ariaLabel, contentClassName, editorClassName, onHeightChange, value }) {
|
|
355
476
|
const normalizedValue = normalizeReadOnlyMarkdownValue(value);
|
|
356
477
|
if (!normalizedValue.trim()) return null;
|
|
@@ -382,17 +503,22 @@ function MarkdownParts({ additionalPlugins, ariaLabel, contentClassName, editorC
|
|
|
382
503
|
onHeightChange,
|
|
383
504
|
value: part.value
|
|
384
505
|
}, `markdown:${index}`);
|
|
506
|
+
const bodyParts = parseMarkdownDetails(part.body);
|
|
507
|
+
const code = getOwnFencedCode(bodyParts);
|
|
385
508
|
return /* @__PURE__ */ jsxs("details", {
|
|
386
509
|
className: "codiff-markdown-details",
|
|
387
510
|
onToggle: (event) => onHeightChange?.(event.currentTarget.getBoundingClientRect().height),
|
|
388
511
|
open: part.open,
|
|
389
|
-
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, {
|
|
390
516
|
additionalPlugins,
|
|
391
517
|
ariaLabel: `${ariaLabel} details`,
|
|
392
518
|
contentClassName,
|
|
393
519
|
editorClassName,
|
|
394
520
|
onHeightChange,
|
|
395
|
-
parts:
|
|
521
|
+
parts: bodyParts
|
|
396
522
|
})]
|
|
397
523
|
}, `details:${index}`);
|
|
398
524
|
});
|
|
@@ -496,8 +622,7 @@ const getPlanCommentTargetLabel = (anchor) => {
|
|
|
496
622
|
const entity = anchor.kind === "text" ? "Selection" : planCommentEntityLabels[anchor.block.type] ?? "Block";
|
|
497
623
|
const targetText = (anchor.kind === "text" ? anchor.quote?.exact : anchor.block.text)?.replaceAll(/\s+/g, " ").trim();
|
|
498
624
|
if (!targetText) return entity;
|
|
499
|
-
|
|
500
|
-
return `${entity} · ${targetText.length > maximumLength ? `${targetText.slice(0, maximumLength - 1).trimEnd()}…` : targetText}`;
|
|
625
|
+
return `${entity} · ${targetText.length > 48 ? `${targetText.slice(0, 47).trimEnd()}…` : targetText}`;
|
|
501
626
|
};
|
|
502
627
|
const escapeInlineMarkdown = (value) => value.replaceAll("\\", String.raw`\\`).replaceAll(/([`*_[\]<>])/g, String.raw`\$1`);
|
|
503
628
|
const getPlanCommentAuthorLabel = (author) => {
|
|
@@ -1419,22 +1544,14 @@ const getFirstVisibleSection = (file, showWhitespace) => getVisibleDiffSections(
|
|
|
1419
1544
|
//#region lib/files.ts
|
|
1420
1545
|
const statusForTree = {
|
|
1421
1546
|
added: "added",
|
|
1547
|
+
conflicted: "modified",
|
|
1422
1548
|
deleted: "deleted",
|
|
1423
1549
|
modified: "modified",
|
|
1424
1550
|
renamed: "renamed",
|
|
1425
1551
|
untracked: "untracked"
|
|
1426
1552
|
};
|
|
1427
1553
|
const fileTreeSort = (left, right) => compareTreePaths(left.path, right.path);
|
|
1428
|
-
const
|
|
1429
|
-
const homePath = path.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~");
|
|
1430
|
-
const parts = homePath.split("/").filter(Boolean);
|
|
1431
|
-
if (parts.length <= 2) return homePath;
|
|
1432
|
-
const prefix = homePath.startsWith("/") ? "/" : "";
|
|
1433
|
-
const [first, ...rest] = parts;
|
|
1434
|
-
const last = rest.pop();
|
|
1435
|
-
const middle = rest.map((part) => part[0]).join("/");
|
|
1436
|
-
return `${prefix}${first}/${middle ? `${middle}/` : ""}${last}`;
|
|
1437
|
-
};
|
|
1554
|
+
const abbreviateHomePath = (path) => path.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^[A-Za-z]:[/\\]Users[/\\][^/\\]+(?=[/\\]|$)/, "~");
|
|
1438
1555
|
function compareTreePaths(leftPath, rightPath) {
|
|
1439
1556
|
const leftParts = leftPath.split("/");
|
|
1440
1557
|
const rightParts = rightPath.split("/");
|
|
@@ -1566,22 +1683,23 @@ function ReviewFileTree({ files, onActivatePath, reloadDeltaPaths = emptyPaths$2
|
|
|
1566
1683
|
scrollSelectedPathIntoView,
|
|
1567
1684
|
selectedPath
|
|
1568
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]);
|
|
1569
1696
|
return /* @__PURE__ */ jsx("div", {
|
|
1570
1697
|
className: "file-tree-shell",
|
|
1571
1698
|
ref: treeHostRef,
|
|
1572
1699
|
children: /* @__PURE__ */ jsx(FileTree, {
|
|
1573
1700
|
className: "file-tree",
|
|
1574
1701
|
model,
|
|
1575
|
-
onClick:
|
|
1576
|
-
for (const target of event.nativeEvent.composedPath()) {
|
|
1577
|
-
if (!("getAttribute" in target) || typeof target.getAttribute !== "function") continue;
|
|
1578
|
-
const path = target.getAttribute("data-item-path");
|
|
1579
|
-
if (path && filePathSet.has(path)) {
|
|
1580
|
-
onActivatePath(path);
|
|
1581
|
-
return;
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
}, [filePathSet, onActivatePath])
|
|
1702
|
+
onClick: handleTreeClick
|
|
1585
1703
|
})
|
|
1586
1704
|
});
|
|
1587
1705
|
}
|
|
@@ -1633,26 +1751,168 @@ const syncTreeShadowStyle = (treeHost, styleAttribute, css) => {
|
|
|
1633
1751
|
if (!existingStyle) shadowRoot.append(style);
|
|
1634
1752
|
return true;
|
|
1635
1753
|
};
|
|
1754
|
+
[...[..."abcdefghijklmnopqrstuvwxyz"].map((letter) => `Key${letter.toUpperCase()}`), ..."1234567890".split("").map((digit) => `Digit${digit}`)];
|
|
1755
|
+
const codeForCharacter = (character, shiftKey) => null;
|
|
1756
|
+
const shiftedCharacterForCode = (code) => null;
|
|
1757
|
+
const hasKeyboardLayout = () => false;
|
|
1636
1758
|
//#endregion
|
|
1637
1759
|
//#region config/keymap.ts
|
|
1638
1760
|
const isMac = () => navigator.platform.toLowerCase().includes("mac");
|
|
1761
|
+
const unshiftedPunctuationCodes = {
|
|
1762
|
+
"-": "Minus",
|
|
1763
|
+
",": "Comma",
|
|
1764
|
+
";": "Semicolon",
|
|
1765
|
+
".": "Period",
|
|
1766
|
+
"'": "Quote",
|
|
1767
|
+
"[": "BracketLeft",
|
|
1768
|
+
"]": "BracketRight",
|
|
1769
|
+
"/": "Slash",
|
|
1770
|
+
"\\": "Backslash",
|
|
1771
|
+
"`": "Backquote",
|
|
1772
|
+
"=": "Equal"
|
|
1773
|
+
};
|
|
1774
|
+
const shiftedPunctuationCodes = {
|
|
1775
|
+
_: "Minus",
|
|
1776
|
+
":": "Semicolon",
|
|
1777
|
+
"!": "Digit1",
|
|
1778
|
+
"?": "Slash",
|
|
1779
|
+
"\"": "Quote",
|
|
1780
|
+
"(": "Digit9",
|
|
1781
|
+
")": "Digit0",
|
|
1782
|
+
"{": "BracketLeft",
|
|
1783
|
+
"}": "BracketRight",
|
|
1784
|
+
"@": "Digit2",
|
|
1785
|
+
"*": "Digit8",
|
|
1786
|
+
"&": "Digit7",
|
|
1787
|
+
"#": "Digit3",
|
|
1788
|
+
"%": "Digit5",
|
|
1789
|
+
"^": "Digit6",
|
|
1790
|
+
"+": "Equal",
|
|
1791
|
+
"<": "Comma",
|
|
1792
|
+
">": "Period",
|
|
1793
|
+
"|": "Backslash",
|
|
1794
|
+
"~": "Backquote",
|
|
1795
|
+
$: "Digit4"
|
|
1796
|
+
};
|
|
1797
|
+
const physicalCode = (key, shiftKey) => {
|
|
1798
|
+
const layoutCode = codeForCharacter(key, shiftKey);
|
|
1799
|
+
if (layoutCode !== null) return layoutCode;
|
|
1800
|
+
if (hasKeyboardLayout()) return null;
|
|
1801
|
+
if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
|
|
1802
|
+
if (/^[0-9]$/.test(key)) return shiftKey ? null : `Digit${key}`;
|
|
1803
|
+
return (shiftKey ? shiftedPunctuationCodes : unshiftedPunctuationCodes)[key] ?? null;
|
|
1804
|
+
};
|
|
1805
|
+
const resolveComboKey = (spelled, shiftKey) => {
|
|
1806
|
+
if (!shiftKey || spelled.length !== 1 || !hasKeyboardLayout()) return {
|
|
1807
|
+
code: physicalCode(spelled, shiftKey),
|
|
1808
|
+
key: spelled,
|
|
1809
|
+
requiresCode: false
|
|
1810
|
+
};
|
|
1811
|
+
const shiftedCode = codeForCharacter(spelled, true);
|
|
1812
|
+
if (shiftedCode !== null) return {
|
|
1813
|
+
code: shiftedCode,
|
|
1814
|
+
key: spelled,
|
|
1815
|
+
requiresCode: false
|
|
1816
|
+
};
|
|
1817
|
+
const unshiftedCode = codeForCharacter(spelled, false);
|
|
1818
|
+
const shiftedCharacter = unshiftedCode === null ? null : shiftedCharacterForCode(unshiftedCode);
|
|
1819
|
+
if (unshiftedCode !== null && shiftedCharacter !== null) return {
|
|
1820
|
+
code: unshiftedCode,
|
|
1821
|
+
key: shiftedCharacter,
|
|
1822
|
+
requiresCode: true
|
|
1823
|
+
};
|
|
1824
|
+
return {
|
|
1825
|
+
code: null,
|
|
1826
|
+
key: spelled,
|
|
1827
|
+
requiresCode: false
|
|
1828
|
+
};
|
|
1829
|
+
};
|
|
1639
1830
|
const parseKeyCombo = (combo) => {
|
|
1640
1831
|
const parts = combo.split("+").map((part) => part.trim().toLowerCase());
|
|
1641
1832
|
const mac = isMac();
|
|
1833
|
+
const shiftKey = parts.includes("shift");
|
|
1834
|
+
const { code, key, requiresCode } = resolveComboKey(parts.find((part) => part !== "mod" && part !== "ctrl" && part !== "alt" && part !== "shift" && part !== "meta") ?? "", shiftKey);
|
|
1642
1835
|
return {
|
|
1643
1836
|
altKey: parts.includes("alt"),
|
|
1837
|
+
code,
|
|
1644
1838
|
ctrlKey: mac ? parts.includes("ctrl") : parts.includes("mod") || parts.includes("ctrl"),
|
|
1645
|
-
key
|
|
1839
|
+
key,
|
|
1646
1840
|
metaKey: mac ? parts.includes("mod") || parts.includes("meta") : parts.includes("meta"),
|
|
1647
|
-
|
|
1841
|
+
requiresCode,
|
|
1842
|
+
shiftKey
|
|
1648
1843
|
};
|
|
1649
1844
|
};
|
|
1650
1845
|
const getBindingCombos = (binding) => Array.isArray(binding) ? binding : [binding];
|
|
1651
|
-
const
|
|
1846
|
+
const isDeadOrCharacterKey = (key) => key === "Dead" || key.length === 1;
|
|
1847
|
+
const matchesTypedKey = (event, parsed) => event.key.toLowerCase() === parsed.key && (!parsed.requiresCode || parsed.code !== null && event.code === parsed.code);
|
|
1848
|
+
const matchesPhysicalKey = (event, parsed) => parsed.altKey && parsed.code !== null && event.code === parsed.code;
|
|
1849
|
+
const matchesCombo = (event, combo, matchesKey) => {
|
|
1652
1850
|
const parsed = parseKeyCombo(combo);
|
|
1653
|
-
return event
|
|
1851
|
+
return matchesKey(event, parsed) && event.altKey === parsed.altKey && event.ctrlKey === parsed.ctrlKey && event.metaKey === parsed.metaKey && event.shiftKey === parsed.shiftKey;
|
|
1654
1852
|
};
|
|
1655
|
-
const
|
|
1853
|
+
const matchesBinding = (event, binding, matchesKey) => getBindingCombos(binding).some((combo) => matchesCombo(event, combo, matchesKey));
|
|
1854
|
+
const canFallBackToPhysicalKey = (event, keymap) => event.altKey && isMac() && isDeadOrCharacterKey(event.key) && !Object.values(keymap).some((binding) => matchesBinding(event, binding, matchesTypedKey));
|
|
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
|
+
}
|
|
1656
1916
|
//#endregion
|
|
1657
1917
|
//#region app/components/merge-request/GeneralComments.tsx
|
|
1658
1918
|
const getAuthorDisplayName = (author) => author.name || author.login;
|
|
@@ -1696,13 +1956,14 @@ function SubmittedAtTime({ submittedAt }) {
|
|
|
1696
1956
|
}
|
|
1697
1957
|
function ReadOnlyGeneralCommentCard({ className = "", comment, focused = false }) {
|
|
1698
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(" ");
|
|
1699
1965
|
return /* @__PURE__ */ jsxs("article", {
|
|
1700
|
-
className:
|
|
1701
|
-
"review-comment",
|
|
1702
|
-
"general-comment-card",
|
|
1703
|
-
focused ? "focused" : "",
|
|
1704
|
-
className
|
|
1705
|
-
].filter(Boolean).join(" "),
|
|
1966
|
+
className: classes,
|
|
1706
1967
|
id: getGeneralCommentElementId(comment.id),
|
|
1707
1968
|
children: [/* @__PURE__ */ jsx(Avatar, {
|
|
1708
1969
|
name: displayName,
|
|
@@ -1839,13 +2100,16 @@ function GeneralCommentCard({ comment, editDraft, editError, editing, editSubmit
|
|
|
1839
2100
|
})]
|
|
1840
2101
|
});
|
|
1841
2102
|
}
|
|
1842
|
-
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 }) {
|
|
1843
2104
|
const [replyDraft, setReplyDraft] = useState("");
|
|
1844
2105
|
const [replyError, setReplyError] = useState(null);
|
|
1845
2106
|
const [replying, setReplying] = useState(false);
|
|
1846
2107
|
const [showReply, setShowReply] = useState(false);
|
|
1847
2108
|
const [resolving, setResolving] = useState(false);
|
|
1848
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;
|
|
1849
2113
|
const submitReply = useCallback(() => {
|
|
1850
2114
|
const body = replyDraft.trim();
|
|
1851
2115
|
if (!body || replying) return;
|
|
@@ -1865,62 +2129,79 @@ function GeneralCommentThreadCard({ canComment, editDraft, editError, editingCom
|
|
|
1865
2129
|
]);
|
|
1866
2130
|
const toggleResolved = useCallback(() => {
|
|
1867
2131
|
if (resolving) return;
|
|
2132
|
+
const nextResolved = !effectiveResolved;
|
|
2133
|
+
setResolutionOverride({
|
|
2134
|
+
base: resolved,
|
|
2135
|
+
value: nextResolved
|
|
2136
|
+
});
|
|
1868
2137
|
setResolving(true);
|
|
1869
|
-
onResolve(thread.id,
|
|
2138
|
+
onResolve(thread.id, nextResolved).catch((error) => {
|
|
2139
|
+
setResolutionOverride(null);
|
|
1870
2140
|
window.alert(error instanceof Error ? error.message : String(error));
|
|
1871
2141
|
}).finally(() => setResolving(false));
|
|
1872
2142
|
}, [
|
|
2143
|
+
effectiveResolved,
|
|
1873
2144
|
onResolve,
|
|
1874
2145
|
resolved,
|
|
1875
2146
|
resolving,
|
|
1876
2147
|
thread.id
|
|
1877
2148
|
]);
|
|
1878
|
-
|
|
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", {
|
|
1879
2194
|
className: "general-comment-thread",
|
|
1880
|
-
children:
|
|
1881
|
-
thread.comments.
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
onChangeEditDraft,
|
|
1891
|
-
onDelete,
|
|
1892
|
-
onSaveEdit,
|
|
1893
|
-
onStartEdit
|
|
1894
|
-
}, comment.id)),
|
|
1895
|
-
thread.canReply && canComment && !resolved ? showReply ? /* @__PURE__ */ jsx(GeneralCommentComposer, {
|
|
1896
|
-
disabled: false,
|
|
1897
|
-
draft: replyDraft,
|
|
1898
|
-
error: replyError,
|
|
1899
|
-
gitIdentity: null,
|
|
1900
|
-
keymap,
|
|
1901
|
-
onChangeDraft: setReplyDraft,
|
|
1902
|
-
onSubmit: submitReply,
|
|
1903
|
-
submitting: replying
|
|
1904
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
1905
|
-
className: "review-comment-thread-footer",
|
|
1906
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
1907
|
-
className: "review-comment-action",
|
|
1908
|
-
onClick: () => setShowReply(true),
|
|
1909
|
-
type: "button",
|
|
1910
|
-
children: "Reply"
|
|
1911
|
-
})
|
|
1912
|
-
}) : null,
|
|
1913
|
-
thread.canResolve ? /* @__PURE__ */ jsx("div", {
|
|
1914
|
-
className: "review-comment-thread-footer",
|
|
1915
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
1916
|
-
className: "review-comment-action",
|
|
1917
|
-
disabled: resolving,
|
|
1918
|
-
onClick: toggleResolved,
|
|
1919
|
-
type: "button",
|
|
1920
|
-
children: resolving ? "Saving" : resolved ? "Reopen" : "Resolve"
|
|
1921
|
-
})
|
|
1922
|
-
}) : null
|
|
1923
|
-
]
|
|
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
|
|
1924
2205
|
});
|
|
1925
2206
|
}
|
|
1926
2207
|
function SidebarGeneralCommentList({ comments, focusedCommentId, onActivateComment }) {
|
|
@@ -2028,7 +2309,7 @@ function GeneralCommentComposer({ disabled, draft, error, gitIdentity, keymap, o
|
|
|
2028
2309
|
})
|
|
2029
2310
|
});
|
|
2030
2311
|
}
|
|
2031
|
-
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 }) {
|
|
2032
2313
|
const commentsRef = useRef(null);
|
|
2033
2314
|
useEffect(() => {
|
|
2034
2315
|
if (focusedCommentId == null) return;
|
|
@@ -2054,6 +2335,7 @@ function MergeRequestCommentsView({ canComment, commenting, draft, editDraft, ed
|
|
|
2054
2335
|
editingCommentId,
|
|
2055
2336
|
editSubmitting,
|
|
2056
2337
|
focusedCommentId,
|
|
2338
|
+
focusedCommentRequest,
|
|
2057
2339
|
keymap,
|
|
2058
2340
|
onCancelEdit,
|
|
2059
2341
|
onChangeEditDraft,
|
|
@@ -2063,7 +2345,7 @@ function MergeRequestCommentsView({ canComment, commenting, draft, editDraft, ed
|
|
|
2063
2345
|
});
|
|
2064
2346
|
},
|
|
2065
2347
|
onReply: (threadId, body) => commenting?.onReplyGeneralComment(threadId, body) ?? Promise.reject(/* @__PURE__ */ new Error("Replying is unavailable.")),
|
|
2066
|
-
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.")),
|
|
2067
2349
|
onSaveEdit,
|
|
2068
2350
|
onStartEdit,
|
|
2069
2351
|
thread
|
|
@@ -2209,23 +2491,6 @@ const getReviewCommentsFromState = (state) => (state.reviewComments ?? []).flatM
|
|
|
2209
2491
|
});
|
|
2210
2492
|
const shouldDiscardReviewCommentOnEscape = (body, confirmDiscard = window.confirm) => body.trim().length === 0 || confirmDiscard("Discard this review comment?");
|
|
2211
2493
|
//#endregion
|
|
2212
|
-
//#region app/components/useCopiedState.ts
|
|
2213
|
-
function useCopiedState(timeoutMs) {
|
|
2214
|
-
const [copied, setCopied] = useState(false);
|
|
2215
|
-
const copiedTimerRef = useRef(null);
|
|
2216
|
-
useEffect(() => () => {
|
|
2217
|
-
if (copiedTimerRef.current != null) window.clearTimeout(copiedTimerRef.current);
|
|
2218
|
-
}, []);
|
|
2219
|
-
return [copied, useCallback(() => {
|
|
2220
|
-
setCopied(true);
|
|
2221
|
-
if (copiedTimerRef.current != null) window.clearTimeout(copiedTimerRef.current);
|
|
2222
|
-
copiedTimerRef.current = window.setTimeout(() => {
|
|
2223
|
-
setCopied(false);
|
|
2224
|
-
copiedTimerRef.current = null;
|
|
2225
|
-
}, timeoutMs);
|
|
2226
|
-
}, [timeoutMs])];
|
|
2227
|
-
}
|
|
2228
|
-
//#endregion
|
|
2229
2494
|
//#region app/components/Panels.tsx
|
|
2230
2495
|
const getPullRequestReviewActionStatus = (reviewStatus, event) => event === "APPROVE" ? reviewStatus?.approve : event === "COMMENT" ? reviewStatus?.comment : reviewStatus?.requestChanges;
|
|
2231
2496
|
const isPullRequestReviewActionDisabled = (reviewStatus, event) => getPullRequestReviewActionStatus(reviewStatus, event)?.disabled === true;
|
|
@@ -3273,6 +3538,7 @@ setCustomExtension("cts", "typescript");
|
|
|
3273
3538
|
setCustomExtension("mts", "typescript");
|
|
3274
3539
|
const statusLabel = {
|
|
3275
3540
|
added: "Added",
|
|
3541
|
+
conflicted: "Conflicted",
|
|
3276
3542
|
deleted: "Deleted",
|
|
3277
3543
|
modified: "Modified",
|
|
3278
3544
|
renamed: "Renamed",
|
|
@@ -3405,6 +3671,20 @@ const codeViewUnsafeCSS = `
|
|
|
3405
3671
|
cursor: progress;
|
|
3406
3672
|
}
|
|
3407
3673
|
|
|
3674
|
+
:host([data-codiff-definition-mode]) [data-codiff-identifier] {
|
|
3675
|
+
cursor: pointer;
|
|
3676
|
+
text-decoration-color: color-mix(in srgb, currentColor 38%, transparent);
|
|
3677
|
+
text-decoration-line: underline;
|
|
3678
|
+
text-decoration-style: dotted;
|
|
3679
|
+
text-decoration-thickness: 1px;
|
|
3680
|
+
text-underline-offset: 3px;
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
:host([data-codiff-definition-mode]) [data-codiff-identifier]:hover {
|
|
3684
|
+
text-decoration-color: currentColor;
|
|
3685
|
+
text-decoration-style: solid;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3408
3688
|
.codiff-search-mark {
|
|
3409
3689
|
background: var(--diffs-find-highlight-bg, rgb(255 216 92 / 0.65));
|
|
3410
3690
|
border-radius: 3px;
|
|
@@ -3431,8 +3711,179 @@ const codeViewUnsafeCSS = `
|
|
|
3431
3711
|
[data-selected-line] [data-gutter-utility-slot] {
|
|
3432
3712
|
display: none;
|
|
3433
3713
|
}
|
|
3714
|
+
|
|
3715
|
+
/* The imperative API wraps annotations in a node that resets white-space, but
|
|
3716
|
+
the React API slots them in bare, so they inherit "pre" from the shadow
|
|
3717
|
+
<pre> and prose never wraps. */
|
|
3718
|
+
[data-annotation-content] slot::slotted(*) {
|
|
3719
|
+
white-space: normal;
|
|
3720
|
+
}
|
|
3434
3721
|
`;
|
|
3435
3722
|
//#endregion
|
|
3723
|
+
//#region lib/identifier-navigation.ts
|
|
3724
|
+
const identifierPattern = /[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*/gu;
|
|
3725
|
+
const exactIdentifierPattern = /^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u;
|
|
3726
|
+
const identifierContainedPattern = /[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*/u;
|
|
3727
|
+
const commonKeywords = /* @__PURE__ */ new Set([
|
|
3728
|
+
"abstract",
|
|
3729
|
+
"as",
|
|
3730
|
+
"async",
|
|
3731
|
+
"await",
|
|
3732
|
+
"break",
|
|
3733
|
+
"case",
|
|
3734
|
+
"catch",
|
|
3735
|
+
"class",
|
|
3736
|
+
"const",
|
|
3737
|
+
"continue",
|
|
3738
|
+
"def",
|
|
3739
|
+
"default",
|
|
3740
|
+
"defer",
|
|
3741
|
+
"do",
|
|
3742
|
+
"else",
|
|
3743
|
+
"enum",
|
|
3744
|
+
"export",
|
|
3745
|
+
"extends",
|
|
3746
|
+
"false",
|
|
3747
|
+
"finally",
|
|
3748
|
+
"fn",
|
|
3749
|
+
"for",
|
|
3750
|
+
"from",
|
|
3751
|
+
"func",
|
|
3752
|
+
"function",
|
|
3753
|
+
"if",
|
|
3754
|
+
"implements",
|
|
3755
|
+
"import",
|
|
3756
|
+
"in",
|
|
3757
|
+
"interface",
|
|
3758
|
+
"let",
|
|
3759
|
+
"match",
|
|
3760
|
+
"mod",
|
|
3761
|
+
"namespace",
|
|
3762
|
+
"new",
|
|
3763
|
+
"nil",
|
|
3764
|
+
"none",
|
|
3765
|
+
"null",
|
|
3766
|
+
"package",
|
|
3767
|
+
"pass",
|
|
3768
|
+
"private",
|
|
3769
|
+
"protected",
|
|
3770
|
+
"pub",
|
|
3771
|
+
"public",
|
|
3772
|
+
"return",
|
|
3773
|
+
"self",
|
|
3774
|
+
"static",
|
|
3775
|
+
"struct",
|
|
3776
|
+
"super",
|
|
3777
|
+
"switch",
|
|
3778
|
+
"this",
|
|
3779
|
+
"throw",
|
|
3780
|
+
"throws",
|
|
3781
|
+
"trait",
|
|
3782
|
+
"true",
|
|
3783
|
+
"try",
|
|
3784
|
+
"type",
|
|
3785
|
+
"typeof",
|
|
3786
|
+
"undefined",
|
|
3787
|
+
"use",
|
|
3788
|
+
"var",
|
|
3789
|
+
"void",
|
|
3790
|
+
"while",
|
|
3791
|
+
"with",
|
|
3792
|
+
"yield"
|
|
3793
|
+
]);
|
|
3794
|
+
const identifierNavigationAttribute = "data-codiff-identifier";
|
|
3795
|
+
const definitionModifierAttribute = "data-codiff-definition-mode";
|
|
3796
|
+
const nonCodeScopePattern = /(^|\s)(comment|string)([.\s]|$)/;
|
|
3797
|
+
const nonCodeTokenColors = new Set([dunkel_default, licht_default].flatMap((theme) => theme.tokenColors.flatMap((token) => {
|
|
3798
|
+
const scopes = Array.isArray(token.scope) ? token.scope : [token.scope];
|
|
3799
|
+
const foreground = token.settings.foreground?.toLowerCase();
|
|
3800
|
+
return foreground && scopes.some((scope) => typeof scope === "string" && nonCodeScopePattern.test(scope)) ? [foreground] : [];
|
|
3801
|
+
})));
|
|
3802
|
+
const isNavigableIdentifierToken = (text) => {
|
|
3803
|
+
return exactIdentifierPattern.test(text) && !commonKeywords.has(text.toLowerCase());
|
|
3804
|
+
};
|
|
3805
|
+
const isNonCodeSyntaxToken = (element) => {
|
|
3806
|
+
const style = element?.getAttribute("style")?.toLowerCase() ?? "";
|
|
3807
|
+
return [...nonCodeTokenColors].some((color) => style.includes(color));
|
|
3808
|
+
};
|
|
3809
|
+
const clearIdentifierNavigationWrappers = (root) => {
|
|
3810
|
+
for (const wrapper of Array.from(root.querySelectorAll(`[${identifierNavigationAttribute}]`))) {
|
|
3811
|
+
const parent = wrapper.parentElement;
|
|
3812
|
+
wrapper.replaceWith(document.createTextNode(wrapper.textContent ?? ""));
|
|
3813
|
+
parent?.normalize();
|
|
3814
|
+
}
|
|
3815
|
+
};
|
|
3816
|
+
const wrapIdentifierNavigationText = (root) => {
|
|
3817
|
+
const textNodes = [];
|
|
3818
|
+
for (const line of Array.from(root.querySelectorAll("[data-line]"))) {
|
|
3819
|
+
const walker = document.createTreeWalker(line, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
|
|
3820
|
+
const parent = node.parentElement;
|
|
3821
|
+
return node.textContent && identifierContainedPattern.test(node.textContent) && !parent?.closest(`[${identifierNavigationAttribute}], mark.codiff-search-mark`) && !isNonCodeSyntaxToken(parent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
3822
|
+
} });
|
|
3823
|
+
let node = walker.nextNode();
|
|
3824
|
+
while (node) {
|
|
3825
|
+
textNodes.push(node);
|
|
3826
|
+
node = walker.nextNode();
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
for (const textNode of textNodes) {
|
|
3830
|
+
const text = textNode.textContent ?? "";
|
|
3831
|
+
const fragment = document.createDocumentFragment();
|
|
3832
|
+
let offset = 0;
|
|
3833
|
+
let wrapped = false;
|
|
3834
|
+
for (const match of text.matchAll(identifierPattern)) {
|
|
3835
|
+
const identifier = match[0];
|
|
3836
|
+
if (!isNavigableIdentifierToken(identifier)) continue;
|
|
3837
|
+
if (match.index > offset) fragment.append(document.createTextNode(text.slice(offset, match.index)));
|
|
3838
|
+
const wrapper = document.createElement("span");
|
|
3839
|
+
wrapper.setAttribute(identifierNavigationAttribute, "");
|
|
3840
|
+
wrapper.textContent = identifier;
|
|
3841
|
+
fragment.append(wrapper);
|
|
3842
|
+
offset = match.index + identifier.length;
|
|
3843
|
+
wrapped = true;
|
|
3844
|
+
}
|
|
3845
|
+
if (!wrapped) continue;
|
|
3846
|
+
if (offset < text.length) fragment.append(document.createTextNode(text.slice(offset)));
|
|
3847
|
+
textNode.replaceWith(fragment);
|
|
3848
|
+
}
|
|
3849
|
+
};
|
|
3850
|
+
const applyIdentifierNavigationState = (renderedItems, active) => {
|
|
3851
|
+
for (const { element } of renderedItems) {
|
|
3852
|
+
element.toggleAttribute(definitionModifierAttribute, active);
|
|
3853
|
+
const root = element.shadowRoot;
|
|
3854
|
+
if (!root) continue;
|
|
3855
|
+
clearIdentifierNavigationWrappers(root);
|
|
3856
|
+
if (active) wrapIdentifierNavigationText(root);
|
|
3857
|
+
}
|
|
3858
|
+
};
|
|
3859
|
+
const getIdentifierAtOffset = (text, offset) => {
|
|
3860
|
+
for (const match of text.matchAll(identifierPattern)) {
|
|
3861
|
+
const start = match.index;
|
|
3862
|
+
const end = start + match[0].length;
|
|
3863
|
+
if (offset >= start && offset < end) return {
|
|
3864
|
+
identifier: match[0],
|
|
3865
|
+
start
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3868
|
+
return null;
|
|
3869
|
+
};
|
|
3870
|
+
const getIdentifierFromPointerEvent = (event, lineElement) => {
|
|
3871
|
+
const caretRange = document.caretRangeFromPoint?.(event.clientX, event.clientY);
|
|
3872
|
+
if (caretRange && lineElement.contains(caretRange.startContainer)) {
|
|
3873
|
+
const prefix = document.createRange();
|
|
3874
|
+
prefix.selectNodeContents(lineElement);
|
|
3875
|
+
prefix.setEnd(caretRange.startContainer, caretRange.startOffset);
|
|
3876
|
+
const match = getIdentifierAtOffset(lineElement.textContent || "", prefix.toString().length);
|
|
3877
|
+
if (match) return match.identifier;
|
|
3878
|
+
}
|
|
3879
|
+
const target = event.target;
|
|
3880
|
+
if (target && lineElement.contains(target)) {
|
|
3881
|
+
const matches = [...(target.textContent || "").matchAll(identifierPattern)];
|
|
3882
|
+
if (matches.length === 1) return matches[0][0];
|
|
3883
|
+
}
|
|
3884
|
+
return null;
|
|
3885
|
+
};
|
|
3886
|
+
//#endregion
|
|
3436
3887
|
//#region lib/item-version.ts
|
|
3437
3888
|
const getItemVersion = (value) => {
|
|
3438
3889
|
let hash = 0;
|
|
@@ -3448,6 +3899,8 @@ const isNativeInputTarget = (target) => {
|
|
|
3448
3899
|
const candidate = target;
|
|
3449
3900
|
return candidate?.closest?.("input, select, textarea") != null || candidate?.isContentEditable === true;
|
|
3450
3901
|
};
|
|
3902
|
+
const isMacPlatform = (platform = navigator.platform) => platform.toLowerCase().includes("mac");
|
|
3903
|
+
const isPrimaryModifier = (event, platform = navigator.platform) => isMacPlatform(platform) ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
|
3451
3904
|
//#endregion
|
|
3452
3905
|
//#region lib/review-identity.ts
|
|
3453
3906
|
const getFileReviewIdentity = (file) => ({
|
|
@@ -3527,6 +3980,101 @@ const applySearchHighlights = (renderedItems, query, activeMatch) => {
|
|
|
3527
3980
|
}
|
|
3528
3981
|
};
|
|
3529
3982
|
//#endregion
|
|
3983
|
+
//#region lib/source.ts
|
|
3984
|
+
const rangeLabel = (source) => `${source.base}${source.symmetric ? "..." : ".."}${source.head}`;
|
|
3985
|
+
const getSourceKey = (source) => source.type === "commit" ? `commit:${source.ref}` : source.type === "branch-diff" ? `branch-diff:${source.ref}:${source.baseRef}:${source.headRef}` : source.type === "branch-working-tree" ? `branch-working-tree:${source.ref}:${source.baseRef}:${source.headRef}` : source.type === "branch" ? `branch:${source.ref}` : source.type === "range" ? `range:${rangeLabel(source)}` : source.type === "pull-request" ? `pull-request:${source.provider ?? ""}:${source.host ?? ""}:${source.projectPath ?? `${source.owner ?? ""}/${source.repo ?? ""}`}#${source.number ?? source.url}` : "working-tree";
|
|
3986
|
+
const getShortRef = (ref) => ref.slice(0, 7);
|
|
3987
|
+
const getSourceLabel = (source) => source.type === "commit" ? getShortRef(source.ref) : source.type === "branch" || source.type === "branch-diff" ? `Branch vs ${source.ref}` : source.type === "branch-working-tree" ? `Local + branch vs ${source.ref}` : source.type === "range" ? rangeLabel(source) : source.type === "pull-request" ? source.number ? `${source.provider === "gitlab" ? "MR" : "PR"} #${source.number}` : source.provider === "gitlab" ? "Merge request" : "Pull request" : "Uncommitted";
|
|
3988
|
+
//#endregion
|
|
3989
|
+
//#region app/components/DefinitionPopover.tsx
|
|
3990
|
+
function DefinitionPopover({ anchor, getDestination, identifier, onClose, onOpen, result }) {
|
|
3991
|
+
const popoverRef = useRef(null);
|
|
3992
|
+
useEffect(() => {
|
|
3993
|
+
const closeOnEscape = (event) => {
|
|
3994
|
+
if (event.key === "Escape") onClose();
|
|
3995
|
+
};
|
|
3996
|
+
const closeOutside = (event) => {
|
|
3997
|
+
const target = event.target;
|
|
3998
|
+
if (target && !popoverRef.current?.contains(target)) onClose();
|
|
3999
|
+
};
|
|
4000
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
4001
|
+
document.addEventListener("pointerdown", closeOutside);
|
|
4002
|
+
return () => {
|
|
4003
|
+
document.removeEventListener("keydown", closeOnEscape);
|
|
4004
|
+
document.removeEventListener("pointerdown", closeOutside);
|
|
4005
|
+
};
|
|
4006
|
+
}, [onClose]);
|
|
4007
|
+
const left = Math.min(anchor.x, Math.max(12, window.innerWidth - 432));
|
|
4008
|
+
const top = Math.min(anchor.y + 10, Math.max(12, window.innerHeight - 320));
|
|
4009
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
4010
|
+
"aria-label": `Definitions for ${identifier}`,
|
|
4011
|
+
className: "definition-popover",
|
|
4012
|
+
ref: popoverRef,
|
|
4013
|
+
role: "dialog",
|
|
4014
|
+
style: {
|
|
4015
|
+
left,
|
|
4016
|
+
top
|
|
4017
|
+
},
|
|
4018
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4019
|
+
className: "definition-popover-header",
|
|
4020
|
+
children: [/* @__PURE__ */ jsxs("span", { children: ["Definition of ", /* @__PURE__ */ jsx("code", { children: identifier })] }), /* @__PURE__ */ jsx("button", {
|
|
4021
|
+
"aria-label": "Close definition results",
|
|
4022
|
+
onClick: onClose,
|
|
4023
|
+
type: "button",
|
|
4024
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: 14 })
|
|
4025
|
+
})]
|
|
4026
|
+
}), !result ? /* @__PURE__ */ jsx("div", {
|
|
4027
|
+
className: "definition-popover-message",
|
|
4028
|
+
children: "Searching…"
|
|
4029
|
+
}) : result.status === "unavailable" ? /* @__PURE__ */ jsx("div", {
|
|
4030
|
+
className: "definition-popover-message",
|
|
4031
|
+
children: result.reason
|
|
4032
|
+
}) : result.candidates.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
4033
|
+
className: "definition-popover-message",
|
|
4034
|
+
children: "No likely definitions found."
|
|
4035
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
4036
|
+
className: "definition-popover-results",
|
|
4037
|
+
children: result.candidates.map((candidate) => {
|
|
4038
|
+
const destination = getDestination(candidate);
|
|
4039
|
+
const destinationLabel = destination === "diff" ? "Jump within diff" : destination === "editor" ? "Open in editor" : "Unavailable outside this historical diff";
|
|
4040
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
4041
|
+
className: "definition-popover-result",
|
|
4042
|
+
disabled: destination === "unavailable",
|
|
4043
|
+
onClick: () => onOpen(candidate),
|
|
4044
|
+
type: "button",
|
|
4045
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
4046
|
+
className: "definition-popover-location",
|
|
4047
|
+
children: [
|
|
4048
|
+
candidate.path,
|
|
4049
|
+
":",
|
|
4050
|
+
candidate.lineNumber,
|
|
4051
|
+
/* @__PURE__ */ jsxs("span", {
|
|
4052
|
+
className: "definition-popover-meta",
|
|
4053
|
+
children: [/* @__PURE__ */ jsx("span", { children: candidate.kind }), /* @__PURE__ */ jsx("span", {
|
|
4054
|
+
"aria-label": destinationLabel,
|
|
4055
|
+
className: "definition-popover-destination",
|
|
4056
|
+
role: "img",
|
|
4057
|
+
title: destinationLabel,
|
|
4058
|
+
children: destination === "diff" ? /* @__PURE__ */ jsx(CrosshairSimpleIcon, {
|
|
4059
|
+
"aria-hidden": "true",
|
|
4060
|
+
size: 13
|
|
4061
|
+
}) : destination === "editor" ? /* @__PURE__ */ jsx(ArrowSquareOutIcon, {
|
|
4062
|
+
"aria-hidden": "true",
|
|
4063
|
+
size: 13
|
|
4064
|
+
}) : /* @__PURE__ */ jsx(ProhibitIcon, {
|
|
4065
|
+
"aria-hidden": "true",
|
|
4066
|
+
size: 13
|
|
4067
|
+
})
|
|
4068
|
+
})]
|
|
4069
|
+
})
|
|
4070
|
+
]
|
|
4071
|
+
}), /* @__PURE__ */ jsx("code", { children: candidate.line })]
|
|
4072
|
+
}, `${candidate.path}:${candidate.lineNumber}`);
|
|
4073
|
+
})
|
|
4074
|
+
})]
|
|
4075
|
+
}), document.body);
|
|
4076
|
+
}
|
|
4077
|
+
//#endregion
|
|
3530
4078
|
//#region app/components/MarkdownDocumentEditor.tsx
|
|
3531
4079
|
const persistenceAdapter = { save: ({ content, document }) => window.codiff.saveMarkdownDocument({
|
|
3532
4080
|
baseVersion: document.version,
|
|
@@ -3643,12 +4191,6 @@ const RepositoryMarkdownEditor = forwardRef(function RepositoryMarkdownEditor({
|
|
|
3643
4191
|
});
|
|
3644
4192
|
});
|
|
3645
4193
|
//#endregion
|
|
3646
|
-
//#region lib/source.ts
|
|
3647
|
-
const rangeLabel = (source) => `${source.base}${source.symmetric ? "..." : ".."}${source.head}`;
|
|
3648
|
-
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";
|
|
3649
|
-
const getShortRef = (ref) => ref.slice(0, 7);
|
|
3650
|
-
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";
|
|
3651
|
-
//#endregion
|
|
3652
4194
|
//#region lib/narrative-walkthrough.ts
|
|
3653
4195
|
const walkthroughFileName = (path) => path.split("/").pop() ?? path;
|
|
3654
4196
|
const uniquePaths = (paths) => {
|
|
@@ -4071,7 +4613,8 @@ const chapterIcons = {
|
|
|
4071
4613
|
wrench: WrenchIcon
|
|
4072
4614
|
};
|
|
4073
4615
|
function ChapterIcon({ icon, size = 13 }) {
|
|
4074
|
-
|
|
4616
|
+
const Icon = chapterIcons[icon] ?? chapterIcons.path;
|
|
4617
|
+
return /* @__PURE__ */ jsx(Icon, { size });
|
|
4075
4618
|
}
|
|
4076
4619
|
function ImportancePill({ importance }) {
|
|
4077
4620
|
return /* @__PURE__ */ jsx("span", {
|
|
@@ -4356,13 +4899,14 @@ function WalkthroughProgress({ phase, responseLabelIndex, stageRevision }) {
|
|
|
4356
4899
|
}, [stageRevision]);
|
|
4357
4900
|
const elapsedSeconds = timerState.stageRevision === stageRevision ? timerState.elapsedSeconds : 0;
|
|
4358
4901
|
const showTimer = elapsedSeconds >= TIMER_THRESHOLD_SECONDS;
|
|
4902
|
+
const label = phase === "agent-generation" ? "Analyzing changes…" : phase === "response-received" ? walkthroughResponseLabels[Math.abs(responseLabelIndex) % walkthroughResponseLabels.length] : "Generating walkthrough…";
|
|
4359
4903
|
return /* @__PURE__ */ jsxs("span", {
|
|
4360
4904
|
"aria-live": "polite",
|
|
4361
4905
|
className: "walkthrough-progress",
|
|
4362
4906
|
role: "status",
|
|
4363
4907
|
children: [/* @__PURE__ */ jsx("span", {
|
|
4364
4908
|
className: "walkthrough-progress-label",
|
|
4365
|
-
children:
|
|
4909
|
+
children: label
|
|
4366
4910
|
}), /* @__PURE__ */ jsx("span", {
|
|
4367
4911
|
"aria-hidden": !showTimer,
|
|
4368
4912
|
className: `walkthrough-progress-timer${showTimer ? " visible" : ""}`,
|
|
@@ -5402,6 +5946,9 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5402
5946
|
flushDraft,
|
|
5403
5947
|
onCommentBlur
|
|
5404
5948
|
]);
|
|
5949
|
+
const handleAddComment = useCallback(() => {
|
|
5950
|
+
document.activeElement?.blur();
|
|
5951
|
+
}, []);
|
|
5405
5952
|
const handleFocus = useCallback(() => {
|
|
5406
5953
|
onCommentFocus(draftComment);
|
|
5407
5954
|
}, [draftComment, onCommentFocus]);
|
|
@@ -5417,13 +5964,21 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5417
5964
|
]);
|
|
5418
5965
|
const handleKeyDown = useCallback((event) => {
|
|
5419
5966
|
if (matchesShortcut(event, keymap, "submitComment")) {
|
|
5420
|
-
if (supportsReviewCommentActions
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5967
|
+
if (supportsReviewCommentActions) {
|
|
5968
|
+
if (commentCanSubmit) {
|
|
5969
|
+
event.preventDefault();
|
|
5970
|
+
event.stopPropagation();
|
|
5971
|
+
handleSubmitComment();
|
|
5972
|
+
}
|
|
5424
5973
|
return;
|
|
5425
5974
|
}
|
|
5426
|
-
|
|
5975
|
+
event.preventDefault();
|
|
5976
|
+
event.stopPropagation();
|
|
5977
|
+
handleAddComment();
|
|
5978
|
+
return;
|
|
5979
|
+
}
|
|
5980
|
+
if (matchesShortcut(event, keymap, "askAgent")) {
|
|
5981
|
+
if (canAskCodex) {
|
|
5427
5982
|
event.preventDefault();
|
|
5428
5983
|
event.stopPropagation();
|
|
5429
5984
|
handleAskCodex();
|
|
@@ -5441,6 +5996,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5441
5996
|
comment.id,
|
|
5442
5997
|
comment.isReadOnly,
|
|
5443
5998
|
draft,
|
|
5999
|
+
handleAddComment,
|
|
5444
6000
|
handleAskCodex,
|
|
5445
6001
|
handleSubmitComment,
|
|
5446
6002
|
keymap,
|
|
@@ -5496,7 +6052,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
|
|
|
5496
6052
|
className: "review-comment-action",
|
|
5497
6053
|
disabled: !canAskCodex,
|
|
5498
6054
|
onClick: handleAskCodex,
|
|
5499
|
-
title: canAskCodex ? `Ask ${agentLabel}` : `Write a note before asking ${agentLabel}`,
|
|
6055
|
+
title: canAskCodex ? `Ask ${agentLabel} (${getShortcutLabel(keymap, "askAgent")})` : `Write a note before asking ${agentLabel}`,
|
|
5500
6056
|
type: "button",
|
|
5501
6057
|
children: [/* @__PURE__ */ jsx("img", {
|
|
5502
6058
|
alt: "",
|
|
@@ -5658,7 +6214,7 @@ const groupReviewCommentsByThread = (comments) => {
|
|
|
5658
6214
|
};
|
|
5659
6215
|
const noopResolveThread = () => {};
|
|
5660
6216
|
const noopLayoutReady = () => {};
|
|
5661
|
-
function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
6217
|
+
function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusCommentRequest, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
5662
6218
|
const [resolveState, setResolveState] = useState({
|
|
5663
6219
|
error: null,
|
|
5664
6220
|
submitting: false,
|
|
@@ -5667,10 +6223,13 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5667
6223
|
const lastComment = comments.at(-1);
|
|
5668
6224
|
const threadId = lastComment?.threadId;
|
|
5669
6225
|
const threadResolved = comments.some((comment) => comment.isThreadResolved === true);
|
|
6226
|
+
const hasFocusedComment = focusCommentId != null && comments.some((comment) => comment.id === focusCommentId);
|
|
6227
|
+
const [resolutionOverride, setResolutionOverride] = useState(null);
|
|
6228
|
+
const effectiveThreadResolved = resolutionOverride?.base === threadResolved ? resolutionOverride.value : threadResolved;
|
|
6229
|
+
const resolving = resolveState.threadId === threadId && resolveState.submitting;
|
|
5670
6230
|
const canResolveThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.canResolveThread === true);
|
|
5671
|
-
const canReplyToThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.isReadOnly) && !comments.some((comment) => !comment.isReadOnly) && !comments.some((comment) => comment.canReplyThread === false) && !
|
|
6231
|
+
const canReplyToThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.isReadOnly) && !comments.some((comment) => !comment.isReadOnly) && !comments.some((comment) => comment.canReplyThread === false) && !effectiveThreadResolved && !resolving;
|
|
5672
6232
|
const hasThreadActions = canReplyToThread || canResolveThread;
|
|
5673
|
-
const resolving = resolveState.threadId === threadId && resolveState.submitting;
|
|
5674
6233
|
const resolveError = resolveState.threadId === threadId ? resolveState.error : null;
|
|
5675
6234
|
const handleReply = useCallback(() => {
|
|
5676
6235
|
if (!threadId || !lastComment) return;
|
|
@@ -5682,16 +6241,22 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5682
6241
|
]);
|
|
5683
6242
|
const handleResolve = useCallback(() => {
|
|
5684
6243
|
if (!threadId || resolving) return;
|
|
6244
|
+
const nextResolved = !effectiveThreadResolved;
|
|
6245
|
+
setResolutionOverride({
|
|
6246
|
+
base: threadResolved,
|
|
6247
|
+
value: nextResolved
|
|
6248
|
+
});
|
|
5685
6249
|
setResolveState({
|
|
5686
6250
|
error: null,
|
|
5687
6251
|
submitting: true,
|
|
5688
6252
|
threadId
|
|
5689
6253
|
});
|
|
5690
|
-
Promise.resolve(onResolveThread(threadId,
|
|
6254
|
+
Promise.resolve(onResolveThread(threadId, nextResolved)).then(() => setResolveState({
|
|
5691
6255
|
error: null,
|
|
5692
6256
|
submitting: false,
|
|
5693
6257
|
threadId
|
|
5694
6258
|
})).catch((error) => {
|
|
6259
|
+
setResolutionOverride(null);
|
|
5695
6260
|
setResolveState({
|
|
5696
6261
|
error: error instanceof Error ? error.message : String(error),
|
|
5697
6262
|
submitting: false,
|
|
@@ -5699,54 +6264,64 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
|
|
|
5699
6264
|
});
|
|
5700
6265
|
});
|
|
5701
6266
|
}, [
|
|
6267
|
+
effectiveThreadResolved,
|
|
5702
6268
|
onResolveThread,
|
|
5703
6269
|
resolving,
|
|
5704
6270
|
threadId,
|
|
5705
6271
|
threadResolved
|
|
5706
6272
|
]);
|
|
5707
|
-
|
|
6273
|
+
const threadContent = /* @__PURE__ */ jsxs(Fragment$1, { children: [comments.map((comment) => {
|
|
6274
|
+
const displayName = comment.author ? getReviewAuthorDisplayName(comment.author) : getGitIdentityDisplayName(identity);
|
|
6275
|
+
return /* @__PURE__ */ jsx(ReviewCommentEditor, {
|
|
6276
|
+
agentId,
|
|
6277
|
+
agentLabel,
|
|
6278
|
+
comment,
|
|
6279
|
+
displayName,
|
|
6280
|
+
focusCommentId,
|
|
6281
|
+
focusEditorRef,
|
|
6282
|
+
identity,
|
|
6283
|
+
keymap,
|
|
6284
|
+
onAskCodex,
|
|
6285
|
+
onCommentBlur,
|
|
6286
|
+
onCommentDraftChange,
|
|
6287
|
+
onCommentFocus,
|
|
6288
|
+
onDeleteComment,
|
|
6289
|
+
onSaveCommentEdit,
|
|
6290
|
+
onSubmitComment,
|
|
6291
|
+
onUpdateComment,
|
|
6292
|
+
supportsReviewCommentActions
|
|
6293
|
+
}, comment.id);
|
|
6294
|
+
}), hasThreadActions ? /* @__PURE__ */ jsxs("div", {
|
|
6295
|
+
className: "review-comment-thread-footer",
|
|
6296
|
+
children: [resolveError ? /* @__PURE__ */ jsx("span", {
|
|
6297
|
+
className: "review-comment-thread-error",
|
|
6298
|
+
children: resolveError
|
|
6299
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
6300
|
+
className: "review-comment-thread-actions",
|
|
6301
|
+
children: [canReplyToThread ? /* @__PURE__ */ jsx("button", {
|
|
6302
|
+
className: "review-comment-action",
|
|
6303
|
+
onClick: handleReply,
|
|
6304
|
+
type: "button",
|
|
6305
|
+
children: "Reply"
|
|
6306
|
+
}) : null, canResolveThread ? /* @__PURE__ */ jsx("button", {
|
|
6307
|
+
className: "review-comment-action",
|
|
6308
|
+
disabled: resolving,
|
|
6309
|
+
onClick: handleResolve,
|
|
6310
|
+
type: "button",
|
|
6311
|
+
children: resolving ? "Saving" : effectiveThreadResolved ? "Reopen" : "Resolve"
|
|
6312
|
+
}) : null]
|
|
6313
|
+
})]
|
|
6314
|
+
}) : null] });
|
|
6315
|
+
return effectiveThreadResolved ? /* @__PURE__ */ jsx(ResolvedThreadDisclosure, {
|
|
6316
|
+
commentCount: comments.length,
|
|
6317
|
+
focused: hasFocusedComment,
|
|
6318
|
+
focusRequest: focusCommentRequest,
|
|
6319
|
+
initialExpanded: Boolean(resolveError),
|
|
6320
|
+
saving: resolving,
|
|
6321
|
+
children: threadContent
|
|
6322
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
5708
6323
|
className: "review-comment-thread-group",
|
|
5709
|
-
children:
|
|
5710
|
-
return /* @__PURE__ */ jsx(ReviewCommentEditor, {
|
|
5711
|
-
agentId,
|
|
5712
|
-
agentLabel,
|
|
5713
|
-
comment,
|
|
5714
|
-
displayName: comment.author ? getReviewAuthorDisplayName(comment.author) : getGitIdentityDisplayName(identity),
|
|
5715
|
-
focusCommentId,
|
|
5716
|
-
focusEditorRef,
|
|
5717
|
-
identity,
|
|
5718
|
-
keymap,
|
|
5719
|
-
onAskCodex,
|
|
5720
|
-
onCommentBlur,
|
|
5721
|
-
onCommentDraftChange,
|
|
5722
|
-
onCommentFocus,
|
|
5723
|
-
onDeleteComment,
|
|
5724
|
-
onSaveCommentEdit,
|
|
5725
|
-
onSubmitComment,
|
|
5726
|
-
onUpdateComment,
|
|
5727
|
-
supportsReviewCommentActions
|
|
5728
|
-
}, comment.id);
|
|
5729
|
-
}), hasThreadActions ? /* @__PURE__ */ jsxs("div", {
|
|
5730
|
-
className: "review-comment-thread-footer",
|
|
5731
|
-
children: [resolveError ? /* @__PURE__ */ jsx("span", {
|
|
5732
|
-
className: "review-comment-thread-error",
|
|
5733
|
-
children: resolveError
|
|
5734
|
-
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
5735
|
-
className: "review-comment-thread-actions",
|
|
5736
|
-
children: [canReplyToThread ? /* @__PURE__ */ jsx("button", {
|
|
5737
|
-
className: "review-comment-action",
|
|
5738
|
-
onClick: handleReply,
|
|
5739
|
-
type: "button",
|
|
5740
|
-
children: "Reply"
|
|
5741
|
-
}) : null, canResolveThread ? /* @__PURE__ */ jsx("button", {
|
|
5742
|
-
className: "review-comment-action",
|
|
5743
|
-
disabled: resolving,
|
|
5744
|
-
onClick: handleResolve,
|
|
5745
|
-
type: "button",
|
|
5746
|
-
children: resolving ? "Saving" : threadResolved ? "Reopen" : "Resolve"
|
|
5747
|
-
}) : null]
|
|
5748
|
-
})]
|
|
5749
|
-
}) : null]
|
|
6324
|
+
children: threadContent
|
|
5750
6325
|
});
|
|
5751
6326
|
}
|
|
5752
6327
|
function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusCommentId, focusCommentRequest, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onHeightChange, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
|
|
@@ -5779,14 +6354,16 @@ function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusComm
|
|
|
5779
6354
|
return () => observer.disconnect();
|
|
5780
6355
|
}, [annotationComments.length, onHeightChange]);
|
|
5781
6356
|
if (annotationComments.length === 0) return null;
|
|
6357
|
+
const commentGroups = groupReviewCommentsByThread(annotationComments);
|
|
5782
6358
|
return /* @__PURE__ */ jsx("div", {
|
|
5783
6359
|
className: "review-comment-thread",
|
|
5784
6360
|
ref: threadRef,
|
|
5785
|
-
children:
|
|
6361
|
+
children: commentGroups.map((group) => /* @__PURE__ */ jsx(ReviewCommentThreadGroup, {
|
|
5786
6362
|
agentId,
|
|
5787
6363
|
agentLabel,
|
|
5788
6364
|
comments: group.comments,
|
|
5789
6365
|
focusCommentId,
|
|
6366
|
+
focusCommentRequest,
|
|
5790
6367
|
focusEditorRef: setFocusEditorRef,
|
|
5791
6368
|
identity,
|
|
5792
6369
|
keymap,
|
|
@@ -5988,7 +6565,7 @@ const getHunkSelectionRange = (hunk) => {
|
|
|
5988
6565
|
} : null;
|
|
5989
6566
|
};
|
|
5990
6567
|
const isSameSelection = (a, b) => a.start === b.start && a.end === b.end && a.side === b.side;
|
|
5991
|
-
function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedGenerated = emptyExpandedGenerated, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, supportsReviewCommentActions, theme = "system", viewed, walkthroughNotes, wordWrap }) {
|
|
6568
|
+
function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedGenerated = emptyExpandedGenerated, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onFindDefinitions, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenDefinition, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, supportsReviewCommentActions, theme = "system", viewed, walkthroughNotes, wordWrap }) {
|
|
5992
6569
|
const codeViewRef = useRef(null);
|
|
5993
6570
|
const markdownEditorRefs = useRef(/* @__PURE__ */ new Map());
|
|
5994
6571
|
const refreshingMarkdownSectionsRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -5996,6 +6573,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
5996
6573
|
const handledScrollRequestRef = useRef(null);
|
|
5997
6574
|
const handledHunkNavRef = useRef(hunkNavigation?.request ?? null);
|
|
5998
6575
|
const emptyCommentDeleteTimersRef = useRef(/* @__PURE__ */ new Map());
|
|
6576
|
+
const definitionHighlightFrameRef = useRef(null);
|
|
5999
6577
|
const highlightFrameRef = useRef(null);
|
|
6000
6578
|
const ignoreNextLineSelectionEndRef = useRef(false);
|
|
6001
6579
|
const navigatedSelectionRef = useRef(null);
|
|
@@ -6009,6 +6587,15 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6009
6587
|
const [imagePreviewLayoutPassBySection, setImagePreviewLayoutPassBySection] = useState({});
|
|
6010
6588
|
const [commentLayoutPassByItem, setCommentLayoutPassByItem] = useState({});
|
|
6011
6589
|
const [selectedLines, setSelectedLines] = useState(null);
|
|
6590
|
+
const [definitionLookup, setDefinitionLookup] = useState(null);
|
|
6591
|
+
const definitionLookupRequestRef = useRef(0);
|
|
6592
|
+
const definitionModifierActiveRef = useRef(false);
|
|
6593
|
+
const sourceKey = getSourceKey(source);
|
|
6594
|
+
const [definitionLookupSourceKey, setDefinitionLookupSourceKey] = useState(sourceKey);
|
|
6595
|
+
if (definitionLookupSourceKey !== sourceKey) {
|
|
6596
|
+
setDefinitionLookupSourceKey(sourceKey);
|
|
6597
|
+
setDefinitionLookup(null);
|
|
6598
|
+
}
|
|
6012
6599
|
const selectedLinesRef = useRef(null);
|
|
6013
6600
|
const commitMessageMetadata = source.type === "commit" ? commitMetadata : null;
|
|
6014
6601
|
const shouldShowCommitMessage = commitMessageMetadata != null;
|
|
@@ -6058,19 +6645,22 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6058
6645
|
[itemId]: (current[itemId] ?? 0) + 1
|
|
6059
6646
|
}));
|
|
6060
6647
|
}, []);
|
|
6061
|
-
const { firstItemByBlockId, firstItemByPath, itemBlockId, itemMetadata, items, searchTargetsByBaseItemId } = useMemo(() => {
|
|
6648
|
+
const { commentTargetById, firstItemByBlockId, firstItemByPath, itemBlockId, itemMetadata, items, searchTargetsByBaseItemId, selectedHeaderItemIds } = useMemo(() => {
|
|
6062
6649
|
const nextItems = [];
|
|
6650
|
+
const nextCommentTargetById = /* @__PURE__ */ new Map();
|
|
6063
6651
|
const nextFirstItemByBlockId = /* @__PURE__ */ new Map();
|
|
6064
6652
|
const nextFirstItemByPath = /* @__PURE__ */ new Map();
|
|
6065
6653
|
const nextItemBlockId = /* @__PURE__ */ new Map();
|
|
6066
6654
|
const nextItemMetadata = /* @__PURE__ */ new Map();
|
|
6067
6655
|
const nextSearchTargetsByBaseItemId = /* @__PURE__ */ new Map();
|
|
6656
|
+
const nextSelectedHeaderItemIds = /* @__PURE__ */ new Set();
|
|
6068
6657
|
const fontLayoutKey = `line-height:${diffLineHeight}`;
|
|
6069
6658
|
for (const block of reviewBlocks) {
|
|
6070
6659
|
if (block.header) {
|
|
6071
6660
|
const headerId = getBlockHeaderItemId(block);
|
|
6072
6661
|
nextFirstItemByBlockId.set(block.id, nextFirstItemByBlockId.get(block.id) ?? headerId);
|
|
6073
6662
|
nextItemBlockId.set(headerId, block.id);
|
|
6663
|
+
if ((block.headerSelected ?? block.selected) === true) nextSelectedHeaderItemIds.add(headerId);
|
|
6074
6664
|
nextItems.push({
|
|
6075
6665
|
annotations: [{
|
|
6076
6666
|
lineNumber: 1,
|
|
@@ -6088,7 +6678,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6088
6678
|
},
|
|
6089
6679
|
id: headerId,
|
|
6090
6680
|
type: "file",
|
|
6091
|
-
version: getItemVersion(`${block.id}:walkthrough-header
|
|
6681
|
+
version: getItemVersion(`${block.id}:walkthrough-header`)
|
|
6092
6682
|
});
|
|
6093
6683
|
}
|
|
6094
6684
|
if (!block.file) continue;
|
|
@@ -6126,6 +6716,11 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6126
6716
|
const blockSectionComments = blockCommentsBySection.get(section.id) ?? [];
|
|
6127
6717
|
const sectionComments = dedupeReviewComments([...visibleGlobalComments, ...blockSectionComments]);
|
|
6128
6718
|
for (const comment of sectionComments) {
|
|
6719
|
+
if (!nextCommentTargetById.has(comment.id)) nextCommentTargetById.set(comment.id, isLineReviewComment(comment) ? {
|
|
6720
|
+
id,
|
|
6721
|
+
lineNumber: comment.lineNumber,
|
|
6722
|
+
side: comment.side
|
|
6723
|
+
} : { id });
|
|
6129
6724
|
const key = getCommentKey(comment);
|
|
6130
6725
|
const existing = annotationMap.get(key);
|
|
6131
6726
|
if (existing && existing.metadata.type === "review-comment") annotationMap.set(key, {
|
|
@@ -6237,12 +6832,14 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6237
6832
|
}
|
|
6238
6833
|
}
|
|
6239
6834
|
return {
|
|
6835
|
+
commentTargetById: nextCommentTargetById,
|
|
6240
6836
|
firstItemByBlockId: nextFirstItemByBlockId,
|
|
6241
6837
|
firstItemByPath: nextFirstItemByPath,
|
|
6242
6838
|
itemBlockId: nextItemBlockId,
|
|
6243
6839
|
itemMetadata: nextItemMetadata,
|
|
6244
6840
|
items: nextItems,
|
|
6245
|
-
searchTargetsByBaseItemId: nextSearchTargetsByBaseItemId
|
|
6841
|
+
searchTargetsByBaseItemId: nextSearchTargetsByBaseItemId,
|
|
6842
|
+
selectedHeaderItemIds: nextSelectedHeaderItemIds
|
|
6246
6843
|
};
|
|
6247
6844
|
}, [
|
|
6248
6845
|
collapsed,
|
|
@@ -6295,6 +6892,18 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6295
6892
|
itemMetadata,
|
|
6296
6893
|
searchTargetsByBaseItemId
|
|
6297
6894
|
]);
|
|
6895
|
+
const getDefinitionDiffTarget = useCallback((candidate) => {
|
|
6896
|
+
for (const item of items) {
|
|
6897
|
+
if (item.type !== "diff") continue;
|
|
6898
|
+
const metadata = itemMetadata.get(item.id);
|
|
6899
|
+
const path = candidate.side === "deletions" ? metadata?.file.oldPath ?? metadata?.file.path : metadata?.file.path;
|
|
6900
|
+
if (metadata && path === candidate.path && lineIsVisibleInFileDiff(item.fileDiff, candidate.side, candidate.lineNumber)) return {
|
|
6901
|
+
item,
|
|
6902
|
+
metadata
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
return null;
|
|
6906
|
+
}, [itemMetadata, items]);
|
|
6298
6907
|
const setCodeViewSelectedLines = useCallback((selection) => {
|
|
6299
6908
|
if (selection == null || navigatedSelectionRef.current == null || selection.id !== navigatedSelectionRef.current.id || !isSameSelection(selection.range, navigatedSelectionRef.current.range)) navigatedSelectionRef.current = null;
|
|
6300
6909
|
setSelectedLines(selection);
|
|
@@ -6408,6 +7017,50 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6408
7017
|
createCommentForRange(range, context);
|
|
6409
7018
|
},
|
|
6410
7019
|
onLineClick: (line, context) => {
|
|
7020
|
+
if (isPrimaryModifier(line.event) && onFindDefinitions) {
|
|
7021
|
+
line.event.preventDefault();
|
|
7022
|
+
line.event.stopPropagation();
|
|
7023
|
+
const lineElement = "lineElement" in line ? line.lineElement : null;
|
|
7024
|
+
const identifier = lineElement ? getIdentifierFromPointerEvent(line.event, lineElement) : null;
|
|
7025
|
+
const meta = itemMetadata.get(context.item.id);
|
|
7026
|
+
const side = "annotationSide" in line ? line.annotationSide : null;
|
|
7027
|
+
if (!identifier || !meta || !side) {
|
|
7028
|
+
setDefinitionLookup(null);
|
|
7029
|
+
return;
|
|
7030
|
+
}
|
|
7031
|
+
const requestId = ++definitionLookupRequestRef.current;
|
|
7032
|
+
setDefinitionLookup({
|
|
7033
|
+
anchor: {
|
|
7034
|
+
x: line.event.clientX,
|
|
7035
|
+
y: line.event.clientY
|
|
7036
|
+
},
|
|
7037
|
+
identifier,
|
|
7038
|
+
result: null,
|
|
7039
|
+
sourceKey
|
|
7040
|
+
});
|
|
7041
|
+
onFindDefinitions({
|
|
7042
|
+
identifier,
|
|
7043
|
+
kind: meta.section.kind,
|
|
7044
|
+
lineNumber: line.lineNumber,
|
|
7045
|
+
path: side === "deletions" ? meta.file.oldPath ?? meta.file.path : meta.file.path,
|
|
7046
|
+
side,
|
|
7047
|
+
source
|
|
7048
|
+
}).then((result) => {
|
|
7049
|
+
if (definitionLookupRequestRef.current === requestId) setDefinitionLookup((current) => current?.identifier === identifier && current.sourceKey === sourceKey ? {
|
|
7050
|
+
...current,
|
|
7051
|
+
result
|
|
7052
|
+
} : current);
|
|
7053
|
+
}).catch(() => {
|
|
7054
|
+
if (definitionLookupRequestRef.current === requestId) setDefinitionLookup((current) => current?.identifier === identifier && current.sourceKey === sourceKey ? {
|
|
7055
|
+
...current,
|
|
7056
|
+
result: {
|
|
7057
|
+
reason: "Definition search is unavailable for this repository.",
|
|
7058
|
+
status: "unavailable"
|
|
7059
|
+
}
|
|
7060
|
+
} : current);
|
|
7061
|
+
});
|
|
7062
|
+
return;
|
|
7063
|
+
}
|
|
6411
7064
|
if (isReadOnly) return;
|
|
6412
7065
|
if (isInteractiveReviewEvent(line.event)) return;
|
|
6413
7066
|
const meta = itemMetadata.get(context.item.id);
|
|
@@ -6440,11 +7093,14 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6440
7093
|
const metadata = itemMetadata.get(context.item.id);
|
|
6441
7094
|
const isWalkthroughHeaderItem = context.item.id.endsWith(":walkthrough-header");
|
|
6442
7095
|
node.classList.toggle("codiff-walkthrough-header-item", isWalkthroughHeaderItem);
|
|
6443
|
-
node.classList.toggle("codiff-selected-item", metadata?.isSelected === true);
|
|
7096
|
+
node.classList.toggle("codiff-selected-item", metadata?.isSelected === true || selectedHeaderItemIds.has(context.item.id));
|
|
6444
7097
|
node.classList.toggle("codiff-markdown-preview-item", metadata?.isMarkdownPreview === true);
|
|
6445
7098
|
node.classList.toggle("codiff-image-preview-item", context.item.type === "file" && Boolean(metadata && canRenderImagePreview(metadata.file.path, metadata.section)));
|
|
6446
7099
|
node.classList.toggle("codiff-loadable-summary-item", metadata != null && shouldLoadDiffSectionContents(metadata.section));
|
|
6447
7100
|
node.classList.toggle("codiff-loading-summary-item", Boolean(metadata && loadingSectionIds.has(metadata.section.id)));
|
|
7101
|
+
if (definitionModifierActiveRef.current) window.requestAnimationFrame(() => {
|
|
7102
|
+
if (definitionModifierActiveRef.current && node.isConnected) applyIdentifierNavigationState([{ element: node }], true);
|
|
7103
|
+
});
|
|
6448
7104
|
},
|
|
6449
7105
|
overflow: wordWrap ? "wrap" : "scroll",
|
|
6450
7106
|
stickyHeaders: true,
|
|
@@ -6465,7 +7121,11 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6465
7121
|
loadDiffFiles,
|
|
6466
7122
|
loadingSectionIds,
|
|
6467
7123
|
onCreateComment,
|
|
7124
|
+
onFindDefinitions,
|
|
6468
7125
|
onLoadSection,
|
|
7126
|
+
selectedHeaderItemIds,
|
|
7127
|
+
source,
|
|
7128
|
+
sourceKey,
|
|
6469
7129
|
theme,
|
|
6470
7130
|
wordWrap
|
|
6471
7131
|
]);
|
|
@@ -6557,11 +7217,19 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6557
7217
|
poolSize: Math.min(3, Math.max(1, navigator.hardwareConcurrency || 3)),
|
|
6558
7218
|
workerFactory: () => new Worker(new URL("@pierre/diffs/worker/worker.js", import.meta.url), { type: "module" })
|
|
6559
7219
|
}), []);
|
|
6560
|
-
const
|
|
7220
|
+
const requestScrollTargetIntoView = useCallback((itemId, behavior = "instant", commentTarget) => {
|
|
6561
7221
|
const handle = codeViewRef.current;
|
|
6562
7222
|
const viewer = handle?.getInstance();
|
|
6563
7223
|
if (!handle || !viewer || viewer.getTopForItem(itemId) == null) return false;
|
|
6564
|
-
handle.scrollTo({
|
|
7224
|
+
handle.scrollTo(commentTarget?.lineNumber != null && commentTarget.side ? {
|
|
7225
|
+
align: "center",
|
|
7226
|
+
behavior: getEffectiveScrollBehavior(behavior),
|
|
7227
|
+
id: itemId,
|
|
7228
|
+
lineNumber: commentTarget.lineNumber,
|
|
7229
|
+
offset: 11,
|
|
7230
|
+
side: commentTarget.side,
|
|
7231
|
+
type: "line"
|
|
7232
|
+
} : {
|
|
6565
7233
|
behavior: getEffectiveScrollBehavior(behavior),
|
|
6566
7234
|
id: itemId,
|
|
6567
7235
|
offset: 11,
|
|
@@ -6572,14 +7240,15 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6572
7240
|
useLayoutEffect(() => {
|
|
6573
7241
|
if (!scrollTarget || handledScrollRequestRef.current === scrollTarget.request) return;
|
|
6574
7242
|
const behavior = scrollTarget.behavior ?? "instant";
|
|
6575
|
-
const
|
|
7243
|
+
const commentTarget = scrollTarget.commentId ? commentTargetById.get(scrollTarget.commentId) : void 0;
|
|
7244
|
+
const itemId = commentTarget?.id ?? (scrollTarget.blockId ? firstItemByBlockId.get(scrollTarget.blockId) : scrollTarget.path ? firstItemByPath.get(scrollTarget.path) : null);
|
|
6576
7245
|
if (!itemId) return;
|
|
6577
7246
|
let frame = null;
|
|
6578
7247
|
let attempts = 0;
|
|
6579
7248
|
let canceled = false;
|
|
6580
7249
|
const tryScroll = () => {
|
|
6581
7250
|
if (canceled || handledScrollRequestRef.current === scrollTarget.request) return;
|
|
6582
|
-
if (
|
|
7251
|
+
if (requestScrollTargetIntoView(itemId, behavior, commentTarget)) {
|
|
6583
7252
|
handledScrollRequestRef.current = scrollTarget.request;
|
|
6584
7253
|
return;
|
|
6585
7254
|
}
|
|
@@ -6594,9 +7263,10 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6594
7263
|
if (frame != null) window.cancelAnimationFrame(frame);
|
|
6595
7264
|
};
|
|
6596
7265
|
}, [
|
|
7266
|
+
commentTargetById,
|
|
6597
7267
|
firstItemByBlockId,
|
|
6598
7268
|
firstItemByPath,
|
|
6599
|
-
|
|
7269
|
+
requestScrollTargetIntoView,
|
|
6600
7270
|
scrollTarget
|
|
6601
7271
|
]);
|
|
6602
7272
|
useEffect(() => {
|
|
@@ -6758,6 +7428,22 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6758
7428
|
applySearchHighlights(viewer.getRenderedItems(), searchQuery, resolvedActiveSearchMatch);
|
|
6759
7429
|
});
|
|
6760
7430
|
}, [resolvedActiveSearchMatch, searchQuery]);
|
|
7431
|
+
const updateRenderedIdentifierNavigation = useCallback((active, viewer) => {
|
|
7432
|
+
const nextViewer = viewer ?? codeViewRef.current?.getInstance();
|
|
7433
|
+
if (!nextViewer) return;
|
|
7434
|
+
if (definitionHighlightFrameRef.current != null) {
|
|
7435
|
+
window.cancelAnimationFrame(definitionHighlightFrameRef.current);
|
|
7436
|
+
definitionHighlightFrameRef.current = null;
|
|
7437
|
+
}
|
|
7438
|
+
if (!active) {
|
|
7439
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems(), false);
|
|
7440
|
+
return;
|
|
7441
|
+
}
|
|
7442
|
+
definitionHighlightFrameRef.current = window.requestAnimationFrame(() => {
|
|
7443
|
+
definitionHighlightFrameRef.current = null;
|
|
7444
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems(), true);
|
|
7445
|
+
});
|
|
7446
|
+
}, []);
|
|
6761
7447
|
const scheduleStickyHeaderStateUpdate = useCallback((viewer) => {
|
|
6762
7448
|
const nextViewer = viewer ?? codeViewRef.current?.getInstance();
|
|
6763
7449
|
if (!nextViewer) return;
|
|
@@ -6771,6 +7457,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6771
7457
|
for (const timer of deferredTimersRef.current) window.clearTimeout(timer);
|
|
6772
7458
|
deferredTimersRef.current.clear();
|
|
6773
7459
|
emptyCommentDeleteTimersRef.current.clear();
|
|
7460
|
+
if (definitionHighlightFrameRef.current != null) window.cancelAnimationFrame(definitionHighlightFrameRef.current);
|
|
6774
7461
|
if (highlightFrameRef.current != null) window.cancelAnimationFrame(highlightFrameRef.current);
|
|
6775
7462
|
if (stickyHeaderFrameRef.current != null) window.cancelAnimationFrame(stickyHeaderFrameRef.current);
|
|
6776
7463
|
}, []);
|
|
@@ -6782,6 +7469,38 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6782
7469
|
scheduleSearchHighlights,
|
|
6783
7470
|
scheduleStickyHeaderStateUpdate
|
|
6784
7471
|
]);
|
|
7472
|
+
useEffect(() => {
|
|
7473
|
+
if (!onFindDefinitions) return;
|
|
7474
|
+
const updateDefinitionModifierState = (active) => {
|
|
7475
|
+
if (definitionModifierActiveRef.current === active) return;
|
|
7476
|
+
definitionModifierActiveRef.current = active;
|
|
7477
|
+
updateRenderedIdentifierNavigation(active);
|
|
7478
|
+
};
|
|
7479
|
+
const handleModifierChange = (event) => {
|
|
7480
|
+
updateDefinitionModifierState(isPrimaryModifier(event));
|
|
7481
|
+
};
|
|
7482
|
+
const handleModifierPointer = (event) => {
|
|
7483
|
+
updateDefinitionModifierState(isPrimaryModifier(event));
|
|
7484
|
+
};
|
|
7485
|
+
const clearDefinitionModifierState = () => updateDefinitionModifierState(false);
|
|
7486
|
+
window.addEventListener("keydown", handleModifierChange);
|
|
7487
|
+
window.addEventListener("keyup", handleModifierChange);
|
|
7488
|
+
window.addEventListener("pointerdown", handleModifierPointer);
|
|
7489
|
+
window.addEventListener("pointermove", handleModifierPointer);
|
|
7490
|
+
window.addEventListener("blur", clearDefinitionModifierState);
|
|
7491
|
+
return () => {
|
|
7492
|
+
clearDefinitionModifierState();
|
|
7493
|
+
window.removeEventListener("keydown", handleModifierChange);
|
|
7494
|
+
window.removeEventListener("keyup", handleModifierChange);
|
|
7495
|
+
window.removeEventListener("pointerdown", handleModifierPointer);
|
|
7496
|
+
window.removeEventListener("pointermove", handleModifierPointer);
|
|
7497
|
+
window.removeEventListener("blur", clearDefinitionModifierState);
|
|
7498
|
+
};
|
|
7499
|
+
}, [onFindDefinitions, updateRenderedIdentifierNavigation]);
|
|
7500
|
+
const invalidateDefinitionLookup = useCallback(() => {
|
|
7501
|
+
definitionLookupRequestRef.current++;
|
|
7502
|
+
}, []);
|
|
7503
|
+
useEffect(() => invalidateDefinitionLookup, [invalidateDefinitionLookup]);
|
|
6785
7504
|
useEffect(() => {
|
|
6786
7505
|
const handle = codeViewRef.current;
|
|
6787
7506
|
const viewer = handle?.getInstance();
|
|
@@ -6963,15 +7682,17 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6963
7682
|
}
|
|
6964
7683
|
scheduleSearchHighlights();
|
|
6965
7684
|
scheduleStickyHeaderStateUpdate(viewer);
|
|
7685
|
+
if (definitionModifierActiveRef.current) updateRenderedIdentifierNavigation(true, viewer);
|
|
6966
7686
|
}, [
|
|
6967
7687
|
itemBlockId,
|
|
6968
7688
|
items,
|
|
6969
7689
|
onActiveBlockChange,
|
|
6970
7690
|
onSelectPathFromScroll,
|
|
6971
7691
|
scheduleSearchHighlights,
|
|
6972
|
-
scheduleStickyHeaderStateUpdate
|
|
7692
|
+
scheduleStickyHeaderStateUpdate,
|
|
7693
|
+
updateRenderedIdentifierNavigation
|
|
6973
7694
|
]);
|
|
6974
|
-
return disableWorkerPool ? /* @__PURE__ */ jsx(CodeView, {
|
|
7695
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [disableWorkerPool ? /* @__PURE__ */ jsx(CodeView, {
|
|
6975
7696
|
className: "code-view",
|
|
6976
7697
|
disableWorkerPool,
|
|
6977
7698
|
items: codeViewItems,
|
|
@@ -6999,11 +7720,77 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6999
7720
|
renderCustomHeader,
|
|
7000
7721
|
selectedLines: isReadOnly ? null : selectedLines
|
|
7001
7722
|
})
|
|
7723
|
+
}), definitionLookup?.sourceKey === sourceKey && onOpenDefinition ? /* @__PURE__ */ jsx(DefinitionPopover, {
|
|
7724
|
+
anchor: definitionLookup.anchor,
|
|
7725
|
+
getDestination: (candidate) => getDefinitionDiffTarget(candidate) ? "diff" : candidate.canOpenInEditor ? "editor" : "unavailable",
|
|
7726
|
+
identifier: definitionLookup.identifier,
|
|
7727
|
+
onClose: () => {
|
|
7728
|
+
invalidateDefinitionLookup();
|
|
7729
|
+
setDefinitionLookup(null);
|
|
7730
|
+
},
|
|
7731
|
+
onOpen: (candidate) => {
|
|
7732
|
+
const target = getDefinitionDiffTarget(candidate);
|
|
7733
|
+
if (target) {
|
|
7734
|
+
if (target.metadata.isCollapsed) onToggleCollapsed(target.metadata.file, true, target.metadata.reviewIdentity.key);
|
|
7735
|
+
const scrollToDefinition = () => codeViewRef.current?.scrollTo({
|
|
7736
|
+
align: "center",
|
|
7737
|
+
behavior: "smooth-auto",
|
|
7738
|
+
id: target.item.id,
|
|
7739
|
+
lineNumber: candidate.lineNumber,
|
|
7740
|
+
offset: 11,
|
|
7741
|
+
side: candidate.side,
|
|
7742
|
+
type: "line"
|
|
7743
|
+
});
|
|
7744
|
+
if (target.metadata.isCollapsed) window.requestAnimationFrame(() => window.requestAnimationFrame(scrollToDefinition));
|
|
7745
|
+
else scrollToDefinition();
|
|
7746
|
+
} else if (candidate.canOpenInEditor) onOpenDefinition(candidate);
|
|
7747
|
+
invalidateDefinitionLookup();
|
|
7748
|
+
setDefinitionLookup(null);
|
|
7749
|
+
},
|
|
7750
|
+
result: definitionLookup.result
|
|
7751
|
+
}) : null] });
|
|
7752
|
+
}
|
|
7753
|
+
//#endregion
|
|
7754
|
+
//#region app/components/ReviewModeControl.tsx
|
|
7755
|
+
function ReviewModeControl({ mode, modes, onModeChange }) {
|
|
7756
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7757
|
+
"aria-label": "Review mode",
|
|
7758
|
+
className: "review-mode-control",
|
|
7759
|
+
role: "tablist",
|
|
7760
|
+
children: modes.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
7761
|
+
"aria-label": item.ariaLabel,
|
|
7762
|
+
"aria-selected": mode === item.value,
|
|
7763
|
+
onClick: () => onModeChange(item.value),
|
|
7764
|
+
role: "tab",
|
|
7765
|
+
title: item.title,
|
|
7766
|
+
type: "button",
|
|
7767
|
+
children: [
|
|
7768
|
+
item.icon,
|
|
7769
|
+
/* @__PURE__ */ jsx("span", {
|
|
7770
|
+
className: "review-mode-label",
|
|
7771
|
+
children: item.label
|
|
7772
|
+
}),
|
|
7773
|
+
item.indicator
|
|
7774
|
+
]
|
|
7775
|
+
}, item.value))
|
|
7002
7776
|
});
|
|
7003
7777
|
}
|
|
7004
7778
|
//#endregion
|
|
7005
7779
|
//#region app/components/ReviewTopBar.tsx
|
|
7006
|
-
function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, toggleTitle }) {
|
|
7780
|
+
function ReviewTopBar({ actions, center, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, sidebarPosition = "left", sourceMenu, toggleTitle }) {
|
|
7781
|
+
const sidebarToggle = /* @__PURE__ */ jsx("button", {
|
|
7782
|
+
"aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
7783
|
+
className: "review-top-bar-icon-button sidebar-toggle-button",
|
|
7784
|
+
onClick: onToggleSidebar,
|
|
7785
|
+
title: toggleTitle,
|
|
7786
|
+
type: "button",
|
|
7787
|
+
children: /* @__PURE__ */ jsx(SidebarSimpleIcon, {
|
|
7788
|
+
"aria-hidden": true,
|
|
7789
|
+
mirrored: sidebarPosition === "right",
|
|
7790
|
+
size: 18,
|
|
7791
|
+
weight: "bold"
|
|
7792
|
+
})
|
|
7793
|
+
});
|
|
7007
7794
|
return /* @__PURE__ */ jsxs("header", {
|
|
7008
7795
|
className: "review-top-bar workspace-top-bar",
|
|
7009
7796
|
children: [
|
|
@@ -7011,18 +7798,8 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
|
|
|
7011
7798
|
className: "review-top-bar-left",
|
|
7012
7799
|
children: [
|
|
7013
7800
|
leading,
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
className: "sidebar-toggle-button",
|
|
7017
|
-
onClick: onToggleSidebar,
|
|
7018
|
-
title: toggleTitle,
|
|
7019
|
-
type: "button",
|
|
7020
|
-
children: /* @__PURE__ */ jsx(SidebarSimpleIcon, {
|
|
7021
|
-
"aria-hidden": true,
|
|
7022
|
-
size: 18,
|
|
7023
|
-
weight: "bold"
|
|
7024
|
-
})
|
|
7025
|
-
}),
|
|
7801
|
+
sidebarPosition === "left" ? sidebarToggle : null,
|
|
7802
|
+
sourceMenu,
|
|
7026
7803
|
/* @__PURE__ */ jsx("div", {
|
|
7027
7804
|
className: "review-top-bar-repository-slot",
|
|
7028
7805
|
title: repositoryTooltip,
|
|
@@ -7030,36 +7807,27 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
|
|
|
7030
7807
|
})
|
|
7031
7808
|
]
|
|
7032
7809
|
}),
|
|
7033
|
-
/* @__PURE__ */ jsx(
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
onClick: () => onModeChange(item.value),
|
|
7041
|
-
role: "tab",
|
|
7042
|
-
title: item.title,
|
|
7043
|
-
type: "button",
|
|
7044
|
-
children: [
|
|
7045
|
-
item.icon,
|
|
7046
|
-
/* @__PURE__ */ jsx("span", {
|
|
7047
|
-
className: "review-mode-label",
|
|
7048
|
-
children: item.label
|
|
7049
|
-
}),
|
|
7050
|
-
item.indicator
|
|
7051
|
-
]
|
|
7052
|
-
}, item.value))
|
|
7810
|
+
modes ? /* @__PURE__ */ jsx(ReviewModeControl, {
|
|
7811
|
+
mode,
|
|
7812
|
+
modes,
|
|
7813
|
+
onModeChange
|
|
7814
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
7815
|
+
className: "review-top-bar-center",
|
|
7816
|
+
children: center
|
|
7053
7817
|
}),
|
|
7054
7818
|
/* @__PURE__ */ jsxs("div", {
|
|
7055
7819
|
className: "review-top-bar-right",
|
|
7056
|
-
children: [
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7820
|
+
children: [
|
|
7821
|
+
context ? /* @__PURE__ */ jsx("div", {
|
|
7822
|
+
className: "review-top-bar-context",
|
|
7823
|
+
children: context
|
|
7824
|
+
}) : null,
|
|
7825
|
+
actions ? /* @__PURE__ */ jsx("div", {
|
|
7826
|
+
className: "review-top-bar-actions",
|
|
7827
|
+
children: actions
|
|
7828
|
+
}) : null,
|
|
7829
|
+
sidebarPosition === "right" ? sidebarToggle : null
|
|
7830
|
+
]
|
|
7063
7831
|
})
|
|
7064
7832
|
]
|
|
7065
7833
|
});
|
|
@@ -7211,6 +7979,7 @@ function StageFileRow({ file, on, onToggle }) {
|
|
|
7211
7979
|
function StageGroupRow({ group, onToggleFile, onToggleGroup, selected }) {
|
|
7212
7980
|
const paths = group.files.map((file) => file.path);
|
|
7213
7981
|
const onCount = paths.filter((path) => selected.has(path)).length;
|
|
7982
|
+
const state = onCount === 0 ? "off" : onCount === paths.length ? "on" : "partial";
|
|
7214
7983
|
return /* @__PURE__ */ jsxs("div", {
|
|
7215
7984
|
className: "wt-stage-group",
|
|
7216
7985
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
@@ -7218,7 +7987,7 @@ function StageGroupRow({ group, onToggleFile, onToggleGroup, selected }) {
|
|
|
7218
7987
|
onClick: () => onToggleGroup(paths),
|
|
7219
7988
|
type: "button",
|
|
7220
7989
|
children: [
|
|
7221
|
-
/* @__PURE__ */ jsx(CommitCheck, { state
|
|
7990
|
+
/* @__PURE__ */ jsx(CommitCheck, { state }),
|
|
7222
7991
|
/* @__PURE__ */ jsx("span", {
|
|
7223
7992
|
className: "wt-stage-group-icon",
|
|
7224
7993
|
children: /* @__PURE__ */ jsx(ChapterIcon, {
|
|
@@ -7554,14 +8323,14 @@ function CommitView({ branch, draft, model, onCommit, onCommitOutput, onUpdateMe
|
|
|
7554
8323
|
}
|
|
7555
8324
|
//#endregion
|
|
7556
8325
|
//#region app/components/walkthrough/NarrativeWalkthroughView.tsx
|
|
7557
|
-
const getWalkthroughBlockScrollTarget = ({ activeBlockId, firstSupportBlockId,
|
|
8326
|
+
const getWalkthroughBlockScrollTarget = ({ activeBlockId, firstSupportBlockId, scrollTarget }) => scrollTarget.kind === "support" && firstSupportBlockId ? {
|
|
7558
8327
|
behavior: "smooth",
|
|
7559
8328
|
blockId: firstSupportBlockId,
|
|
7560
|
-
request:
|
|
7561
|
-
} :
|
|
8329
|
+
request: scrollTarget.nonce
|
|
8330
|
+
} : scrollTarget.kind === "stop" && activeBlockId && scrollTarget.nonce > 0 ? {
|
|
7562
8331
|
behavior: "smooth",
|
|
7563
8332
|
blockId: activeBlockId,
|
|
7564
|
-
request:
|
|
8333
|
+
request: scrollTarget.nonce
|
|
7565
8334
|
} : null;
|
|
7566
8335
|
const getFocusedRunDiffs = (item, files) => resolveWalkthroughHunkRuns(item, files).flatMap((run) => {
|
|
7567
8336
|
const focused = focusChangedFileForHunks(run.resolved.file, run.resolved.section, run.hunks);
|
|
@@ -7584,14 +8353,15 @@ const getWalkthroughNavigationKeyDirection = (event) => {
|
|
|
7584
8353
|
if (isCtrlOnly && event.key === "ArrowUp") return -1;
|
|
7585
8354
|
return 0;
|
|
7586
8355
|
};
|
|
8356
|
+
const getWalkthroughKeyboardForwardIndex = ({ index, scrollRequest }) => index === 0 && scrollRequest === 0 ? 0 : index + 1;
|
|
7587
8357
|
const emptyWalkthroughBlockSet = {
|
|
7588
8358
|
blocks: [],
|
|
7589
8359
|
firstBlockIdByStop: [],
|
|
7590
8360
|
stopIndexByBlockId: /* @__PURE__ */ new Map()
|
|
7591
8361
|
};
|
|
7592
|
-
function StopHeader({
|
|
8362
|
+
function StopHeader({ stop }) {
|
|
7593
8363
|
return /* @__PURE__ */ jsxs("div", {
|
|
7594
|
-
className:
|
|
8364
|
+
className: "wt-stop-block wt-stop-block-header",
|
|
7595
8365
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
7596
8366
|
className: "wt-stage-title-row",
|
|
7597
8367
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
@@ -7601,9 +8371,9 @@ function StopHeader({ current, stop }) {
|
|
|
7601
8371
|
}), /* @__PURE__ */ jsx(Narration, { prose: stop.prose })]
|
|
7602
8372
|
});
|
|
7603
8373
|
}
|
|
7604
|
-
function SupportHeader(
|
|
8374
|
+
function SupportHeader() {
|
|
7605
8375
|
return /* @__PURE__ */ jsxs("div", {
|
|
7606
|
-
className:
|
|
8376
|
+
className: "wt-stop-block wt-stop-block-header",
|
|
7607
8377
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
7608
8378
|
className: "wt-stage-title-row",
|
|
7609
8379
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
@@ -7624,10 +8394,7 @@ const createWalkthroughBlocks = (files, walkthroughView, currentIndex) => {
|
|
|
7624
8394
|
firstBlockIdByStop[stop.index] = blockId;
|
|
7625
8395
|
stopIndexByBlockId.set(blockId, stop.index);
|
|
7626
8396
|
blocks.push({
|
|
7627
|
-
header: /* @__PURE__ */ jsx(StopHeader, {
|
|
7628
|
-
current: stop.index === currentIndex,
|
|
7629
|
-
stop
|
|
7630
|
-
}),
|
|
8397
|
+
header: /* @__PURE__ */ jsx(StopHeader, { stop }),
|
|
7631
8398
|
headerSelected: stop.index === currentIndex,
|
|
7632
8399
|
id: blockId
|
|
7633
8400
|
});
|
|
@@ -7639,10 +8406,7 @@ const createWalkthroughBlocks = (files, walkthroughView, currentIndex) => {
|
|
|
7639
8406
|
stopIndexByBlockId.set(blockId, stop.index);
|
|
7640
8407
|
blocks.push({
|
|
7641
8408
|
file,
|
|
7642
|
-
header: runIndex === 0 ? /* @__PURE__ */ jsx(StopHeader, {
|
|
7643
|
-
current: stop.index === currentIndex,
|
|
7644
|
-
stop
|
|
7645
|
-
}) : null,
|
|
8409
|
+
header: runIndex === 0 ? /* @__PURE__ */ jsx(StopHeader, { stop }) : null,
|
|
7646
8410
|
headerSelected: stop.index === currentIndex,
|
|
7647
8411
|
id: blockId,
|
|
7648
8412
|
itemIdPrefix: blockId,
|
|
@@ -7671,7 +8435,7 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace) =
|
|
|
7671
8435
|
const isFirstBlock = blocks.length === 0;
|
|
7672
8436
|
blocks.push({
|
|
7673
8437
|
file,
|
|
7674
|
-
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {
|
|
8438
|
+
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {}) : null,
|
|
7675
8439
|
headerSelected: selected,
|
|
7676
8440
|
id: blockId,
|
|
7677
8441
|
itemIdPrefix: blockId,
|
|
@@ -7685,7 +8449,7 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace) =
|
|
|
7685
8449
|
const isFirstBlock = blocks.length === 0;
|
|
7686
8450
|
blocks.push({
|
|
7687
8451
|
file,
|
|
7688
|
-
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {
|
|
8452
|
+
header: isFirstBlock ? /* @__PURE__ */ jsx(SupportHeader, {}) : null,
|
|
7689
8453
|
headerSelected: selected,
|
|
7690
8454
|
id: blockId,
|
|
7691
8455
|
itemIdPrefix: blockId,
|
|
@@ -7720,8 +8484,10 @@ function Arc({ committable, navigation, onShareWalkthrough, shareWalkthroughDisa
|
|
|
7720
8484
|
const goArcPrev = useCallback(() => {
|
|
7721
8485
|
if (navigation.mode === "stop") navigation.goPrev();
|
|
7722
8486
|
else if (navigation.mode === "support") navigation.goStop(walkthroughView.sequence.length - 1);
|
|
7723
|
-
else if (navigation.mode === "commit")
|
|
7724
|
-
|
|
8487
|
+
else if (navigation.mode === "commit") {
|
|
8488
|
+
if (supportAvailable) navigation.openSupport();
|
|
8489
|
+
else navigation.goStop(walkthroughView.sequence.length - 1);
|
|
8490
|
+
}
|
|
7725
8491
|
}, [
|
|
7726
8492
|
navigation,
|
|
7727
8493
|
supportAvailable,
|
|
@@ -7880,9 +8646,7 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
7880
8646
|
const reviewBlockScrollTarget = getWalkthroughBlockScrollTarget({
|
|
7881
8647
|
activeBlockId,
|
|
7882
8648
|
firstSupportBlockId,
|
|
7883
|
-
|
|
7884
|
-
stopScrollRequest: navigation.scrollTarget.nonce,
|
|
7885
|
-
supportScrollRequest: navigation.supportScrollRequest
|
|
8649
|
+
scrollTarget: navigation.scrollTarget
|
|
7886
8650
|
});
|
|
7887
8651
|
const handleActiveBlockChange = useCallback((blockId) => {
|
|
7888
8652
|
onActiveReviewTargetChange(getBlockReviewTarget(reviewBlocks, blockId));
|
|
@@ -7918,7 +8682,11 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
7918
8682
|
if (direction === 1) {
|
|
7919
8683
|
event.preventDefault();
|
|
7920
8684
|
if (navigation.mode === "stop") {
|
|
7921
|
-
|
|
8685
|
+
const targetIndex = getWalkthroughKeyboardForwardIndex({
|
|
8686
|
+
index: navigation.index,
|
|
8687
|
+
scrollRequest: navigation.scrollTarget.nonce
|
|
8688
|
+
});
|
|
8689
|
+
if (targetIndex < walkthroughView.sequence.length) navigation.goStop(targetIndex);
|
|
7922
8690
|
else if (supportAvailable) navigation.openSupport();
|
|
7923
8691
|
else if (committable) navigation.enterCommit();
|
|
7924
8692
|
} else if (navigation.mode === "support" && committable) navigation.enterCommit();
|
|
@@ -7926,8 +8694,10 @@ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onAct
|
|
|
7926
8694
|
event.preventDefault();
|
|
7927
8695
|
if (navigation.mode === "stop") navigation.goPrev();
|
|
7928
8696
|
else if (navigation.mode === "support") navigation.goStop(navigation.index);
|
|
7929
|
-
else if (navigation.mode === "commit")
|
|
7930
|
-
|
|
8697
|
+
else if (navigation.mode === "commit") {
|
|
8698
|
+
if (supportAvailable) navigation.openSupport();
|
|
8699
|
+
else navigation.goStop(navigation.index);
|
|
8700
|
+
}
|
|
7931
8701
|
}
|
|
7932
8702
|
}, [
|
|
7933
8703
|
committable,
|
|
@@ -8160,9 +8930,9 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8160
8930
|
const [index, setIndex] = useState(0);
|
|
8161
8931
|
const [scrollTarget, setScrollTarget] = useState({
|
|
8162
8932
|
index: 0,
|
|
8933
|
+
kind: "stop",
|
|
8163
8934
|
nonce: 0
|
|
8164
8935
|
});
|
|
8165
|
-
const [supportScrollRequest, setSupportScrollRequest] = useState(0);
|
|
8166
8936
|
const [supportVisited, setSupportVisited] = useState(false);
|
|
8167
8937
|
const [visited, setVisited] = useState(() => {
|
|
8168
8938
|
const stopId = firstStopId(walkthrough);
|
|
@@ -8193,9 +8963,9 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8193
8963
|
setIndex(0);
|
|
8194
8964
|
setScrollTarget({
|
|
8195
8965
|
index: 0,
|
|
8966
|
+
kind: "stop",
|
|
8196
8967
|
nonce: 0
|
|
8197
8968
|
});
|
|
8198
|
-
setSupportScrollRequest(0);
|
|
8199
8969
|
setSupportVisited(false);
|
|
8200
8970
|
const stopId = firstStopId(walkthrough);
|
|
8201
8971
|
setVisited(new Set(stopId ? [stopId] : []));
|
|
@@ -8257,6 +9027,7 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8257
9027
|
pendingSupportScrollRef.current = null;
|
|
8258
9028
|
setScrollTarget((current) => ({
|
|
8259
9029
|
index: clamped,
|
|
9030
|
+
kind: "stop",
|
|
8260
9031
|
nonce: current.nonce + 1
|
|
8261
9032
|
}));
|
|
8262
9033
|
}, [
|
|
@@ -8301,7 +9072,11 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8301
9072
|
if (walkthroughView?.sequence.length) setIndex(walkthroughView.sequence.length - 1);
|
|
8302
9073
|
setMode("support");
|
|
8303
9074
|
pendingSupportScrollRef.current = { walkthrough };
|
|
8304
|
-
|
|
9075
|
+
setScrollTarget((current) => ({
|
|
9076
|
+
...current,
|
|
9077
|
+
kind: "support",
|
|
9078
|
+
nonce: current.nonce + 1
|
|
9079
|
+
}));
|
|
8305
9080
|
setSupportVisited(true);
|
|
8306
9081
|
}, [
|
|
8307
9082
|
walkthrough,
|
|
@@ -8309,11 +9084,15 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8309
9084
|
leaveStopMode
|
|
8310
9085
|
]);
|
|
8311
9086
|
const syncSupportFromScroll = useCallback(() => {
|
|
9087
|
+
const pendingStop = pendingStopScrollRef.current;
|
|
9088
|
+
if (pendingStop) {
|
|
9089
|
+
if (pendingStop.walkthrough === walkthrough) return;
|
|
9090
|
+
pendingStopScrollRef.current = null;
|
|
9091
|
+
}
|
|
8312
9092
|
pendingSupportScrollRef.current = null;
|
|
8313
|
-
pendingStopScrollRef.current = null;
|
|
8314
9093
|
setMode("support");
|
|
8315
9094
|
setSupportVisited(true);
|
|
8316
|
-
}, []);
|
|
9095
|
+
}, [walkthrough]);
|
|
8317
9096
|
return {
|
|
8318
9097
|
commitBody,
|
|
8319
9098
|
commitSelected,
|
|
@@ -8332,7 +9111,6 @@ const useNarrativeNavigation = (walkthrough, files, resetKey = "") => {
|
|
|
8332
9111
|
scrollTarget,
|
|
8333
9112
|
setCommitBody,
|
|
8334
9113
|
setCommitSubject,
|
|
8335
|
-
supportScrollRequest,
|
|
8336
9114
|
supportVisited,
|
|
8337
9115
|
syncIndexFromScroll,
|
|
8338
9116
|
syncSupportFromScroll,
|
|
@@ -8431,7 +9209,7 @@ const writeSidebarWidth = (width, storage = localStorage) => {
|
|
|
8431
9209
|
};
|
|
8432
9210
|
//#endregion
|
|
8433
9211
|
//#region app/hooks/useResizableSidebar.ts
|
|
8434
|
-
function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, readWidth }) {
|
|
9212
|
+
function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, position, readWidth }) {
|
|
8435
9213
|
const [sidebarWidth, setSidebarWidth] = useState(readWidth);
|
|
8436
9214
|
return {
|
|
8437
9215
|
resizeSidebar: useCallback((event) => {
|
|
@@ -8440,7 +9218,7 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8440
9218
|
const handle = event.currentTarget;
|
|
8441
9219
|
const shell = handle.parentElement;
|
|
8442
9220
|
if (!shell) return;
|
|
8443
|
-
const
|
|
9221
|
+
const shellRect = shell.getBoundingClientRect();
|
|
8444
9222
|
handle.setPointerCapture(event.pointerId);
|
|
8445
9223
|
handle.classList.add("dragging");
|
|
8446
9224
|
document.body.style.cursor = "col-resize";
|
|
@@ -8454,7 +9232,7 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8454
9232
|
document.body.style.cursor = "";
|
|
8455
9233
|
};
|
|
8456
9234
|
const handleMove = (moveEvent) => {
|
|
8457
|
-
const rawWidth = moveEvent.clientX -
|
|
9235
|
+
const rawWidth = position === "right" ? shellRect.right - moveEvent.clientX : moveEvent.clientX - shellRect.left;
|
|
8458
9236
|
if (collapseThreshold != null && rawWidth < collapseThreshold) {
|
|
8459
9237
|
collapsed = true;
|
|
8460
9238
|
onCollapse?.();
|
|
@@ -8476,7 +9254,8 @@ function useResizableSidebar({ collapseThreshold, onCollapse, onWidthCommit, rea
|
|
|
8476
9254
|
}, [
|
|
8477
9255
|
collapseThreshold,
|
|
8478
9256
|
onCollapse,
|
|
8479
|
-
onWidthCommit
|
|
9257
|
+
onWidthCommit,
|
|
9258
|
+
position
|
|
8480
9259
|
]),
|
|
8481
9260
|
sidebarWidth
|
|
8482
9261
|
};
|
|
@@ -8582,6 +9361,7 @@ function useReviewCommentDrafts({ canCreateComment = true, comments, onCommentFi
|
|
|
8582
9361
|
setComments,
|
|
8583
9362
|
updateActiveReviewCommentDraft
|
|
8584
9363
|
]),
|
|
9364
|
+
focusComment,
|
|
8585
9365
|
focusCommentId,
|
|
8586
9366
|
focusCommentRequest,
|
|
8587
9367
|
resetCommentFocus,
|
|
@@ -8652,6 +9432,7 @@ function useReviewFileState({ initialSelectedPath = null, onViewedChange } = {})
|
|
|
8652
9432
|
const defaultConfigTemplate = {
|
|
8653
9433
|
settings: {
|
|
8654
9434
|
"agentBackend": "codex",
|
|
9435
|
+
"checkForUpdates": true,
|
|
8655
9436
|
"claudeModel": "claude-sonnet-4-6",
|
|
8656
9437
|
"codeFontFamily": "",
|
|
8657
9438
|
"codeFontSize": 13,
|
|
@@ -8663,6 +9444,7 @@ const defaultConfigTemplate = {
|
|
|
8663
9444
|
"opencodeModel": "opencode-default",
|
|
8664
9445
|
"piModel": "pi-default",
|
|
8665
9446
|
"reviewCommentsPrefix": "# Address these Review Comments",
|
|
9447
|
+
"sidebarPosition": "left",
|
|
8666
9448
|
"showOutdated": false,
|
|
8667
9449
|
"showWhitespace": false,
|
|
8668
9450
|
"theme": "system",
|
|
@@ -8670,6 +9452,7 @@ const defaultConfigTemplate = {
|
|
|
8670
9452
|
"wordWrap": false
|
|
8671
9453
|
},
|
|
8672
9454
|
keymap: {
|
|
9455
|
+
"askAgent": "Mod+Alt+Enter",
|
|
8673
9456
|
"closeSearch": "Escape",
|
|
8674
9457
|
"commandBar": "Mod+Shift+p",
|
|
8675
9458
|
"diffSearch": "Mod+f",
|
|
@@ -8727,10 +9510,35 @@ const emptyReviewComments = [];
|
|
|
8727
9510
|
const emptyGeneralCommentThreads = [];
|
|
8728
9511
|
const emptyPaths = /* @__PURE__ */ new Set();
|
|
8729
9512
|
const emptyWalkthroughNotes = /* @__PURE__ */ new Map();
|
|
9513
|
+
const reviewSurfacePreferencesKey = "codiff:web-review-surface-preferences:v1";
|
|
9514
|
+
const getLocationHashTarget = () => {
|
|
9515
|
+
const hash = window.location.hash.slice(1);
|
|
9516
|
+
if (!hash) return null;
|
|
9517
|
+
try {
|
|
9518
|
+
return decodeURIComponent(hash);
|
|
9519
|
+
} catch {
|
|
9520
|
+
return hash;
|
|
9521
|
+
}
|
|
9522
|
+
};
|
|
9523
|
+
const commentMatchesHashTarget = (comment, target) => comment.id === target || comment.threadId === target || comment.url?.slice(comment.url.lastIndexOf("#") + 1) === target;
|
|
8730
9524
|
const readSharedSidebarWidth = () => typeof localStorage === "undefined" ? 292 : readSidebarWidth();
|
|
8731
9525
|
const writeSharedSidebarWidth = (width) => {
|
|
8732
9526
|
if (typeof localStorage !== "undefined") writeSidebarWidth(width);
|
|
8733
9527
|
};
|
|
9528
|
+
const readStoredSidebarCollapsed = () => {
|
|
9529
|
+
if (typeof localStorage === "undefined") return null;
|
|
9530
|
+
try {
|
|
9531
|
+
const stored = JSON.parse(localStorage.getItem(reviewSurfacePreferencesKey) ?? "{}");
|
|
9532
|
+
if (stored && typeof stored === "object" && "sidebarCollapsed" in stored && typeof stored.sidebarCollapsed === "boolean") return stored.sidebarCollapsed;
|
|
9533
|
+
} catch {}
|
|
9534
|
+
return null;
|
|
9535
|
+
};
|
|
9536
|
+
const writeStoredSidebarCollapsed = (sidebarCollapsed) => {
|
|
9537
|
+
if (typeof localStorage === "undefined") return;
|
|
9538
|
+
try {
|
|
9539
|
+
localStorage.setItem(reviewSurfacePreferencesKey, JSON.stringify({ sidebarCollapsed }));
|
|
9540
|
+
} catch {}
|
|
9541
|
+
};
|
|
8734
9542
|
const getSnapshotReviewComments = (snapshot) => {
|
|
8735
9543
|
if (!snapshot.reviewComments?.length) return emptyReviewComments;
|
|
8736
9544
|
return getReviewCommentsFromState({
|
|
@@ -8752,7 +9560,9 @@ const disabledCommitMessage = async () => ({
|
|
|
8752
9560
|
reason: "Shared walkthroughs are read-only.",
|
|
8753
9561
|
status: "unavailable"
|
|
8754
9562
|
});
|
|
8755
|
-
|
|
9563
|
+
const mobileSidebarMediaQuery = "(max-width: 720px)";
|
|
9564
|
+
const shouldCollapseSidebarInitially = () => readStoredSidebarCollapsed() ?? (typeof window !== "undefined" && window.matchMedia(mobileSidebarMediaQuery).matches);
|
|
9565
|
+
function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMode, interactive, onDeleteShare, onModeChange, providerLabel = "provider", repositoryUrl, settingsBar, sidebarPosition = "left", signInLabel = "Sign in to comment", snapshot, sourceDescriptionFooterAside, title }) {
|
|
8756
9566
|
const canComment = commenting?.canComment ?? Boolean(interactive);
|
|
8757
9567
|
const deleteShare = useCallback(async () => {
|
|
8758
9568
|
if (!onDeleteShare || !window.confirm("Delete this shared walkthrough? This cannot be undone.")) return;
|
|
@@ -8775,23 +9585,44 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8775
9585
|
const keymap = useMemo(() => createDefaultConfig().keymap, []);
|
|
8776
9586
|
const [fileSearchQuery, setFileSearchQuery] = useState("");
|
|
8777
9587
|
const [uncontrolledSidebarMode, setUncontrolledSidebarMode] = useState(() => initialMode ?? (interactive ? "tree" : "walkthrough"));
|
|
8778
|
-
const [sidebarCollapsed, setSidebarCollapsed] = useState(
|
|
9588
|
+
const [sidebarCollapsed, setSidebarCollapsed] = useState(null);
|
|
9589
|
+
const sidebarCollapsedRef = useRef(null);
|
|
9590
|
+
const sidebarInteractedRef = useRef(false);
|
|
9591
|
+
const updateSidebarCollapsed = useCallback((value, persist) => {
|
|
9592
|
+
sidebarCollapsedRef.current = value;
|
|
9593
|
+
setSidebarCollapsed(value);
|
|
9594
|
+
if (persist) writeStoredSidebarCollapsed(value);
|
|
9595
|
+
}, []);
|
|
9596
|
+
const toggleSidebar = useCallback(() => {
|
|
9597
|
+
sidebarInteractedRef.current = true;
|
|
9598
|
+
updateSidebarCollapsed(!(sidebarCollapsedRef.current ?? shouldCollapseSidebarInitially()), true);
|
|
9599
|
+
}, [updateSidebarCollapsed]);
|
|
9600
|
+
useEffect(() => {
|
|
9601
|
+
const timeout = window.setTimeout(() => {
|
|
9602
|
+
if (!sidebarInteractedRef.current) updateSidebarCollapsed(shouldCollapseSidebarInitially(), false);
|
|
9603
|
+
}, 0);
|
|
9604
|
+
return () => window.clearTimeout(timeout);
|
|
9605
|
+
}, [updateSidebarCollapsed]);
|
|
8779
9606
|
useEffect(() => {
|
|
8780
9607
|
const handleKeyDown = (event) => {
|
|
8781
9608
|
if (event.repeat || !matchesShortcut(event, keymap, "toggleSidebar")) return;
|
|
8782
9609
|
event.preventDefault();
|
|
8783
|
-
|
|
9610
|
+
toggleSidebar();
|
|
8784
9611
|
};
|
|
8785
9612
|
window.addEventListener("keydown", handleKeyDown);
|
|
8786
9613
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
8787
|
-
}, [keymap]);
|
|
9614
|
+
}, [keymap, toggleSidebar]);
|
|
8788
9615
|
const sidebarMode = Boolean(initialMode && onModeChange) && initialMode ? initialMode : uncontrolledSidebarMode;
|
|
8789
9616
|
const [treeScrollTarget, setTreeScrollTarget] = useState(null);
|
|
8790
9617
|
const { bumpItemVersion, collapsed, expandedGenerated, itemVersionByKey, selectedPath, setSelectedPath, toggleCollapsed, toggleViewed, viewed } = useReviewFileState({ initialSelectedPath: snapshot.files[0]?.path ?? null });
|
|
8791
9618
|
const { resizeSidebar, sidebarWidth } = useResizableSidebar({
|
|
8792
9619
|
collapseThreshold: 80,
|
|
8793
|
-
onCollapse: () =>
|
|
9620
|
+
onCollapse: () => {
|
|
9621
|
+
sidebarInteractedRef.current = true;
|
|
9622
|
+
updateSidebarCollapsed(true, true);
|
|
9623
|
+
},
|
|
8794
9624
|
onWidthCommit: writeSharedSidebarWidth,
|
|
9625
|
+
position: sidebarPosition,
|
|
8795
9626
|
readWidth: readSharedSidebarWidth
|
|
8796
9627
|
});
|
|
8797
9628
|
const snapshotReviewComments = useMemo(() => getSnapshotReviewComments(snapshot), [snapshot]);
|
|
@@ -8802,7 +9633,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8802
9633
|
} : comment), [editedReviewCommentBodies, snapshotReviewComments]);
|
|
8803
9634
|
const [localReviewComments, setLocalReviewComments] = useState(emptyReviewComments);
|
|
8804
9635
|
const reviewComments = useMemo(() => mergeReviewComments(visibleSnapshotReviewComments, localReviewComments), [localReviewComments, visibleSnapshotReviewComments]);
|
|
8805
|
-
const { activeReviewCommentDraftRef, activeReviewCommentDraftState, clearCommentFocus, createComment, deleteComment: deleteLocalComment, focusCommentId, focusCommentRequest, reviewCommentsRef, updateActiveReviewCommentDraft, updateComment } = useReviewCommentDrafts({
|
|
9636
|
+
const { activeReviewCommentDraftRef, activeReviewCommentDraftState, clearCommentFocus, createComment, deleteComment: deleteLocalComment, focusComment, focusCommentId, focusCommentRequest, reviewCommentsRef, updateActiveReviewCommentDraft, updateComment } = useReviewCommentDrafts({
|
|
8806
9637
|
canCreateComment: canComment,
|
|
8807
9638
|
comments: reviewComments,
|
|
8808
9639
|
onCommentFileChange: bumpItemVersion,
|
|
@@ -8828,6 +9659,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
8828
9659
|
const walkthroughRequestPendingRef = useRef(false);
|
|
8829
9660
|
const [walkthroughRequestId, setWalkthroughRequestId] = useState(0);
|
|
8830
9661
|
const interactiveRef = useRef(interactive);
|
|
9662
|
+
const handledHashTargetRef = useRef(null);
|
|
8831
9663
|
const visibleFiles = useMemo(() => sortFiles(snapshot.files).filter((file) => fuzzyMatches(file.path, fileSearchQuery) && fileHasVisibleDiff(file, snapshot.preferences.showWhitespace)), [
|
|
8832
9664
|
fileSearchQuery,
|
|
8833
9665
|
snapshot.files,
|
|
@@ -9074,6 +9906,56 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9074
9906
|
request: (current?.request ?? 0) + 1
|
|
9075
9907
|
}));
|
|
9076
9908
|
}, [setSelectedPath]);
|
|
9909
|
+
const activateReviewComment = useCallback((comment) => {
|
|
9910
|
+
changeSidebarMode("tree");
|
|
9911
|
+
setSelectedPath(comment.filePath);
|
|
9912
|
+
focusComment(comment.id);
|
|
9913
|
+
setTreeScrollTarget((current) => ({
|
|
9914
|
+
behavior: "smooth",
|
|
9915
|
+
commentId: comment.id,
|
|
9916
|
+
path: comment.filePath,
|
|
9917
|
+
request: (current?.request ?? 0) + 1
|
|
9918
|
+
}));
|
|
9919
|
+
}, [
|
|
9920
|
+
changeSidebarMode,
|
|
9921
|
+
focusComment,
|
|
9922
|
+
setSelectedPath
|
|
9923
|
+
]);
|
|
9924
|
+
const activateHashTarget = useCallback(() => {
|
|
9925
|
+
const target = getLocationHashTarget();
|
|
9926
|
+
if (!target || handledHashTargetRef.current === target) return;
|
|
9927
|
+
const reviewComment = reviewComments.find((comment) => commentMatchesHashTarget(comment, target));
|
|
9928
|
+
if (reviewComment) {
|
|
9929
|
+
handledHashTargetRef.current = target;
|
|
9930
|
+
activateReviewComment(reviewComment);
|
|
9931
|
+
return;
|
|
9932
|
+
}
|
|
9933
|
+
for (const thread of generalCommentThreads) {
|
|
9934
|
+
const generalComment = thread.comments.find((comment) => commentMatchesHashTarget(comment, target)) ?? (thread.id === target ? thread.comments[0] : void 0);
|
|
9935
|
+
if (generalComment) {
|
|
9936
|
+
handledHashTargetRef.current = target;
|
|
9937
|
+
activateGeneralComment(generalComment.id);
|
|
9938
|
+
return;
|
|
9939
|
+
}
|
|
9940
|
+
}
|
|
9941
|
+
}, [
|
|
9942
|
+
activateGeneralComment,
|
|
9943
|
+
activateReviewComment,
|
|
9944
|
+
generalCommentThreads,
|
|
9945
|
+
reviewComments
|
|
9946
|
+
]);
|
|
9947
|
+
useEffect(() => {
|
|
9948
|
+
const timeout = window.setTimeout(activateHashTarget, 0);
|
|
9949
|
+
return () => window.clearTimeout(timeout);
|
|
9950
|
+
}, [activateHashTarget]);
|
|
9951
|
+
useEffect(() => {
|
|
9952
|
+
const handleHashChange = () => {
|
|
9953
|
+
handledHashTargetRef.current = null;
|
|
9954
|
+
activateHashTarget();
|
|
9955
|
+
};
|
|
9956
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
9957
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
9958
|
+
}, [activateHashTarget]);
|
|
9077
9959
|
const updateSelectedPathFromScroll = useCallback((viewer) => {
|
|
9078
9960
|
const nextPath = getSelectedPathFromScroll(viewer, visibleFiles, snapshot.preferences.showWhitespace);
|
|
9079
9961
|
if (nextPath) setSelectedPath((current) => current === nextPath ? current : nextPath);
|
|
@@ -9181,7 +10063,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9181
10063
|
});
|
|
9182
10064
|
};
|
|
9183
10065
|
const sourceLabel = snapshot.repository.source.type === "working-tree" ? null : getSourceLabel(snapshot.repository.source);
|
|
9184
|
-
const rootLabel = repositoryUrl ? snapshot.repository.root :
|
|
10066
|
+
const rootLabel = repositoryUrl ? snapshot.repository.root : abbreviateHomePath(snapshot.repository.root);
|
|
9185
10067
|
const sourceExternalUrl = snapshot.repository.source.type === "pull-request" ? externalUrl ?? snapshot.repository.source.url : null;
|
|
9186
10068
|
const repositoryLinkUrl = repositoryUrl ?? sourceExternalUrl;
|
|
9187
10069
|
const walkthroughStatus = walkthroughRequestPending && interactive?.walkthroughStatus !== "ready" ? "generating" : interactive?.walkthroughStatus;
|
|
@@ -9252,9 +10134,10 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9252
10134
|
children: settingsBar
|
|
9253
10135
|
}) : null] }) : void 0;
|
|
9254
10136
|
return /* @__PURE__ */ jsxs("div", {
|
|
9255
|
-
className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}${sidebarCollapsed ? " sidebar-collapsed" : ""}`,
|
|
10137
|
+
className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}${sidebarCollapsed === null ? " sidebar-auto" : sidebarCollapsed ? " sidebar-collapsed" : ""}`,
|
|
10138
|
+
"data-sidebar-position": sidebarPosition,
|
|
9256
10139
|
"data-theme": shellTheme,
|
|
9257
|
-
style: sidebarCollapsed ? void 0 : { gridTemplateColumns: `${sidebarWidth}px 0 minmax(0, 1fr)` },
|
|
10140
|
+
style: sidebarCollapsed !== false ? void 0 : { gridTemplateColumns: sidebarPosition === "right" ? `minmax(0, 1fr) 0 ${sidebarWidth}px` : `${sidebarWidth}px 0 minmax(0, 1fr)` },
|
|
9258
10141
|
children: [
|
|
9259
10142
|
/* @__PURE__ */ jsx(ReviewTopBar, {
|
|
9260
10143
|
actions: topBarActions,
|
|
@@ -9295,7 +10178,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9295
10178
|
mode: sidebarMode,
|
|
9296
10179
|
modes: reviewModes,
|
|
9297
10180
|
onModeChange: changeSidebarMode,
|
|
9298
|
-
onToggleSidebar:
|
|
10181
|
+
onToggleSidebar: toggleSidebar,
|
|
9299
10182
|
repository: repositoryLinkUrl ? /* @__PURE__ */ jsx("a", {
|
|
9300
10183
|
className: "review-top-bar-repository",
|
|
9301
10184
|
href: repositoryLinkUrl,
|
|
@@ -9307,7 +10190,8 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9307
10190
|
children: rootLabel
|
|
9308
10191
|
}),
|
|
9309
10192
|
repositoryTooltip: snapshot.repository.root,
|
|
9310
|
-
sidebarCollapsed,
|
|
10193
|
+
sidebarCollapsed: sidebarCollapsed ?? false,
|
|
10194
|
+
sidebarPosition,
|
|
9311
10195
|
toggleTitle: `${sidebarCollapsed ? "Expand" : "Collapse"} sidebar`
|
|
9312
10196
|
}),
|
|
9313
10197
|
/* @__PURE__ */ jsxs("aside", {
|
|
@@ -9385,6 +10269,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
|
|
|
9385
10269
|
onCancelEdit: cancelEditGeneralComment,
|
|
9386
10270
|
onChangeDraft: setGeneralCommentDraft,
|
|
9387
10271
|
onChangeEditDraft: setGeneralCommentEditDraft,
|
|
10272
|
+
onResolveDiscussion: resolveDiscussion,
|
|
9388
10273
|
onSaveEdit: saveGeneralCommentEdit,
|
|
9389
10274
|
onStartEdit: startEditGeneralComment,
|
|
9390
10275
|
onSubmit: submitGeneralComment,
|