@plannotator/ui 0.22.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/README.md +60 -0
- package/assets/diff-options.png +0 -0
- package/assets/icon-codex.png +0 -0
- package/assets/look-flat.png +0 -0
- package/assets/look-grid.png +0 -0
- package/assets/review-sections.png +0 -0
- package/assets/review-tree.png +0 -0
- package/assets/workspaces.webp +0 -0
- package/components/AISettingsTab.tsx +145 -0
- package/components/ActionMenu.tsx +101 -0
- package/components/AgentControls.tsx +216 -0
- package/components/AgentsTab.tsx +1294 -0
- package/components/AnnotationPanel.tsx +731 -0
- package/components/AnnotationSidebar.tsx +86 -0
- package/components/AnnotationToolbar.tsx +323 -0
- package/components/AnnotationToolstrip.tsx +355 -0
- package/components/ApproveDropdown.tsx +170 -0
- package/components/AttachmentsButton.tsx +410 -0
- package/components/BlockRenderer.tsx +163 -0
- package/components/BorderTrail.tsx +38 -0
- package/components/CodeFilePicker.tsx +64 -0
- package/components/CodeFilePopout.tsx +602 -0
- package/components/CodePathValidationContext.tsx +14 -0
- package/components/CommentPopover.tsx +512 -0
- package/components/CompletionOverlay.tsx +99 -0
- package/components/ConfirmDialog.tsx +128 -0
- package/components/DocBadges.tsx +231 -0
- package/components/EditorAnnotationCard.tsx +65 -0
- package/components/ExportModal.tsx +565 -0
- package/components/FloatingQuickLabelPicker.tsx +144 -0
- package/components/GitHubIcon.tsx +16 -0
- package/components/GitLabIcon.tsx +30 -0
- package/components/GraphvizBlock.tsx +511 -0
- package/components/ImageAnnotator/Canvas.tsx +118 -0
- package/components/ImageAnnotator/Toolbar.tsx +218 -0
- package/components/ImageAnnotator/index.tsx +272 -0
- package/components/ImageAnnotator/types.ts +39 -0
- package/components/ImageAnnotator/utils.ts +147 -0
- package/components/ImageThumbnail.tsx +126 -0
- package/components/ImportModal.tsx +146 -0
- package/components/InlineMarkdown.tsx +1094 -0
- package/components/KeyboardShortcuts.tsx +208 -0
- package/components/Landing.tsx +496 -0
- package/components/ListItemBody.tsx +62 -0
- package/components/ListMarker.tsx +73 -0
- package/components/LookAndFeelAnnouncementDialog.tsx +230 -0
- package/components/MarkdownEditor.tsx +44 -0
- package/components/MenuVersionSection.tsx +91 -0
- package/components/MermaidBlock.tsx +579 -0
- package/components/ModeToggle.tsx +68 -0
- package/components/OpenInAppButton.tsx +299 -0
- package/components/OverlayScrollArea.tsx +82 -0
- package/components/PermissionModeSetup.tsx +92 -0
- package/components/PinpointOverlay.tsx +99 -0
- package/components/PlanAIAnnouncementDialog.tsx +187 -0
- package/components/PlanHeaderMenu.tsx +299 -0
- package/components/PopoutDialog.tsx +91 -0
- package/components/Popover.tsx +27 -0
- package/components/ProviderIcons.tsx +51 -0
- package/components/PullRequestIcon.tsx +12 -0
- package/components/QuickLabelDropdown.tsx +63 -0
- package/components/RenderedMarkdown.tsx +54 -0
- package/components/RepoIcon.tsx +12 -0
- package/components/ResizeHandle.tsx +86 -0
- package/components/ReviewAgentsIcon.tsx +18 -0
- package/components/SearchableSelect.tsx +157 -0
- package/components/Settings.tsx +2220 -0
- package/components/SparklesIcon.tsx +45 -0
- package/components/StickyHeaderLane.tsx +260 -0
- package/components/TableOfContents.tsx +157 -0
- package/components/TaterSpritePullup.tsx +34 -0
- package/components/TaterSpriteRunning.tsx +54 -0
- package/components/TaterSpriteSitting.tsx +37 -0
- package/components/TextShimmer.tsx +57 -0
- package/components/ThemeProvider.tsx +162 -0
- package/components/ThemeTab.tsx +139 -0
- package/components/ToolbarButtons.tsx +116 -0
- package/components/Tooltip.tsx +45 -0
- package/components/Viewer.tsx +977 -0
- package/components/ai/AIProviderBar.tsx +95 -0
- package/components/ai/DocumentAIChatPanel.tsx +313 -0
- package/components/blocks/AlertBlock.tsx +58 -0
- package/components/blocks/Callout.tsx +60 -0
- package/components/blocks/CodeBlock.tsx +75 -0
- package/components/blocks/HtmlBlock.tsx +122 -0
- package/components/blocks/MathBlock.tsx +36 -0
- package/components/blocks/TableBlock.tsx +146 -0
- package/components/blocks/TablePopout.tsx +279 -0
- package/components/blocks/TableToolbar.tsx +153 -0
- package/components/blocks/proseBody.tsx +102 -0
- package/components/core/button.tsx +44 -0
- package/components/core/textarea.tsx +26 -0
- package/components/diagramLanguages.ts +14 -0
- package/components/goal-setup/GoalSetupSurface.tsx +1354 -0
- package/components/html-viewer/HtmlViewer.tsx +386 -0
- package/components/html-viewer/bridge-script.ts +505 -0
- package/components/html-viewer/index.ts +1 -0
- package/components/html-viewer/useHtmlAnnotation.ts +400 -0
- package/components/icons/AgentIcons.tsx +66 -0
- package/components/icons/AppIcon.tsx +56 -0
- package/components/icons/MessagesIcon.tsx +11 -0
- package/components/icons/ObsidianIcons.tsx +208 -0
- package/components/icons/app/android-studio.svg +369 -0
- package/components/icons/app/antigravity.svg +97 -0
- package/components/icons/app/cursor.svg +16 -0
- package/components/icons/app/file-explorer.svg +20 -0
- package/components/icons/app/finder.png +0 -0
- package/components/icons/app/ghostty.svg +13 -0
- package/components/icons/app/iterm2.svg +13 -0
- package/components/icons/app/powershell.svg +14 -0
- package/components/icons/app/sublime-text.svg +17 -0
- package/components/icons/app/terminal.png +0 -0
- package/components/icons/app/textmate.png +0 -0
- package/components/icons/app/vscode.svg +39 -0
- package/components/icons/app/warp.png +0 -0
- package/components/icons/app/xcode.png +0 -0
- package/components/icons/app/zed-dark.svg +15 -0
- package/components/icons/app/zed.svg +15 -0
- package/components/icons/themeIcons.tsx +47 -0
- package/components/mermaidSvg.ts +33 -0
- package/components/plan-diff/PlanCleanDiffView.tsx +884 -0
- package/components/plan-diff/PlanDiffBadge.tsx +48 -0
- package/components/plan-diff/PlanDiffModeSwitcher.tsx +103 -0
- package/components/plan-diff/PlanDiffViewer.tsx +214 -0
- package/components/plan-diff/PlanRawDiffView.tsx +102 -0
- package/components/plan-diff/VSCodeIcon.tsx +132 -0
- package/components/settings/HooksTab.tsx +208 -0
- package/components/sidebar/ArchiveBrowser.tsx +98 -0
- package/components/sidebar/CountBadge.tsx +12 -0
- package/components/sidebar/FileBrowser.tsx +498 -0
- package/components/sidebar/MessagesBrowser.tsx +109 -0
- package/components/sidebar/SidebarContainer.tsx +353 -0
- package/components/sidebar/SidebarTabs.tsx +155 -0
- package/components/sidebar/VersionBrowser.tsx +141 -0
- package/components/types.d.ts +7 -0
- package/components/ui/badge.tsx +44 -0
- package/components/ui/button.tsx +83 -0
- package/components/ui/card.tsx +58 -0
- package/components/ui/dialog.tsx +106 -0
- package/components/ui/dropdown-menu.tsx +240 -0
- package/components/ui/state-pill.tsx +45 -0
- package/components/ui/tabs.tsx +46 -0
- package/components/ui/textarea.tsx +25 -0
- package/config/configStore.ts +214 -0
- package/config/index.ts +4 -0
- package/config/reviewView.ts +42 -0
- package/config/settings.ts +310 -0
- package/config/useConfig.ts +20 -0
- package/configure.ts +69 -0
- package/globals.d.ts +14 -0
- package/hooks/pfm/useCodeFilePopout.ts +111 -0
- package/hooks/useAIChat.ts +532 -0
- package/hooks/useAIProviderConfig.ts +115 -0
- package/hooks/useActiveSection.ts +78 -0
- package/hooks/useAgentJobs.ts +306 -0
- package/hooks/useAgentSettings.ts +579 -0
- package/hooks/useAgents.ts +90 -0
- package/hooks/useAnnotationDraft.ts +523 -0
- package/hooks/useAnnotationHighlighter.ts +1170 -0
- package/hooks/useArchive.ts +171 -0
- package/hooks/useAutoClose.ts +104 -0
- package/hooks/useCodeAnnotationDraft.ts +194 -0
- package/hooks/useCodeFilePopout.ts +1 -0
- package/hooks/useDismissOnOutsideAndEscape.ts +50 -0
- package/hooks/useDraggable.ts +108 -0
- package/hooks/useEditorAnnotations.ts +64 -0
- package/hooks/useExternalAnnotationHighlights.ts +105 -0
- package/hooks/useExternalAnnotations.ts +268 -0
- package/hooks/useFileBrowser.ts +402 -0
- package/hooks/useInputMethodSwitch.ts +89 -0
- package/hooks/useIsMobile.ts +17 -0
- package/hooks/useLinkedDoc.ts +494 -0
- package/hooks/useOverlayViewport.ts +36 -0
- package/hooks/usePinpoint.ts +182 -0
- package/hooks/usePlanDiff.ts +176 -0
- package/hooks/usePrintMode.ts +27 -0
- package/hooks/useResizablePanel.ts +173 -0
- package/hooks/useScrollViewport.ts +38 -0
- package/hooks/useSharing.ts +441 -0
- package/hooks/useSidebar.ts +55 -0
- package/hooks/useUpdateCheck.ts +132 -0
- package/hooks/useValidatedCodePaths.ts +94 -0
- package/icons/GitUser.tsx +17 -0
- package/lib/utils.ts +11 -0
- package/package.json +117 -0
- package/plannotator.webp +0 -0
- package/print.css +427 -0
- package/shortcuts/code-review/ai.shortcuts.ts +25 -0
- package/shortcuts/code-review/allFilesDiff.shortcuts.ts +46 -0
- package/shortcuts/code-review/annotationToolbar.shortcuts.ts +30 -0
- package/shortcuts/code-review/fileTree.shortcuts.ts +35 -0
- package/shortcuts/code-review/prComments.shortcuts.ts +18 -0
- package/shortcuts/code-review/suggestionModal.shortcuts.ts +25 -0
- package/shortcuts/code-review/tourDialog.shortcuts.ts +18 -0
- package/shortcuts/core.ts +399 -0
- package/shortcuts/index.ts +21 -0
- package/shortcuts/plan-review/annotationPanel.shortcuts.ts +25 -0
- package/shortcuts/plan-review/annotationToolbar.shortcuts.ts +39 -0
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +21 -0
- package/shortcuts/plan-review/goalSetup.shortcuts.ts +10 -0
- package/shortcuts/plan-review/imageAnnotator.shortcuts.ts +42 -0
- package/shortcuts/plan-review/inputMethod.shortcuts.ts +30 -0
- package/shortcuts/plan-review/sidebar.shortcuts.ts +34 -0
- package/shortcuts/plan-review/viewer.shortcuts.ts +24 -0
- package/shortcuts/runtime.ts +259 -0
- package/sprite_package_additional/index.html +34 -0
- package/sprite_package_additional/sprite.png +0 -0
- package/sprite_package_new/index.html +34 -0
- package/sprite_package_new/sprite.png +0 -0
- package/sprite_package_pulluphang/index.html +34 -0
- package/sprite_package_pulluphang/sprite.png +0 -0
- package/styles.css +1 -0
- package/theme.css +893 -0
- package/themes/adwaita.css +62 -0
- package/themes/andromeeda.css +66 -0
- package/themes/aurora-x.css +66 -0
- package/themes/ayu-dark.css +66 -0
- package/themes/caffeine.css +113 -0
- package/themes/catppuccin.css +62 -0
- package/themes/claude-plus.css +60 -0
- package/themes/cursor-hc.css +34 -0
- package/themes/cursor-midnight.css +34 -0
- package/themes/cursor.css +62 -0
- package/themes/dark-plus.css +66 -0
- package/themes/doom-64.css +109 -0
- package/themes/dracula.css +33 -0
- package/themes/everforest-hard.css +62 -0
- package/themes/everforest-soft.css +62 -0
- package/themes/everforest.css +62 -0
- package/themes/github.css +66 -0
- package/themes/gruvbox.css +62 -0
- package/themes/houston.css +66 -0
- package/themes/kanagawa-dragon.css +34 -0
- package/themes/kanagawa-lotus.css +34 -0
- package/themes/kanagawa-wave.css +34 -0
- package/themes/laserwave.css +66 -0
- package/themes/material.css +62 -0
- package/themes/min.css +66 -0
- package/themes/monokai-pro.css +34 -0
- package/themes/neutral.css +59 -0
- package/themes/night-owl.css +66 -0
- package/themes/nord.css +66 -0
- package/themes/one-dark-pro.css +66 -0
- package/themes/one-light.css +66 -0
- package/themes/paulmillr.css +34 -0
- package/themes/plannotator.css +60 -0
- package/themes/plastic.css +66 -0
- package/themes/poimandres.css +66 -0
- package/themes/quantum-rose.css +109 -0
- package/themes/red.css +66 -0
- package/themes/rose-pine.css +62 -0
- package/themes/simple.css +124 -0
- package/themes/slack.css +66 -0
- package/themes/snazzy-light.css +66 -0
- package/themes/soft-pop.css +60 -0
- package/themes/solar-dusk.css +109 -0
- package/themes/solarized.css +66 -0
- package/themes/synthwave-84.css +34 -0
- package/themes/terminal.css +62 -0
- package/themes/tinacious.css +57 -0
- package/themes/tokyo-night.css +62 -0
- package/themes/vesper.css +62 -0
- package/themes/vitesse-black.css +66 -0
- package/themes/vitesse.css +62 -0
- package/types.ts +264 -0
- package/utils/agentSwitch.ts +71 -0
- package/utils/aiChatFormat.ts +33 -0
- package/utils/aiPrompt.ts +29 -0
- package/utils/aiProvider.ts +217 -0
- package/utils/anchors.ts +11 -0
- package/utils/annotateAgentTerminal.ts +29 -0
- package/utils/annotationHelpers.ts +101 -0
- package/utils/bear.ts +59 -0
- package/utils/blockTargeting.ts +240 -0
- package/utils/callback.ts +99 -0
- package/utils/commentContent.ts +8 -0
- package/utils/defaultNotesApp.ts +20 -0
- package/utils/diffFonts.ts +33 -0
- package/utils/editorMode.ts +31 -0
- package/utils/fileBrowser.ts +40 -0
- package/utils/generateId.ts +7 -0
- package/utils/generateIdentity.ts +29 -0
- package/utils/identity.ts +112 -0
- package/utils/inlineTransforms.ts +41 -0
- package/utils/inputMethod.ts +17 -0
- package/utils/lookAndFeelAnnouncement.ts +18 -0
- package/utils/obsidian.ts +201 -0
- package/utils/octarine.ts +53 -0
- package/utils/parser.ts +991 -0
- package/utils/permissionMode.ts +77 -0
- package/utils/planAIAnnouncement.ts +17 -0
- package/utils/planAgentInstructions.ts +137 -0
- package/utils/planDiffEngine.ts +590 -0
- package/utils/planSave.ts +49 -0
- package/utils/platform.ts +11 -0
- package/utils/quickLabels.ts +77 -0
- package/utils/reviewAgentInstructions.ts +182 -0
- package/utils/sanitizeHtml.ts +45 -0
- package/utils/sharing.ts +350 -0
- package/utils/slugify.ts +37 -0
- package/utils/storage.ts +141 -0
- package/utils/themeRegistry.ts +567 -0
- package/utils/uiPreferences.ts +34 -0
- package/utils/upload.ts +56 -0
- package/utils/wideMode.ts +48 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef, type RefObject } from "react";
|
|
2
|
+
import { AnnotationType, type Annotation, type EditorMode, type ImageAttachment } from "../../types";
|
|
3
|
+
import type { QuickLabel } from "../../utils/quickLabels";
|
|
4
|
+
import { getIdentity } from "../../utils/identity";
|
|
5
|
+
import type {
|
|
6
|
+
ToolbarState,
|
|
7
|
+
CommentPopoverState,
|
|
8
|
+
QuickLabelPickerState,
|
|
9
|
+
UseAnnotationHighlighterReturn,
|
|
10
|
+
} from "../../hooks/useAnnotationHighlighter";
|
|
11
|
+
|
|
12
|
+
const PREFIX = "plannotator-bridge-";
|
|
13
|
+
|
|
14
|
+
// Collision-proof annotation ids. `Date.now()` alone repeats within a millisecond,
|
|
15
|
+
// so two quick annotations could share a data-bind-id and clobber each other.
|
|
16
|
+
let htmlAnnSeq = 0;
|
|
17
|
+
function nextHtmlAnnId(): string {
|
|
18
|
+
return `html-ann-${Date.now().toString(36)}-${(htmlAnnSeq++).toString(36)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface BridgeSelectionMessage {
|
|
22
|
+
type: `${typeof PREFIX}selection`;
|
|
23
|
+
text: string;
|
|
24
|
+
rect: { top: number; left: number; width: number; height: number };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface BridgeMarkClickMessage {
|
|
28
|
+
type: `${typeof PREFIX}mark-click`;
|
|
29
|
+
id: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface BridgeResizeMessage {
|
|
33
|
+
type: `${typeof PREFIX}resize`;
|
|
34
|
+
height: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type BridgeMessage = BridgeSelectionMessage | BridgeMarkClickMessage | BridgeResizeMessage | { type: string };
|
|
38
|
+
|
|
39
|
+
export interface UseHtmlAnnotationOptions {
|
|
40
|
+
iframeRef: RefObject<HTMLIFrameElement | null>;
|
|
41
|
+
annotations: Annotation[];
|
|
42
|
+
onAddAnnotation?: (ann: Annotation) => void;
|
|
43
|
+
onSelectAnnotation?: (id: string | null) => void;
|
|
44
|
+
selectedAnnotationId: string | null;
|
|
45
|
+
mode: EditorMode;
|
|
46
|
+
onResize?: (height: number) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function postToIframe(iframe: HTMLIFrameElement | null, msg: Record<string, unknown>) {
|
|
50
|
+
iframe?.contentWindow?.postMessage(msg, "*");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useHtmlAnnotation({
|
|
54
|
+
iframeRef,
|
|
55
|
+
annotations,
|
|
56
|
+
onAddAnnotation,
|
|
57
|
+
onSelectAnnotation,
|
|
58
|
+
selectedAnnotationId,
|
|
59
|
+
mode,
|
|
60
|
+
onResize,
|
|
61
|
+
}: UseHtmlAnnotationOptions): Omit<UseAnnotationHighlighterReturn, "highlighterRef"> {
|
|
62
|
+
const [toolbarState, setToolbarState] = useState<ToolbarState | null>(null);
|
|
63
|
+
const [commentPopover, setCommentPopover] = useState<CommentPopoverState | null>(null);
|
|
64
|
+
const [quickLabelPicker, setQuickLabelPicker] = useState<QuickLabelPickerState | null>(null);
|
|
65
|
+
|
|
66
|
+
const pendingTextRef = useRef<string>("");
|
|
67
|
+
const modeRef = useRef(mode);
|
|
68
|
+
modeRef.current = mode;
|
|
69
|
+
// Mirror toolbar visibility into a ref so the (stable) message handler can gate
|
|
70
|
+
// type-to-comment on "the markup toolbar is showing", like AnnotationToolbar does.
|
|
71
|
+
const toolbarStateRef = useRef(toolbarState);
|
|
72
|
+
toolbarStateRef.current = toolbarState;
|
|
73
|
+
// Mirror the open comment/quick-label state so the selection-clear handler can
|
|
74
|
+
// tell whether the user is mid-compose and must keep the captured text alive.
|
|
75
|
+
const commentPopoverRef = useRef(commentPopover);
|
|
76
|
+
commentPopoverRef.current = commentPopover;
|
|
77
|
+
const quickLabelPickerRef = useRef(quickLabelPicker);
|
|
78
|
+
quickLabelPickerRef.current = quickLabelPicker;
|
|
79
|
+
|
|
80
|
+
const onAddRef = useRef(onAddAnnotation);
|
|
81
|
+
onAddRef.current = onAddAnnotation;
|
|
82
|
+
const onSelectRef = useRef(onSelectAnnotation);
|
|
83
|
+
onSelectRef.current = onSelectAnnotation;
|
|
84
|
+
|
|
85
|
+
const anchorRef = useRef<HTMLDivElement | null>(null);
|
|
86
|
+
|
|
87
|
+
const getOrCreateAnchor = useCallback(() => {
|
|
88
|
+
if (!anchorRef.current) {
|
|
89
|
+
const div = document.createElement("div");
|
|
90
|
+
div.style.position = "fixed";
|
|
91
|
+
div.style.pointerEvents = "none";
|
|
92
|
+
div.style.width = "1px";
|
|
93
|
+
div.style.height = "1px";
|
|
94
|
+
document.body.appendChild(div);
|
|
95
|
+
anchorRef.current = div;
|
|
96
|
+
}
|
|
97
|
+
return anchorRef.current;
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
const positionAnchor = useCallback(
|
|
101
|
+
(bridgeRect: { top: number; left: number; width: number; height: number }) => {
|
|
102
|
+
const iframe = iframeRef.current;
|
|
103
|
+
if (!iframe) return null;
|
|
104
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
105
|
+
// Fresh anchor per selection. The toolbar/popover recompute position only
|
|
106
|
+
// when their `element` node identity changes, so reusing one anchor div
|
|
107
|
+
// leaves them pinned to the previous selection. Drop the old one first.
|
|
108
|
+
if (anchorRef.current) anchorRef.current.remove();
|
|
109
|
+
anchorRef.current = null;
|
|
110
|
+
const anchor = getOrCreateAnchor();
|
|
111
|
+
anchor.style.top = `${iframeRect.top + bridgeRect.top}px`;
|
|
112
|
+
anchor.style.left = `${iframeRect.left + bridgeRect.left + bridgeRect.width / 2}px`;
|
|
113
|
+
return anchor;
|
|
114
|
+
},
|
|
115
|
+
[iframeRef, getOrCreateAnchor],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
function handler(e: MessageEvent<BridgeMessage>) {
|
|
120
|
+
if (!e.data || typeof e.data.type !== "string" || !e.data.type.startsWith(PREFIX)) return;
|
|
121
|
+
|
|
122
|
+
const type = e.data.type;
|
|
123
|
+
|
|
124
|
+
if (type === `${PREFIX}selection`) {
|
|
125
|
+
const msg = e.data as BridgeSelectionMessage;
|
|
126
|
+
pendingTextRef.current = msg.text;
|
|
127
|
+
const anchor = positionAnchor(msg.rect);
|
|
128
|
+
if (!anchor) return;
|
|
129
|
+
|
|
130
|
+
const currentMode = modeRef.current;
|
|
131
|
+
|
|
132
|
+
if (currentMode === "redline") {
|
|
133
|
+
const id = nextHtmlAnnId();
|
|
134
|
+
postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "deletion" });
|
|
135
|
+
onAddRef.current?.({
|
|
136
|
+
id,
|
|
137
|
+
blockId: "",
|
|
138
|
+
startOffset: 0,
|
|
139
|
+
endOffset: 0,
|
|
140
|
+
type: AnnotationType.DELETION,
|
|
141
|
+
originalText: msg.text,
|
|
142
|
+
author: getIdentity(),
|
|
143
|
+
createdA: Date.now(),
|
|
144
|
+
});
|
|
145
|
+
pendingTextRef.current = "";
|
|
146
|
+
} else if (currentMode === "comment") {
|
|
147
|
+
// Release iframe focus so the popover's textarea autofocus lands in the
|
|
148
|
+
// parent (otherwise the iframe keeps focus and swallows further keys).
|
|
149
|
+
iframeRef.current?.blur();
|
|
150
|
+
setCommentPopover({
|
|
151
|
+
anchorEl: anchor,
|
|
152
|
+
contextText: msg.text,
|
|
153
|
+
selectedText: msg.text,
|
|
154
|
+
});
|
|
155
|
+
} else if (currentMode === "quickLabel") {
|
|
156
|
+
setQuickLabelPicker({
|
|
157
|
+
anchorEl: anchor,
|
|
158
|
+
cursorHint: { x: parseFloat(anchor.style.left), y: parseFloat(anchor.style.top) },
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
setToolbarState({
|
|
162
|
+
element: anchor,
|
|
163
|
+
source: null,
|
|
164
|
+
selectionText: msg.text,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (type === `${PREFIX}selection-clear`) {
|
|
170
|
+
setToolbarState(null);
|
|
171
|
+
// Keep the captured text alive while a comment/quick-label is open: the user
|
|
172
|
+
// is composing, and the selection collapsing or scrolling out of view must
|
|
173
|
+
// not drop the annotation on submit. It's overwritten on the next selection.
|
|
174
|
+
if (!commentPopoverRef.current && !quickLabelPickerRef.current) {
|
|
175
|
+
pendingTextRef.current = "";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (type === `${PREFIX}selection-rect`) {
|
|
180
|
+
// The iframe content scrolled — move the anchor to the selection's new
|
|
181
|
+
// position and nudge the toolbar/popover (which listen to window scroll) to
|
|
182
|
+
// recompute, so they stay attached to the selection.
|
|
183
|
+
const iframe = iframeRef.current;
|
|
184
|
+
const anchor = anchorRef.current;
|
|
185
|
+
if (!iframe || !anchor) return;
|
|
186
|
+
const r = (e.data as unknown as { rect: { top: number; left: number; width: number; height: number } }).rect;
|
|
187
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
188
|
+
anchor.style.top = `${iframeRect.top + r.top}px`;
|
|
189
|
+
anchor.style.left = `${iframeRect.left + r.left + r.width / 2}px`;
|
|
190
|
+
window.dispatchEvent(new Event("scroll"));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (type === `${PREFIX}keytype`) {
|
|
194
|
+
// Type-to-comment: only when the markup toolbar is showing (matches the
|
|
195
|
+
// markdown path, where AnnotationToolbar owns this keydown). Open a comment
|
|
196
|
+
// pre-filled with the typed char.
|
|
197
|
+
if (!toolbarStateRef.current) return;
|
|
198
|
+
const key = (e.data as { key?: string }).key;
|
|
199
|
+
const text = pendingTextRef.current;
|
|
200
|
+
if (!key || !text) return;
|
|
201
|
+
const anchor = anchorRef.current ?? getOrCreateAnchor();
|
|
202
|
+
// Release iframe focus so the popover textarea can take it (and the rest of
|
|
203
|
+
// the typing) — otherwise the iframe keeps focus and the bridge eats keys.
|
|
204
|
+
iframeRef.current?.blur();
|
|
205
|
+
setToolbarState(null);
|
|
206
|
+
setCommentPopover({ anchorEl: anchor, contextText: text, selectedText: text, initialText: key });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (type === `${PREFIX}mark-click`) {
|
|
210
|
+
const msg = e.data as BridgeMarkClickMessage;
|
|
211
|
+
onSelectRef.current?.(msg.id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (type === `${PREFIX}resize`) {
|
|
215
|
+
const msg = e.data as BridgeResizeMessage;
|
|
216
|
+
onResize?.(msg.height);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
window.addEventListener("message", handler);
|
|
221
|
+
return () => {
|
|
222
|
+
window.removeEventListener("message", handler);
|
|
223
|
+
if (anchorRef.current) {
|
|
224
|
+
anchorRef.current.remove();
|
|
225
|
+
anchorRef.current = null;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}, [iframeRef, positionAnchor, onResize, getOrCreateAnchor]);
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (selectedAnnotationId) {
|
|
232
|
+
postToIframe(iframeRef.current, {
|
|
233
|
+
type: `${PREFIX}scroll-to`,
|
|
234
|
+
id: selectedAnnotationId,
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
postToIframe(iframeRef.current, {
|
|
238
|
+
type: `${PREFIX}focus-mark`,
|
|
239
|
+
id: null,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}, [selectedAnnotationId, iframeRef]);
|
|
243
|
+
|
|
244
|
+
const handleAnnotate = useCallback(
|
|
245
|
+
(type: AnnotationType) => {
|
|
246
|
+
const text = pendingTextRef.current;
|
|
247
|
+
if (!text || type !== AnnotationType.DELETION) return;
|
|
248
|
+
|
|
249
|
+
const id = nextHtmlAnnId();
|
|
250
|
+
postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "deletion" });
|
|
251
|
+
onAddRef.current?.({
|
|
252
|
+
id,
|
|
253
|
+
blockId: "",
|
|
254
|
+
startOffset: 0,
|
|
255
|
+
endOffset: 0,
|
|
256
|
+
type: AnnotationType.DELETION,
|
|
257
|
+
originalText: text,
|
|
258
|
+
author: getIdentity(),
|
|
259
|
+
createdA: Date.now(),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
setToolbarState(null);
|
|
263
|
+
pendingTextRef.current = "";
|
|
264
|
+
},
|
|
265
|
+
[iframeRef],
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const handleRequestComment = useCallback(
|
|
269
|
+
(initialChar?: string) => {
|
|
270
|
+
const text = pendingTextRef.current;
|
|
271
|
+
if (!text) return;
|
|
272
|
+
const anchor = anchorRef.current ?? getOrCreateAnchor();
|
|
273
|
+
setToolbarState(null);
|
|
274
|
+
setCommentPopover({ anchorEl: anchor, contextText: text, selectedText: text, initialText: initialChar });
|
|
275
|
+
},
|
|
276
|
+
[getOrCreateAnchor],
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const handleCommentSubmit = useCallback(
|
|
280
|
+
(comment: string, images?: ImageAttachment[]) => {
|
|
281
|
+
// Prefer the text captured when the popover opened — it can't be clobbered by
|
|
282
|
+
// a later selection change or clear while the user is composing the comment.
|
|
283
|
+
const text = commentPopoverRef.current?.selectedText || pendingTextRef.current;
|
|
284
|
+
if (!text) return;
|
|
285
|
+
|
|
286
|
+
const id = nextHtmlAnnId();
|
|
287
|
+
postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "comment" });
|
|
288
|
+
onAddRef.current?.({
|
|
289
|
+
id,
|
|
290
|
+
blockId: "",
|
|
291
|
+
startOffset: 0,
|
|
292
|
+
endOffset: 0,
|
|
293
|
+
type: AnnotationType.COMMENT,
|
|
294
|
+
text: comment,
|
|
295
|
+
originalText: text,
|
|
296
|
+
author: getIdentity(),
|
|
297
|
+
createdA: Date.now(),
|
|
298
|
+
images,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
setCommentPopover(null);
|
|
302
|
+
pendingTextRef.current = "";
|
|
303
|
+
},
|
|
304
|
+
[iframeRef],
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const handleCommentClose = useCallback(() => {
|
|
308
|
+
setCommentPopover(null);
|
|
309
|
+
pendingTextRef.current = "";
|
|
310
|
+
}, []);
|
|
311
|
+
|
|
312
|
+
const handleToolbarClose = useCallback(() => {
|
|
313
|
+
setToolbarState(null);
|
|
314
|
+
pendingTextRef.current = "";
|
|
315
|
+
}, []);
|
|
316
|
+
|
|
317
|
+
const applyQuickLabel = useCallback(
|
|
318
|
+
(label: QuickLabel, clearState: () => void) => {
|
|
319
|
+
const text = pendingTextRef.current;
|
|
320
|
+
if (!text) return;
|
|
321
|
+
const id = nextHtmlAnnId();
|
|
322
|
+
postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "comment" });
|
|
323
|
+
onAddRef.current?.({
|
|
324
|
+
id,
|
|
325
|
+
blockId: "",
|
|
326
|
+
startOffset: 0,
|
|
327
|
+
endOffset: 0,
|
|
328
|
+
type: AnnotationType.COMMENT,
|
|
329
|
+
text: label.text,
|
|
330
|
+
originalText: text,
|
|
331
|
+
isQuickLabel: true,
|
|
332
|
+
quickLabelTip: label.tip,
|
|
333
|
+
author: getIdentity(),
|
|
334
|
+
createdA: Date.now(),
|
|
335
|
+
});
|
|
336
|
+
clearState();
|
|
337
|
+
pendingTextRef.current = "";
|
|
338
|
+
},
|
|
339
|
+
[iframeRef],
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const handleQuickLabel = useCallback(
|
|
343
|
+
(label: QuickLabel) => applyQuickLabel(label, () => setToolbarState(null)),
|
|
344
|
+
[applyQuickLabel],
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const handleFloatingQuickLabel = useCallback(
|
|
348
|
+
(label: QuickLabel) => applyQuickLabel(label, () => setQuickLabelPicker(null)),
|
|
349
|
+
[applyQuickLabel],
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const handleQuickLabelPickerDismiss = useCallback(() => {
|
|
353
|
+
setQuickLabelPicker(null);
|
|
354
|
+
pendingTextRef.current = "";
|
|
355
|
+
}, []);
|
|
356
|
+
|
|
357
|
+
const removeHighlight = useCallback(
|
|
358
|
+
(id: string) => {
|
|
359
|
+
postToIframe(iframeRef.current, { type: `${PREFIX}remove-mark`, id });
|
|
360
|
+
},
|
|
361
|
+
[iframeRef],
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const clearAllHighlights = useCallback(() => {
|
|
365
|
+
postToIframe(iframeRef.current, { type: `${PREFIX}clear-marks` });
|
|
366
|
+
}, [iframeRef]);
|
|
367
|
+
|
|
368
|
+
const applyAnnotations = useCallback(
|
|
369
|
+
(anns: Annotation[]) => {
|
|
370
|
+
for (const ann of anns) {
|
|
371
|
+
if (ann.type === AnnotationType.GLOBAL_COMMENT) continue;
|
|
372
|
+
const annType = ann.type === AnnotationType.DELETION ? "deletion" : "comment";
|
|
373
|
+
postToIframe(iframeRef.current, {
|
|
374
|
+
type: `${PREFIX}find-and-mark`,
|
|
375
|
+
id: ann.id,
|
|
376
|
+
originalText: ann.originalText,
|
|
377
|
+
annotationType: annType,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
[iframeRef],
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
toolbarState,
|
|
386
|
+
commentPopover,
|
|
387
|
+
quickLabelPicker,
|
|
388
|
+
handleAnnotate,
|
|
389
|
+
handleQuickLabel,
|
|
390
|
+
handleToolbarClose,
|
|
391
|
+
handleRequestComment,
|
|
392
|
+
handleCommentSubmit,
|
|
393
|
+
handleCommentClose,
|
|
394
|
+
handleFloatingQuickLabel,
|
|
395
|
+
handleQuickLabelPickerDismiss,
|
|
396
|
+
removeHighlight,
|
|
397
|
+
clearAllHighlights,
|
|
398
|
+
applyAnnotations,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent brand marks, inlined as components so the single-file review bundle has
|
|
3
|
+
* no external asset dependency. Claude is a vector path; Codex ships only as a
|
|
4
|
+
* PNG in the repo, so it rides as a base64 data-URI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function ClaudeIcon({ className }: { className?: string }) {
|
|
8
|
+
return (
|
|
9
|
+
<svg viewBox="0 0 32 32" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
10
|
+
<path d="m6.283 21.28 6.293-3.531.106-.306-.106-.171h-.307l-1.051-.065-3.596-.097-3.118-.13-3.021-.162-.761-.161-.712-.94.073-.469.639-.429.916.08 2.023.138 3.037.209 2.203.13 3.263.339h.518l.073-.21-.177-.129-.138-.13-3.142-2.129-3.401-2.25-1.782-1.296-.963-.656-.486-.616-.21-1.343.875-.963 1.175.08.3.08 1.19.915 2.542 1.967 3.319 2.445.486.404.194-.138.024-.097-.218-.365-1.806-3.263-1.926-3.32-.857-1.375-.227-.825c-.08-.339-.138-.624-.138-.972L8.384.177 8.935 0l1.328.177.56.486.824 1.887 1.337 2.972 2.073 4.04.607 1.199.324 1.11.121.339h.21v-.194l.17-2.276.315-2.795.307-3.596.106-1.012.501-1.214.995-.657.778.372.639.916-.088.591-.381 2.471-.745 3.87-.485 2.591h.282l.324-.324 1.311-1.74 2.203-2.754.972-1.093 1.133-1.207.728-.574h1.376l1.013 1.505-.454 1.555-1.416 1.797-1.175 1.522-1.685 2.268-1.051 1.814.097.144.25-.023 3.805-.81 2.056-.372 2.454-.421 1.11.518.12.527-.436 1.078-2.624.648-3.077.615-4.582 1.084-.057.041.065.08 2.065.195.883.047h2.162l4.025.3 1.052.696.63.851-.106.647-1.619.825-2.186-.518-5.1-1.214-1.75-.436h-.242v.145l1.458 1.425 2.671 2.412 3.346 3.11.17.769-.43.607-.453-.065-2.939-2.211-1.134-.996-2.568-2.162h-.17v.227l.591.866 3.125 4.697.162 1.441-.226.468-.81.283-.89-.162-1.829-2.568-1.888-2.891-1.522-2.592-.186.106-.898 9.677-.421.495-.972.371-.81-.615-.43-.996.43-1.967.518-2.568.422-2.041.38-2.535.226-.842-.015-.056-.185.023-1.912 2.624-2.906 3.928-2.3 2.462-.551.218-.954-.494.088-.883.533-.787 3.184-4.049 1.919-2.509 1.24-1.449-.009-.21h-.073l-8.455 5.49-1.505.194-.648-.607.08-.995.307-.324 2.542-1.749-.009.008z" fill="#d97757" />
|
|
11
|
+
</svg>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CODEX_DATA_URI =
|
|
16
|
+
"data:image/png;base64,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";
|
|
17
|
+
|
|
18
|
+
export function CodexIcon({ className }: { className?: string }) {
|
|
19
|
+
return <img src={CODEX_DATA_URI} className={className} alt="" aria-hidden="true" draggable={false} />;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Pi brand mark — path data from packages/ui/components/ProviderIcons.tsx's
|
|
23
|
+
// PiIcon (itself extracted from apps/marketing/public/assets/icon-pi.svg).
|
|
24
|
+
// currentColor so it adapts to theme, like OpenCodeIcon below.
|
|
25
|
+
export function PiIcon({ className }: { className?: string }) {
|
|
26
|
+
return (
|
|
27
|
+
<svg viewBox="0 0 800 800" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
28
|
+
<path fill="currentColor" fillRule="evenodd" d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z" />
|
|
29
|
+
<path fill="currentColor" d="M517.36 400H634.72V634.72H517.36Z" />
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// GitHub Copilot brand mark — path from the official agent icon set
|
|
35
|
+
// (welcome.developers.workers.dev/icons/agents/copilot); the dark/light
|
|
36
|
+
// variants there differ only by fill, so one currentColor component covers
|
|
37
|
+
// both themes, like PiIcon/OpenCodeIcon.
|
|
38
|
+
export function CopilotIcon({ className }: { className?: string }) {
|
|
39
|
+
return (
|
|
40
|
+
<svg viewBox="0 0 256 208" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
41
|
+
<path fill="currentColor" d="M205.3 31.4c14 14.8 20 35.2 22.5 63.6 6.6 0 12.8 1.5 17 7.2l7.8 10.6c2.2 3 3.4 6.6 3.4 10.4v28.7a12 12 0 0 1-4.8 9.5C215.9 187.2 172.3 208 128 208c-49 0-98.2-28.3-123.2-46.6a12 12 0 0 1-4.8-9.5v-28.7c0-3.8 1.2-7.4 3.4-10.5l7.8-10.5c4.2-5.7 10.4-7.2 17-7.2 2.5-28.4 8.4-48.8 22.5-63.6C77.3 3.2 112.6 0 127.6 0h.4c14.7 0 50.4 2.9 77.3 31.4ZM128 78.7c-3 0-6.5.2-10.3.6a27.1 27.1 0 0 1-6 12.1 45 45 0 0 1-32 13c-6.8 0-13.9-1.5-19.7-5.2-5.5 1.9-10.8 4.5-11.2 11-.5 12.2-.6 24.5-.6 36.8 0 6.1 0 12.3-.2 18.5 0 3.6 2.2 6.9 5.5 8.4C79.9 185.9 105 192 128 192s48-6 74.5-18.1a9.4 9.4 0 0 0 5.5-8.4c.3-18.4 0-37-.8-55.3-.4-6.6-5.7-9.1-11.2-11-5.8 3.7-13 5.1-19.7 5.1a45 45 0 0 1-32-12.9 27.1 27.1 0 0 1-6-12.1c-3.4-.4-6.9-.5-10.3-.6Zm-27 44c5.8 0 10.5 4.6 10.5 10.4v19.2a10.4 10.4 0 0 1-20.8 0V133c0-5.8 4.6-10.4 10.4-10.4Zm53.4 0c5.8 0 10.4 4.6 10.4 10.4v19.2a10.4 10.4 0 0 1-20.8 0V133c0-5.8 4.7-10.4 10.4-10.4Zm-73-94.4c-11.2 1.1-20.6 4.8-25.4 10-10.4 11.3-8.2 40.1-2.2 46.2A31.2 31.2 0 0 0 75 91.7c6.8 0 19.6-1.5 30.1-12.2 4.7-4.5 7.5-15.7 7.2-27-.3-9.1-2.9-16.7-6.7-19.9-4.2-3.6-13.6-5.2-24.2-4.3Zm69 4.3c-3.8 3.2-6.4 10.8-6.7 19.9-.3 11.3 2.5 22.5 7.2 27a41.7 41.7 0 0 0 30 12.2c8.9 0 17-2.9 21.3-7.2 6-6.1 8.2-34.9-2.2-46.3-4.8-5-14.2-8.8-25.4-9.9-10.6-1-20 .7-24.2 4.3ZM128 56c-2.6 0-5.6.2-9 .5.4 1.7.5 3.7.7 5.7 0 1.5 0 3-.2 4.5 3.2-.3 6-.3 8.5-.3 2.6 0 5.3 0 8.5.3-.2-1.6-.2-3-.2-4.5.2-2 .3-4 .7-5.7-3.4-.3-6.4-.5-9-.5Z" />
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Cursor brand mark — from packages/ui/components/icons/app/cursor.svg
|
|
47
|
+
// (black rounded square + white cursor facet).
|
|
48
|
+
export function CursorIcon({ className }: { className?: string }) {
|
|
49
|
+
return (
|
|
50
|
+
<svg viewBox="0 0 40 40" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
51
|
+
<path d="M28.375 4H11.625C7.41383 4 4 7.41383 4 11.625V28.375C4 32.5862 7.41383 36 11.625 36H28.375C32.5862 36 36 32.5862 36 28.375V11.625C36 7.41383 32.5862 4 28.375 4Z" fill="#000" />
|
|
52
|
+
<path d="M29.6214 14.2072L20.9502 9.12249C20.6718 8.95917 20.3282 8.95917 20.0498 9.12249L11.3786 14.2072C11.1445 14.3445 11 14.5984 11 14.8733V25.1267C11 25.4012 11.1445 25.6555 11.3786 25.7928L20.0498 30.8775C20.3282 31.0408 20.6718 31.0408 20.9502 30.8775L29.6214 25.7928C29.8555 25.6555 30 25.4016 30 25.1267V14.8733C30 14.5988 29.8555 14.3445 29.6214 14.2072ZM29.0767 15.2843L20.706 30.0101C20.6494 30.1093 20.5 30.0688 20.5 29.9538V20.3115C20.5 20.1189 20.3986 19.9407 20.2342 19.8439L12.0124 15.0226C11.9147 14.9651 11.9546 14.8134 12.0678 14.8134H28.8093C29.047 14.8134 29.1956 15.0751 29.0767 15.2843Z" fill="#fff" />
|
|
53
|
+
</svg>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// OpenCode brand mark — extracted from apps/marketing/public/assets/icon-opencode-dark.svg
|
|
58
|
+
// (same vector ProviderIcons.tsx uses; currentColor so it adapts to theme).
|
|
59
|
+
export function OpenCodeIcon({ className }: { className?: string }) {
|
|
60
|
+
return (
|
|
61
|
+
<svg viewBox="0 0 32 32" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
62
|
+
<path d="M3 32V0h26v32zM22 7H10v18h12z" fill="currentColor" />
|
|
63
|
+
<path d="M10 13h12v12H10z" fill="currentColor" opacity={0.4} />
|
|
64
|
+
</svg>
|
|
65
|
+
);
|
|
66
|
+
}
|