@plannotator/ui 0.52.1 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HANDOFF.md +284 -29
- package/README.md +45 -5
- package/components/AnnotationPanel.tsx +195 -118
- package/components/AnnotationToolbar.tsx +17 -14
- package/components/AnnotationToolstrip.tsx +54 -40
- package/components/BlockRenderer.tsx +20 -14
- package/components/CodeFilePopout.tsx +132 -69
- package/components/CommentPopover.tsx +8 -349
- package/components/ComposerTokenTextarea.tsx +379 -0
- package/components/ExportModal.tsx +6 -1
- package/components/HostTitle.tsx +108 -0
- package/components/HtmlSurfaceControls.tsx +2 -1
- package/components/ImageLightbox.tsx +288 -17
- package/components/ImagePeek.tsx +255 -0
- package/components/ImageResolverContext.tsx +31 -0
- package/components/InlineMarkdown.tsx +123 -15
- package/components/LocalLinkContext.tsx +49 -0
- package/components/MentionPicker.tsx +51 -0
- package/components/MenuVersionSection.tsx +51 -25
- package/components/OpenInAppButton.tsx +18 -49
- package/components/PlanHeaderMenu.tsx +2 -8
- package/components/ProviderIcons.tsx +10 -4
- package/components/RenderedMarkdown.tsx +40 -4
- package/components/Settings.tsx +33 -5
- package/components/SkillReferenceMenu.tsx +67 -18
- package/components/Tooltip.tsx +51 -4
- package/components/Viewer.tsx +220 -49
- package/components/ai/AIProviderBar.tsx +11 -0
- package/components/ai/AskSessionSetupHint.tsx +12 -0
- package/components/ai/DocumentAIChatPanel.tsx +17 -5
- package/components/ai/SessionAskNotice.tsx +4 -3
- package/components/blocks/BlockquoteBlock.tsx +159 -0
- package/components/blocks/HtmlBlock.tsx +121 -9
- package/components/blocks/QuestionBlock.tsx +183 -96
- package/components/blocks/QuestionContext.tsx +3 -0
- package/components/diagram/DiagramViewer.tsx +41 -4
- package/components/diagram/index.ts +1 -1
- package/components/html-viewer/HtmlViewer.tsx +95 -3
- package/components/html-viewer/bridge-script.asset.js +413 -7
- package/components/html-viewer/bridge-script.lite.ts +1 -1
- package/components/html-viewer/bridge-script.ts +454 -7
- package/components/html-viewer/useHtmlAnnotation.ts +51 -3
- package/components/icons/AgentIcons.tsx +9 -7
- package/components/plan-diff/PlanCleanDiffView.tsx +239 -36
- package/components/plan-diff/PlanDiffViewer.tsx +6 -1
- package/configure.ts +27 -1
- package/hooks/useAgentJobs.ts +8 -3
- package/hooks/useAnnotationHighlighter.ts +128 -23
- package/hooks/useExternalAnnotations.ts +6 -0
- package/hooks/useMentionAutocomplete.ts +1 -1
- package/hooks/useOpenInApps.ts +88 -0
- package/hooks/useSkillReferenceAutocomplete.ts +7 -3
- package/hooks/useUpdateCheck.ts +38 -98
- package/hooks/useUpdateCheckSetting.ts +43 -0
- package/hooks/useUpdateToast.ts +101 -0
- package/hooks/useVimSelection.ts +9 -0
- package/package.json +2 -2
- package/shortcuts/code-review/markdownPreview.shortcuts.ts +18 -0
- package/shortcuts/index.ts +1 -0
- package/shortcuts/runtime.ts +10 -3
- package/styles.css +1 -1
- package/types.ts +48 -0
- package/utils/agentClosed.ts +26 -0
- package/utils/agentToolSetting.ts +16 -9
- package/utils/aiProvider.ts +7 -0
- package/utils/askSessionSetupHint.ts +27 -0
- package/utils/blockTargeting.ts +14 -3
- package/utils/mentions.ts +34 -5
- package/utils/parser.ts +65 -51
- package/utils/pasteInsert.ts +234 -0
- package/utils/planDiffEngine.ts +272 -2
- package/utils/skillCatalog.ts +15 -2
- package/utils/skillReferences.ts +50 -13
- package/utils/storage.ts +5 -0
- package/utils/textCut.ts +41 -0
- package/utils/updateNotice.ts +120 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState,
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import type { EditorMode, InputMethod } from '../types';
|
|
3
3
|
import { TaterSpritePullup } from './TaterSpritePullup';
|
|
4
4
|
|
|
@@ -36,6 +36,12 @@ export interface AnnotationToolstripProps {
|
|
|
36
36
|
* retain the complete action-mode group.
|
|
37
37
|
*/
|
|
38
38
|
hideQuickLabel?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Omit only the Redline action. Defaults to false. For a host whose
|
|
41
|
+
* composer is its own (the Inbox surface on a phone draws Select, Pinpoint,
|
|
42
|
+
* Markup and Comment).
|
|
43
|
+
*/
|
|
44
|
+
hideRedline?: boolean;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/** Render the shared input-method and annotation-mode controls. */
|
|
@@ -50,6 +56,7 @@ export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
|
|
|
50
56
|
iconOnly = false,
|
|
51
57
|
hideInputMethodSwitch = false,
|
|
52
58
|
hideQuickLabel = false,
|
|
59
|
+
hideRedline = false,
|
|
53
60
|
}) => {
|
|
54
61
|
const [showHelp, setShowHelp] = useState(false);
|
|
55
62
|
const [helpTab, setHelpTab] = useState<'selection' | 'plannotator'>('selection');
|
|
@@ -136,6 +143,7 @@ export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
|
|
|
136
143
|
</svg>
|
|
137
144
|
}
|
|
138
145
|
/>
|
|
146
|
+
{!hideRedline && (
|
|
139
147
|
<ToolstripButton
|
|
140
148
|
active={mode === 'redline'}
|
|
141
149
|
onClick={() => onModeChange('redline')}
|
|
@@ -150,6 +158,7 @@ export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
|
|
|
150
158
|
</svg>
|
|
151
159
|
}
|
|
152
160
|
/>
|
|
161
|
+
)}
|
|
153
162
|
{!hideQuickLabel && (
|
|
154
163
|
<ToolstripButton
|
|
155
164
|
active={mode === 'quickLabel'}
|
|
@@ -282,10 +291,26 @@ const ICON_SIZE = 28; // collapsed button width (px)
|
|
|
282
291
|
const H_PAD = 10; // horizontal padding when expanded (px) — matches px-2.5
|
|
283
292
|
const GAP = 6; // gap between icon and label (px) — matches gap-1.5
|
|
284
293
|
const ICON_INNER = 14; // icon element width (px)
|
|
294
|
+
const COLLAPSED_PAD = (ICON_SIZE - ICON_INNER) / 2;
|
|
285
295
|
const DURATION = 180; // transition ms
|
|
296
|
+
const EASE = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)';
|
|
286
297
|
|
|
287
298
|
/* ─── Button ─── */
|
|
288
299
|
|
|
300
|
+
/**
|
|
301
|
+
* One toolstrip button: the icon, and a label that slides out beside it.
|
|
302
|
+
*
|
|
303
|
+
* The width comes from layout, never from a JS measurement. A hidden copy of
|
|
304
|
+
* the label sits in a one-column grid whose column animates `0fr` -> `1fr`, so
|
|
305
|
+
* the button's shrink-to-fit width runs from the icon-only 28 px to the icon,
|
|
306
|
+
* the gap and the label; the side padding animates 7 -> 10 px with it. The
|
|
307
|
+
* width at every frame matches the explicit-width version this replaced
|
|
308
|
+
* (28 + (12 + label) x progress, same 180 ms curve). The visible label is
|
|
309
|
+
* positioned beside the icon and clipped by the button's own edge, as before:
|
|
310
|
+
* a grid track laid out at a fraction of its width reveals its content more
|
|
311
|
+
* slowly than the button grows, so the label is not drawn inside it.
|
|
312
|
+
* Reading `offsetWidth` per button forced a layout on every document open.
|
|
313
|
+
*/
|
|
289
314
|
const ToolstripButton: React.FC<{
|
|
290
315
|
active: boolean;
|
|
291
316
|
onClick: () => void;
|
|
@@ -297,18 +322,9 @@ const ToolstripButton: React.FC<{
|
|
|
297
322
|
iconOnly?: boolean;
|
|
298
323
|
}> = ({ active, onClick, icon, label, color, mounted, compact = false, iconOnly = false }) => {
|
|
299
324
|
const [hovered, setHovered] = useState(false);
|
|
300
|
-
const [labelWidth, setLabelWidth] = useState(0);
|
|
301
|
-
const measureRef = useRef<HTMLSpanElement>(null);
|
|
302
325
|
const styles = colorStyles[color];
|
|
303
326
|
const [isTouchDevice] = useState(() => 'ontouchstart' in window || navigator.maxTouchPoints > 0);
|
|
304
327
|
|
|
305
|
-
// Measure label text width synchronously before first paint
|
|
306
|
-
useLayoutEffect(() => {
|
|
307
|
-
if (measureRef.current) {
|
|
308
|
-
setLabelWidth(measureRef.current.offsetWidth);
|
|
309
|
-
}
|
|
310
|
-
}, [label]);
|
|
311
|
-
|
|
312
328
|
// iconOnly: never expand (mobile sticky lane).
|
|
313
329
|
// compact: only active expands (sm+ sticky lane — shows current mode).
|
|
314
330
|
// default: active or hovered expands (top-of-doc full toolstrip).
|
|
@@ -317,8 +333,7 @@ const ToolstripButton: React.FC<{
|
|
|
317
333
|
: compact
|
|
318
334
|
? active
|
|
319
335
|
: (active || hovered || isTouchDevice);
|
|
320
|
-
const
|
|
321
|
-
const currentWidth = expanded ? expandedWidth : ICON_SIZE;
|
|
336
|
+
const pad = expanded ? H_PAD : COLLAPSED_PAD;
|
|
322
337
|
|
|
323
338
|
const colorClass = active
|
|
324
339
|
? styles.active
|
|
@@ -326,12 +341,9 @@ const ToolstripButton: React.FC<{
|
|
|
326
341
|
? styles.hover
|
|
327
342
|
: styles.inactive;
|
|
328
343
|
|
|
344
|
+
const slide = (property: string) => `${property} ${DURATION}ms ${EASE}`;
|
|
329
345
|
const transition = mounted
|
|
330
|
-
?
|
|
331
|
-
: 'none';
|
|
332
|
-
|
|
333
|
-
const innerTransition = mounted
|
|
334
|
-
? `padding-left ${DURATION}ms cubic-bezier(0.25, 0.46, 0.45, 0.94)`
|
|
346
|
+
? `${slide('padding-inline-start')}, ${slide('padding-inline-end')}, background-color ${DURATION}ms ease, color ${DURATION}ms ease, box-shadow ${DURATION}ms ease`
|
|
335
347
|
: 'none';
|
|
336
348
|
|
|
337
349
|
return (
|
|
@@ -340,34 +352,36 @@ const ToolstripButton: React.FC<{
|
|
|
340
352
|
onMouseEnter={() => setHovered(true)}
|
|
341
353
|
onMouseLeave={() => setHovered(false)}
|
|
342
354
|
aria-pressed={active}
|
|
343
|
-
className={`relative flex items-center h-7 rounded-md overflow-hidden ${colorClass}`}
|
|
344
|
-
style={{
|
|
355
|
+
className={`relative flex items-center h-7 rounded-md overflow-hidden whitespace-nowrap ${colorClass}`}
|
|
356
|
+
style={{ paddingInlineStart: pad, paddingInlineEnd: pad, transition }}
|
|
345
357
|
>
|
|
346
|
-
{
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
{icon}
|
|
359
|
+
{/* The label people see, beside the icon, clipped at the button's edge. */}
|
|
360
|
+
<span
|
|
361
|
+
className="absolute inset-y-0 flex items-center text-xs font-medium"
|
|
362
|
+
style={{
|
|
363
|
+
insetInlineStart: pad + ICON_INNER + GAP,
|
|
364
|
+
opacity: expanded ? 1 : 0,
|
|
365
|
+
transition: mounted
|
|
366
|
+
? `${slide('inset-inline-start')}, opacity ${expanded ? DURATION : DURATION * 0.6}ms ease ${expanded ? '60ms' : '0ms'}`
|
|
367
|
+
: 'none',
|
|
368
|
+
}}
|
|
350
369
|
>
|
|
351
|
-
{
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
opacity: expanded ? 1 : 0,
|
|
356
|
-
transition: mounted ? `opacity ${expanded ? DURATION : DURATION * 0.6}ms ease ${expanded ? '60ms' : '0ms'}` : 'none',
|
|
357
|
-
}}
|
|
358
|
-
>
|
|
359
|
-
{label}
|
|
360
|
-
</span>
|
|
361
|
-
</div>
|
|
362
|
-
|
|
363
|
-
{/* Hidden measurement span — rendered offscreen to get label pixel width */}
|
|
370
|
+
{label}
|
|
371
|
+
</span>
|
|
372
|
+
{/* The width: a hidden copy of the gap and label in a column that
|
|
373
|
+
animates 0fr -> 1fr. min-width 0 and overflow hidden let it reach 0. */}
|
|
364
374
|
<span
|
|
365
|
-
ref={measureRef}
|
|
366
|
-
className="text-xs font-medium absolute pointer-events-none"
|
|
367
|
-
style={{ visibility: 'hidden', position: 'absolute', left: -9999 }}
|
|
368
375
|
aria-hidden
|
|
376
|
+
style={{
|
|
377
|
+
display: 'grid',
|
|
378
|
+
gridTemplateColumns: expanded ? '1fr' : '0fr',
|
|
379
|
+
transition: mounted ? slide('grid-template-columns') : 'none',
|
|
380
|
+
}}
|
|
369
381
|
>
|
|
370
|
-
{
|
|
382
|
+
<span style={{ minWidth: 0, overflow: 'hidden', visibility: 'hidden' }}>
|
|
383
|
+
<span className="block text-xs font-medium" style={{ paddingInlineStart: GAP }}>{label}</span>
|
|
384
|
+
</span>
|
|
371
385
|
</span>
|
|
372
386
|
</button>
|
|
373
387
|
);
|
|
@@ -6,6 +6,7 @@ import { CodeBlock } from "./blocks/CodeBlock";
|
|
|
6
6
|
import { HtmlBlock } from "./blocks/HtmlBlock";
|
|
7
7
|
import { Callout } from "./blocks/Callout";
|
|
8
8
|
import { AlertBlock } from "./blocks/AlertBlock";
|
|
9
|
+
import { BlockquoteBlock } from "./blocks/BlockquoteBlock";
|
|
9
10
|
import { TableBlock } from "./blocks/TableBlock";
|
|
10
11
|
import { MathBlock } from "./blocks/MathBlock";
|
|
11
12
|
import { QuestionBlock, type QuestionBlockProps } from "./blocks/QuestionBlock";
|
|
@@ -61,7 +62,11 @@ export const BlockRenderer: React.FC<{
|
|
|
61
62
|
onOpenQuestionDecision?: QuestionBlockProps['onOpenDecision'];
|
|
62
63
|
/** `'none'` hides the card's own status tag (see `QuestionBlock`). */
|
|
63
64
|
questionStatusTag?: QuestionBlockProps['statusTag'];
|
|
64
|
-
|
|
65
|
+
/** The host's words for a question's status tag (see `QuestionBlock`). */
|
|
66
|
+
questionStatusLabel?: QuestionBlockProps['statusLabel'];
|
|
67
|
+
/** Host content under a question's prompt (see `QuestionBlock`). */
|
|
68
|
+
renderQuestionBelowPrompt?: QuestionBlockProps['renderBelowPrompt'];
|
|
69
|
+
}> = ({ block, onOpenLinkedDoc, onOpenCodeFile, imageBaseDir, onImageClick, onToggleCheckbox, checkboxOverrides, orderedIndex, githubRepo, repoHost, headingAnchorId, onNavigateAnchor, question, questionTotal, questionAnswer, onAnswerQuestion, onSaveQuestionAnswer, saveQuestionAnswerLabel, renderQuestionFooter, questionDecisionRecording, questionDecisionScope, onToggleQuestionDecisionRecording, onOpenQuestionDecision, questionStatusTag, questionStatusLabel, renderQuestionBelowPrompt }) => {
|
|
65
70
|
switch (block.type) {
|
|
66
71
|
case 'heading': {
|
|
67
72
|
const Tag = `h${block.level || 1}` as React.ElementType;
|
|
@@ -99,20 +104,19 @@ export const BlockRenderer: React.FC<{
|
|
|
99
104
|
/>
|
|
100
105
|
);
|
|
101
106
|
}
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
const paragraphs = block.content.split(/\n\n+/);
|
|
107
|
+
// One quote whatever it holds: its content is the quote's own markdown
|
|
108
|
+
// (paragraphs, code fences, lists, nested quotes), see BlockquoteBlock.
|
|
105
109
|
return (
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
<BlockquoteBlock
|
|
111
|
+
block={block}
|
|
112
|
+
onOpenLinkedDoc={onOpenLinkedDoc}
|
|
113
|
+
onOpenCodeFile={onOpenCodeFile}
|
|
114
|
+
imageBaseDir={imageBaseDir}
|
|
115
|
+
onImageClick={onImageClick}
|
|
116
|
+
githubRepo={githubRepo}
|
|
117
|
+
repoHost={repoHost}
|
|
118
|
+
onNavigateAnchor={onNavigateAnchor}
|
|
119
|
+
/>
|
|
116
120
|
);
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -193,6 +197,8 @@ export const BlockRenderer: React.FC<{
|
|
|
193
197
|
onToggleDecisionRecording={onToggleQuestionDecisionRecording}
|
|
194
198
|
onOpenDecision={onOpenQuestionDecision}
|
|
195
199
|
statusTag={questionStatusTag}
|
|
200
|
+
statusLabel={questionStatusLabel}
|
|
201
|
+
renderBelowPrompt={renderQuestionBelowPrompt}
|
|
196
202
|
onOpenLinkedDoc={onOpenLinkedDoc}
|
|
197
203
|
onOpenCodeFile={onOpenCodeFile}
|
|
198
204
|
imageBaseDir={imageBaseDir}
|
|
@@ -92,7 +92,7 @@ function buildPierreCSS(mode: 'dark' | 'light', bg: string, fg: string): string
|
|
|
92
92
|
pre, code { background-color: ${bg} !important; }
|
|
93
93
|
[data-column-number] { background-color: ${bg} !important; }
|
|
94
94
|
[data-file] { height: 100% !important; }
|
|
95
|
-
[data-code] { height: 100% !important; overflow-y: auto !important; }
|
|
95
|
+
[data-code] { height: 100% !important; overflow-y: auto !important; align-content: start; }
|
|
96
96
|
[data-line] { cursor: pointer; }
|
|
97
97
|
`;
|
|
98
98
|
}
|
|
@@ -180,7 +180,7 @@ const CodeInlineAnnotation: React.FC<{
|
|
|
180
180
|
<span className="rounded bg-muted px-1.5 py-0.5 font-mono normal-case text-foreground">
|
|
181
181
|
{lineLabel(annotation.lineStart, annotation.lineEnd)}
|
|
182
182
|
</span>
|
|
183
|
-
{annotation.author && <span className="truncate normal-case">by {annotation.author}</span>}
|
|
183
|
+
{annotation.author && <span className="truncate normal-case">by <bdi>{annotation.author}</bdi></span>}
|
|
184
184
|
<div className="ml-auto flex items-center gap-1 opacity-0 group-hover:opacity-100 [@media(hover:none)]:opacity-100">
|
|
185
185
|
{onEdit && !isEditing && (
|
|
186
186
|
<button
|
|
@@ -283,21 +283,46 @@ const CodeInlineAnnotation: React.FC<{
|
|
|
283
283
|
);
|
|
284
284
|
};
|
|
285
285
|
|
|
286
|
-
export
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
export interface CodeFileViewProps {
|
|
287
|
+
filepath: string;
|
|
288
|
+
contents: string;
|
|
289
|
+
prerenderedHTML?: string;
|
|
290
|
+
annotations?: CodeAnnotation[];
|
|
291
|
+
selectedAnnotationId?: string | null;
|
|
292
|
+
onAddAnnotation?: (annotation: CodeFileAnnotationInput) => void;
|
|
293
|
+
onEditAnnotation?: (id: string, updates: Partial<CodeAnnotation>) => void;
|
|
294
|
+
onDeleteAnnotation?: (id: string) => void;
|
|
295
|
+
onSelectAnnotation?: (id: string | null) => void;
|
|
296
|
+
/** Whether the comment composer offers image attachments. Default true (the popout's behavior). */
|
|
297
|
+
allowImages?: boolean;
|
|
298
|
+
/** The file area's class. Default: fills the rest of a flex column. */
|
|
299
|
+
className?: string;
|
|
300
|
+
/** The highlighting language, instead of Pierre's inference from the name (`'text'`: no highlighting, for a very large file). */
|
|
301
|
+
lang?: string;
|
|
302
|
+
/** Long lines: `'scroll'` sideways (the default) or `'wrap'` onto the next row, for a minified file. */
|
|
303
|
+
overflow?: 'scroll' | 'wrap';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* One code file drawn by Pierre's `File`, with line and range comments: the
|
|
308
|
+
* gutter `+`, a click on a line, a dragged range or a text selection opens
|
|
309
|
+
* the comment composer, and saved comments render inline under their lines.
|
|
310
|
+
* The body of `CodeFilePopout`, usable on its own (the Inbox's file pane).
|
|
311
|
+
*/
|
|
312
|
+
export const CodeFileView: React.FC<CodeFileViewProps> = ({
|
|
289
313
|
filepath,
|
|
290
314
|
contents,
|
|
291
315
|
prerenderedHTML,
|
|
292
|
-
error,
|
|
293
|
-
requestedPath,
|
|
294
316
|
annotations = [],
|
|
295
317
|
selectedAnnotationId,
|
|
296
318
|
onAddAnnotation,
|
|
297
319
|
onEditAnnotation,
|
|
298
320
|
onDeleteAnnotation,
|
|
299
321
|
onSelectAnnotation,
|
|
300
|
-
|
|
322
|
+
allowImages = true,
|
|
323
|
+
className = 'flex-1 min-h-0',
|
|
324
|
+
lang,
|
|
325
|
+
overflow = 'scroll',
|
|
301
326
|
}) => {
|
|
302
327
|
const { resolvedMode } = useTheme();
|
|
303
328
|
const mode = resolvedMode ?? 'dark';
|
|
@@ -306,7 +331,6 @@ export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
|
|
|
306
331
|
type: mode as 'dark' | 'light',
|
|
307
332
|
css: buildPierreCSS(mode, colors.bg, colors.fg),
|
|
308
333
|
}));
|
|
309
|
-
const [copied, setCopied] = useState(false);
|
|
310
334
|
const [pendingComment, setPendingComment] = useState<PendingComment | null>(null);
|
|
311
335
|
const fileAreaRef = useRef<HTMLDivElement>(null);
|
|
312
336
|
const lastPointerRectRef = useRef<DOMRect | null>(null);
|
|
@@ -328,7 +352,6 @@ export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
|
|
|
328
352
|
|
|
329
353
|
const displayName = filepath.split('/').pop() || filepath;
|
|
330
354
|
const relativePath = filepath.replace(/.*\/(?=.*\/)/, '');
|
|
331
|
-
const lineCount = useMemo(() => contents.split('\n').length, [contents]);
|
|
332
355
|
const selectedCodeAnnotation = useMemo(
|
|
333
356
|
() => annotations.find((ann) => ann.id === selectedAnnotationId),
|
|
334
357
|
[annotations, selectedAnnotationId],
|
|
@@ -457,6 +480,94 @@ export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
|
|
|
457
480
|
);
|
|
458
481
|
}, [onAddAnnotation, openCommentForRange]);
|
|
459
482
|
|
|
483
|
+
return (
|
|
484
|
+
<>
|
|
485
|
+
<div
|
|
486
|
+
ref={fileAreaRef}
|
|
487
|
+
className={className}
|
|
488
|
+
onPointerMove={(e) => {
|
|
489
|
+
lastPointerRectRef.current = new DOMRect(e.clientX, e.clientY, 0, 0);
|
|
490
|
+
}}
|
|
491
|
+
onMouseUp={() => {
|
|
492
|
+
requestAnimationFrame(openCommentForBrowserSelection);
|
|
493
|
+
}}
|
|
494
|
+
>
|
|
495
|
+
<File
|
|
496
|
+
key={filepath}
|
|
497
|
+
file={lang ? { name: displayName, contents, lang } : { name: displayName, contents }}
|
|
498
|
+
prerenderedHTML={effectivePrerenderedHTML}
|
|
499
|
+
className="h-full"
|
|
500
|
+
lineAnnotations={lineAnnotations}
|
|
501
|
+
selectedLines={selectedLines}
|
|
502
|
+
renderAnnotation={renderAnnotation}
|
|
503
|
+
renderGutterUtility={renderGutterUtility}
|
|
504
|
+
style={{
|
|
505
|
+
'--diffs-dark-bg': colors.bg,
|
|
506
|
+
'--diffs-light-bg': colors.bg,
|
|
507
|
+
'--diffs-dark': colors.fg,
|
|
508
|
+
'--diffs-light': colors.fg,
|
|
509
|
+
} as React.CSSProperties}
|
|
510
|
+
options={{
|
|
511
|
+
themeType: pierreTheme.type,
|
|
512
|
+
unsafeCSS: pierreTheme.css,
|
|
513
|
+
overflow,
|
|
514
|
+
disableFileHeader: true,
|
|
515
|
+
enableLineSelection: true,
|
|
516
|
+
enableGutterUtility: !!onAddAnnotation,
|
|
517
|
+
lineHoverHighlight: onAddAnnotation ? 'line' : 'disabled',
|
|
518
|
+
onLineClick: handleLineClick,
|
|
519
|
+
onLineSelectionEnd: handleLineSelectionEnd,
|
|
520
|
+
}}
|
|
521
|
+
/>
|
|
522
|
+
</div>
|
|
523
|
+
{pendingComment && onAddAnnotation && (
|
|
524
|
+
<CommentPopover
|
|
525
|
+
anchorEl={pendingComment.anchorEl}
|
|
526
|
+
anchorRect={pendingComment.anchorRect}
|
|
527
|
+
contextText={pendingComment.contextText}
|
|
528
|
+
isGlobal={false}
|
|
529
|
+
skillReferences
|
|
530
|
+
allowImages={allowImages}
|
|
531
|
+
onSubmit={(text, images) => {
|
|
532
|
+
onAddAnnotation({
|
|
533
|
+
filePath: filepath,
|
|
534
|
+
lineStart: pendingComment.range.start,
|
|
535
|
+
lineEnd: pendingComment.range.end,
|
|
536
|
+
text,
|
|
537
|
+
images,
|
|
538
|
+
originalCode: pendingComment.originalCode,
|
|
539
|
+
});
|
|
540
|
+
setPendingComment(null);
|
|
541
|
+
}}
|
|
542
|
+
onClose={() => setPendingComment(null)}
|
|
543
|
+
/>
|
|
544
|
+
)}
|
|
545
|
+
</>
|
|
546
|
+
);
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
|
|
550
|
+
open,
|
|
551
|
+
onClose,
|
|
552
|
+
filepath,
|
|
553
|
+
contents,
|
|
554
|
+
prerenderedHTML,
|
|
555
|
+
error,
|
|
556
|
+
requestedPath,
|
|
557
|
+
annotations = [],
|
|
558
|
+
selectedAnnotationId,
|
|
559
|
+
onAddAnnotation,
|
|
560
|
+
onEditAnnotation,
|
|
561
|
+
onDeleteAnnotation,
|
|
562
|
+
onSelectAnnotation,
|
|
563
|
+
container,
|
|
564
|
+
}) => {
|
|
565
|
+
const [copied, setCopied] = useState(false);
|
|
566
|
+
|
|
567
|
+
const displayName = filepath.split('/').pop() || filepath;
|
|
568
|
+
const relativePath = filepath.replace(/.*\/(?=.*\/)/, '');
|
|
569
|
+
const lineCount = useMemo(() => contents.split('\n').length, [contents]);
|
|
570
|
+
|
|
460
571
|
const handleCopy = async () => {
|
|
461
572
|
if (await copyTextToClipboard(contents)) {
|
|
462
573
|
setCopied(true);
|
|
@@ -539,65 +650,17 @@ export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
|
|
|
539
650
|
</div>
|
|
540
651
|
|
|
541
652
|
<div className="border-t border-border/30" />
|
|
542
|
-
<
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
key={filepath}
|
|
554
|
-
file={{ name: displayName, contents }}
|
|
555
|
-
prerenderedHTML={effectivePrerenderedHTML}
|
|
556
|
-
className="h-full"
|
|
557
|
-
lineAnnotations={lineAnnotations}
|
|
558
|
-
selectedLines={selectedLines}
|
|
559
|
-
renderAnnotation={renderAnnotation}
|
|
560
|
-
renderGutterUtility={renderGutterUtility}
|
|
561
|
-
style={{
|
|
562
|
-
'--diffs-dark-bg': colors.bg,
|
|
563
|
-
'--diffs-light-bg': colors.bg,
|
|
564
|
-
'--diffs-dark': colors.fg,
|
|
565
|
-
'--diffs-light': colors.fg,
|
|
566
|
-
} as React.CSSProperties}
|
|
567
|
-
options={{
|
|
568
|
-
themeType: pierreTheme.type,
|
|
569
|
-
unsafeCSS: pierreTheme.css,
|
|
570
|
-
overflow: 'scroll',
|
|
571
|
-
disableFileHeader: true,
|
|
572
|
-
enableLineSelection: true,
|
|
573
|
-
enableGutterUtility: !!onAddAnnotation,
|
|
574
|
-
lineHoverHighlight: onAddAnnotation ? 'line' : 'disabled',
|
|
575
|
-
onLineClick: handleLineClick,
|
|
576
|
-
onLineSelectionEnd: handleLineSelectionEnd,
|
|
577
|
-
}}
|
|
578
|
-
/>
|
|
579
|
-
</div>
|
|
580
|
-
{pendingComment && onAddAnnotation && (
|
|
581
|
-
<CommentPopover
|
|
582
|
-
anchorEl={pendingComment.anchorEl}
|
|
583
|
-
anchorRect={pendingComment.anchorRect}
|
|
584
|
-
contextText={pendingComment.contextText}
|
|
585
|
-
isGlobal={false}
|
|
586
|
-
skillReferences
|
|
587
|
-
onSubmit={(text, images) => {
|
|
588
|
-
onAddAnnotation({
|
|
589
|
-
filePath: filepath,
|
|
590
|
-
lineStart: pendingComment.range.start,
|
|
591
|
-
lineEnd: pendingComment.range.end,
|
|
592
|
-
text,
|
|
593
|
-
images,
|
|
594
|
-
originalCode: pendingComment.originalCode,
|
|
595
|
-
});
|
|
596
|
-
setPendingComment(null);
|
|
597
|
-
}}
|
|
598
|
-
onClose={() => setPendingComment(null)}
|
|
599
|
-
/>
|
|
600
|
-
)}
|
|
653
|
+
<CodeFileView
|
|
654
|
+
filepath={filepath}
|
|
655
|
+
contents={contents}
|
|
656
|
+
prerenderedHTML={prerenderedHTML}
|
|
657
|
+
annotations={annotations}
|
|
658
|
+
selectedAnnotationId={selectedAnnotationId}
|
|
659
|
+
onAddAnnotation={onAddAnnotation}
|
|
660
|
+
onEditAnnotation={onEditAnnotation}
|
|
661
|
+
onDeleteAnnotation={onDeleteAnnotation}
|
|
662
|
+
onSelectAnnotation={onSelectAnnotation}
|
|
663
|
+
/>
|
|
601
664
|
</PopoutDialog>
|
|
602
665
|
);
|
|
603
666
|
};
|