@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,386 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { createPortal } from "react-dom";
|
|
11
|
+
import type { Annotation, EditorMode, ImageAttachment, InputMethod } from "../../types";
|
|
12
|
+
import { AnnotationType } from "../../types";
|
|
13
|
+
import { getIdentity } from "../../utils/identity";
|
|
14
|
+
import { AnnotationToolbar } from "../AnnotationToolbar";
|
|
15
|
+
import { AttachmentsButton } from "../AttachmentsButton";
|
|
16
|
+
import { CommentPopover, type CommentAskAIHandler } from "../CommentPopover";
|
|
17
|
+
import { FloatingQuickLabelPicker } from "../FloatingQuickLabelPicker";
|
|
18
|
+
import type { ViewerHandle } from "../Viewer";
|
|
19
|
+
import { useHtmlAnnotation } from "./useHtmlAnnotation";
|
|
20
|
+
import { ANNOTATION_HIGHLIGHT_CSS, BRIDGE_SCRIPT } from "./bridge-script";
|
|
21
|
+
|
|
22
|
+
const PREFIX = "plannotator-bridge-";
|
|
23
|
+
|
|
24
|
+
const THEME_TOKENS = [
|
|
25
|
+
"--background",
|
|
26
|
+
"--foreground",
|
|
27
|
+
"--card",
|
|
28
|
+
"--card-foreground",
|
|
29
|
+
"--primary",
|
|
30
|
+
"--primary-foreground",
|
|
31
|
+
"--secondary",
|
|
32
|
+
"--secondary-foreground",
|
|
33
|
+
"--muted",
|
|
34
|
+
"--muted-foreground",
|
|
35
|
+
"--accent",
|
|
36
|
+
"--accent-foreground",
|
|
37
|
+
"--destructive",
|
|
38
|
+
"--destructive-foreground",
|
|
39
|
+
"--success",
|
|
40
|
+
"--success-foreground",
|
|
41
|
+
"--warning",
|
|
42
|
+
"--warning-foreground",
|
|
43
|
+
"--border",
|
|
44
|
+
"--input",
|
|
45
|
+
"--ring",
|
|
46
|
+
"--code-bg",
|
|
47
|
+
"--focus-highlight",
|
|
48
|
+
"--font-sans",
|
|
49
|
+
"--font-mono",
|
|
50
|
+
"--radius",
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
function readThemeTokens(): Record<string, string> {
|
|
54
|
+
const style = getComputedStyle(document.documentElement);
|
|
55
|
+
const tokens: Record<string, string> = {};
|
|
56
|
+
for (const key of THEME_TOKENS) {
|
|
57
|
+
const val = style.getPropertyValue(key).trim();
|
|
58
|
+
if (val) tokens[key] = val;
|
|
59
|
+
}
|
|
60
|
+
return tokens;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isLightTheme(): boolean {
|
|
64
|
+
return document.documentElement.classList.contains("light");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface HtmlViewerProps {
|
|
68
|
+
rawHtml: string;
|
|
69
|
+
annotations: Annotation[];
|
|
70
|
+
onAddAnnotation: (ann: Annotation) => void;
|
|
71
|
+
onSelectAnnotation: (id: string | null) => void;
|
|
72
|
+
selectedAnnotationId: string | null;
|
|
73
|
+
mode: EditorMode;
|
|
74
|
+
/** Input method: 'drag' = text selection, 'pinpoint' = click an element. */
|
|
75
|
+
inputMethod: InputMethod;
|
|
76
|
+
globalAttachments?: ImageAttachment[];
|
|
77
|
+
onAddGlobalAttachment?: (image: ImageAttachment) => void;
|
|
78
|
+
onRemoveGlobalAttachment?: (path: string) => void;
|
|
79
|
+
maxWidth?: number | null;
|
|
80
|
+
/** Render edge-to-edge: fill the viewport, drop the card chrome + action bar,
|
|
81
|
+
* and let the iframe own the full height instead of auto-resizing to content. */
|
|
82
|
+
fullViewport?: boolean;
|
|
83
|
+
/** Hide the floating doc-level controls (attachments + global comment) in
|
|
84
|
+
* full-viewport mode, so the user can read the page unobstructed. */
|
|
85
|
+
hideControls?: boolean;
|
|
86
|
+
/** A version diff (vs the previous version) is available to toggle. */
|
|
87
|
+
diffAvailable?: boolean;
|
|
88
|
+
/** Whether the diff-highlighted HTML is currently shown. */
|
|
89
|
+
diffActive?: boolean;
|
|
90
|
+
/** Toggle the diff-highlighted view on/off. */
|
|
91
|
+
onToggleDiff?: () => void;
|
|
92
|
+
onAskAI?: CommentAskAIHandler;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
96
|
+
(
|
|
97
|
+
{
|
|
98
|
+
rawHtml,
|
|
99
|
+
annotations,
|
|
100
|
+
onAddAnnotation,
|
|
101
|
+
onSelectAnnotation,
|
|
102
|
+
selectedAnnotationId,
|
|
103
|
+
mode,
|
|
104
|
+
inputMethod,
|
|
105
|
+
globalAttachments = [],
|
|
106
|
+
onAddGlobalAttachment,
|
|
107
|
+
onRemoveGlobalAttachment,
|
|
108
|
+
maxWidth,
|
|
109
|
+
fullViewport,
|
|
110
|
+
hideControls,
|
|
111
|
+
diffAvailable,
|
|
112
|
+
diffActive,
|
|
113
|
+
onToggleDiff,
|
|
114
|
+
onAskAI,
|
|
115
|
+
},
|
|
116
|
+
ref,
|
|
117
|
+
) => {
|
|
118
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
119
|
+
const globalCommentButtonRef = useRef<HTMLButtonElement>(null);
|
|
120
|
+
const [iframeHeight, setIframeHeight] = useState(600);
|
|
121
|
+
const [iframeReady, setIframeReady] = useState(false);
|
|
122
|
+
const [globalCommentPopover, setGlobalCommentPopover] = useState<{
|
|
123
|
+
anchorEl: HTMLElement;
|
|
124
|
+
contextText: string;
|
|
125
|
+
} | null>(null);
|
|
126
|
+
|
|
127
|
+
const srcdoc = useMemo(() => {
|
|
128
|
+
const tokens = readThemeTokens();
|
|
129
|
+
let themeCSS = ":root {\n";
|
|
130
|
+
for (const [key, val] of Object.entries(tokens)) {
|
|
131
|
+
themeCSS += ` ${key}: ${val};\n`;
|
|
132
|
+
}
|
|
133
|
+
themeCSS += "}\n";
|
|
134
|
+
if (isLightTheme()) themeCSS += ":root { color-scheme: light; }\n:root.light, :root { }\n";
|
|
135
|
+
|
|
136
|
+
// Version-diff highlights: htmlDiff wraps changed text in <ins>/<del>.
|
|
137
|
+
const diffCSS =
|
|
138
|
+
"ins{background:#e6ffec;color:#0a7d33;text-decoration:none;border-radius:2px;box-shadow:0 0 0 1px #abf2bc inset}" +
|
|
139
|
+
"del{background:#ffebe9;color:#b31d28;text-decoration:line-through;border-radius:2px;box-shadow:0 0 0 1px #ffc1bc inset}";
|
|
140
|
+
const injection = `<style>${themeCSS}${ANNOTATION_HIGHLIGHT_CSS}${diffCSS}</style><script>${BRIDGE_SCRIPT}</script>`;
|
|
141
|
+
const headClose = rawHtml.indexOf("</head>");
|
|
142
|
+
if (headClose !== -1) {
|
|
143
|
+
return rawHtml.slice(0, headClose) + injection + rawHtml.slice(headClose);
|
|
144
|
+
}
|
|
145
|
+
return injection + rawHtml;
|
|
146
|
+
}, [rawHtml]);
|
|
147
|
+
|
|
148
|
+
const handleResize = useCallback((height: number) => {
|
|
149
|
+
setIframeHeight(height);
|
|
150
|
+
}, []);
|
|
151
|
+
|
|
152
|
+
const hook = useHtmlAnnotation({
|
|
153
|
+
iframeRef,
|
|
154
|
+
annotations,
|
|
155
|
+
onAddAnnotation,
|
|
156
|
+
onSelectAnnotation,
|
|
157
|
+
selectedAnnotationId,
|
|
158
|
+
mode,
|
|
159
|
+
onResize: handleResize,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
function handler(e: MessageEvent) {
|
|
164
|
+
if (e.data?.type === `${PREFIX}ready`) {
|
|
165
|
+
setIframeReady(true);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
window.addEventListener("message", handler);
|
|
169
|
+
return () => window.removeEventListener("message", handler);
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (!iframeReady) return;
|
|
174
|
+
if (annotations.length > 0) {
|
|
175
|
+
hook.applyAnnotations(annotations);
|
|
176
|
+
}
|
|
177
|
+
}, [iframeReady]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
178
|
+
|
|
179
|
+
// Tell the bridge the current input method (drag vs pinpoint). Re-posts on
|
|
180
|
+
// ready (fresh iframe) and whenever the user switches it in the toolstrip.
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!iframeReady) return;
|
|
183
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
184
|
+
{ type: `${PREFIX}set-input-method`, method: inputMethod },
|
|
185
|
+
"*",
|
|
186
|
+
);
|
|
187
|
+
}, [iframeReady, inputMethod]);
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (!iframeReady) return;
|
|
191
|
+
function sendTheme() {
|
|
192
|
+
const tokens = readThemeTokens();
|
|
193
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
194
|
+
{ type: `${PREFIX}theme`, tokens, isLight: isLightTheme() },
|
|
195
|
+
"*",
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
sendTheme();
|
|
199
|
+
const observer = new MutationObserver(sendTheme);
|
|
200
|
+
observer.observe(document.documentElement, {
|
|
201
|
+
attributes: true,
|
|
202
|
+
attributeFilter: ["class", "style"],
|
|
203
|
+
});
|
|
204
|
+
return () => observer.disconnect();
|
|
205
|
+
}, [iframeReady]);
|
|
206
|
+
|
|
207
|
+
useImperativeHandle(ref, () => ({
|
|
208
|
+
removeHighlight: hook.removeHighlight,
|
|
209
|
+
clearAllHighlights: hook.clearAllHighlights,
|
|
210
|
+
applySharedAnnotations: hook.applyAnnotations,
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
const handleGlobalCommentSubmit = useCallback(
|
|
214
|
+
(text: string, images?: ImageAttachment[]) => {
|
|
215
|
+
onAddAnnotation({
|
|
216
|
+
id: `global-${Date.now()}`,
|
|
217
|
+
blockId: "",
|
|
218
|
+
startOffset: 0,
|
|
219
|
+
endOffset: 0,
|
|
220
|
+
type: AnnotationType.GLOBAL_COMMENT,
|
|
221
|
+
text: text.trim(),
|
|
222
|
+
originalText: "",
|
|
223
|
+
author: getIdentity(),
|
|
224
|
+
createdA: Date.now(),
|
|
225
|
+
images,
|
|
226
|
+
});
|
|
227
|
+
setGlobalCommentPopover(null);
|
|
228
|
+
},
|
|
229
|
+
[onAddAnnotation],
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Document-level controls (attachments + global comment). Shared between the
|
|
233
|
+
// normal layout (bar above the card) and full-viewport (floating overlay), so
|
|
234
|
+
// edge-to-edge HTML keeps these affordances rather than dropping them.
|
|
235
|
+
const actionButtons = (
|
|
236
|
+
<>
|
|
237
|
+
{diffAvailable && onToggleDiff && (
|
|
238
|
+
<button
|
|
239
|
+
onClick={onToggleDiff}
|
|
240
|
+
className={`flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-md transition-colors cursor-pointer ${diffActive ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground bg-muted/50 hover:bg-muted"}`}
|
|
241
|
+
title={diffActive ? "Hide changes vs previous version" : "Show changes vs previous version"}
|
|
242
|
+
>
|
|
243
|
+
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
244
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-9L21 3m0 0l-4.5 4.5M21 3H7.5" />
|
|
245
|
+
</svg>
|
|
246
|
+
<span>{diffActive ? "Hide changes" : "Show changes"}</span>
|
|
247
|
+
</button>
|
|
248
|
+
)}
|
|
249
|
+
{onAddGlobalAttachment && onRemoveGlobalAttachment && (
|
|
250
|
+
<AttachmentsButton
|
|
251
|
+
images={globalAttachments}
|
|
252
|
+
onAdd={onAddGlobalAttachment}
|
|
253
|
+
onRemove={onRemoveGlobalAttachment}
|
|
254
|
+
variant="toolbar"
|
|
255
|
+
/>
|
|
256
|
+
)}
|
|
257
|
+
<button
|
|
258
|
+
ref={globalCommentButtonRef}
|
|
259
|
+
onClick={() => {
|
|
260
|
+
setGlobalCommentPopover({
|
|
261
|
+
anchorEl: globalCommentButtonRef.current!,
|
|
262
|
+
contextText: "",
|
|
263
|
+
});
|
|
264
|
+
}}
|
|
265
|
+
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground bg-muted/50 hover:bg-muted rounded-md transition-colors cursor-pointer"
|
|
266
|
+
title="Add global comment"
|
|
267
|
+
>
|
|
268
|
+
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
269
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" />
|
|
270
|
+
</svg>
|
|
271
|
+
<span>Comment</span>
|
|
272
|
+
</button>
|
|
273
|
+
</>
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<>
|
|
278
|
+
<div
|
|
279
|
+
className={`relative w-full${fullViewport ? " h-full flex flex-col" : ""}`}
|
|
280
|
+
style={fullViewport ? undefined : { maxWidth: maxWidth ?? undefined }}
|
|
281
|
+
>
|
|
282
|
+
{/* Action bar — above the iframe in normal mode (outside overflow:hidden). */}
|
|
283
|
+
{!fullViewport && (
|
|
284
|
+
<div data-print-hide className="flex justify-end gap-1 md:gap-2 mb-2">
|
|
285
|
+
{actionButtons}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
|
|
289
|
+
<article
|
|
290
|
+
data-print-region="article"
|
|
291
|
+
className={fullViewport ? "relative overflow-hidden w-full flex-1" : "relative bg-card rounded-xl shadow-xl overflow-hidden w-full"}
|
|
292
|
+
>
|
|
293
|
+
{/* Full-viewport mode has no card chrome, so float the same controls
|
|
294
|
+
over the top-right of the iframe (with a backdrop so they read over
|
|
295
|
+
any HTML). The selection toolbar is portaled separately. */}
|
|
296
|
+
{fullViewport && !hideControls && (
|
|
297
|
+
<div
|
|
298
|
+
data-print-hide
|
|
299
|
+
className="absolute top-3 right-3 z-10 flex items-center gap-1 md:gap-2 rounded-lg border border-border/50 bg-background/80 px-1.5 py-1 shadow-md backdrop-blur-sm"
|
|
300
|
+
>
|
|
301
|
+
{actionButtons}
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
<iframe
|
|
305
|
+
ref={iframeRef}
|
|
306
|
+
srcDoc={srcdoc}
|
|
307
|
+
sandbox="allow-scripts"
|
|
308
|
+
style={{
|
|
309
|
+
width: "100%",
|
|
310
|
+
height: fullViewport ? "100%" : `${iframeHeight}px`,
|
|
311
|
+
border: "none",
|
|
312
|
+
display: "block",
|
|
313
|
+
colorScheme: "auto",
|
|
314
|
+
}}
|
|
315
|
+
title="HTML Plan Viewer"
|
|
316
|
+
/>
|
|
317
|
+
</article>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Toolbar portal */}
|
|
321
|
+
{hook.toolbarState &&
|
|
322
|
+
createPortal(
|
|
323
|
+
<AnnotationToolbar
|
|
324
|
+
positionMode="center-above"
|
|
325
|
+
element={hook.toolbarState.element}
|
|
326
|
+
copyText={hook.toolbarState.selectionText}
|
|
327
|
+
onAnnotate={hook.handleAnnotate}
|
|
328
|
+
onRequestComment={hook.handleRequestComment}
|
|
329
|
+
onQuickLabel={hook.handleQuickLabel}
|
|
330
|
+
onClose={hook.handleToolbarClose}
|
|
331
|
+
/>,
|
|
332
|
+
document.body,
|
|
333
|
+
)}
|
|
334
|
+
|
|
335
|
+
{/* Comment popover portal */}
|
|
336
|
+
{hook.commentPopover &&
|
|
337
|
+
createPortal(
|
|
338
|
+
<CommentPopover
|
|
339
|
+
anchorEl={hook.commentPopover.anchorEl}
|
|
340
|
+
contextText={hook.commentPopover.contextText}
|
|
341
|
+
initialText={hook.commentPopover.initialText}
|
|
342
|
+
isGlobal={false}
|
|
343
|
+
onSubmit={hook.handleCommentSubmit}
|
|
344
|
+
onClose={hook.handleCommentClose}
|
|
345
|
+
onAskAI={onAskAI}
|
|
346
|
+
askAIContext={{
|
|
347
|
+
kind: "selection",
|
|
348
|
+
label: "Selected HTML",
|
|
349
|
+
text: hook.commentPopover.selectedText ?? hook.commentPopover.contextText,
|
|
350
|
+
}}
|
|
351
|
+
/>,
|
|
352
|
+
document.body,
|
|
353
|
+
)}
|
|
354
|
+
|
|
355
|
+
{/* Quick label picker portal */}
|
|
356
|
+
{hook.quickLabelPicker &&
|
|
357
|
+
createPortal(
|
|
358
|
+
<FloatingQuickLabelPicker
|
|
359
|
+
anchorEl={hook.quickLabelPicker.anchorEl}
|
|
360
|
+
cursorHint={hook.quickLabelPicker.cursorHint}
|
|
361
|
+
onSelect={hook.handleFloatingQuickLabel}
|
|
362
|
+
onDismiss={hook.handleQuickLabelPickerDismiss}
|
|
363
|
+
/>,
|
|
364
|
+
document.body,
|
|
365
|
+
)}
|
|
366
|
+
|
|
367
|
+
{/* Global comment popover portal */}
|
|
368
|
+
{globalCommentPopover &&
|
|
369
|
+
createPortal(
|
|
370
|
+
<CommentPopover
|
|
371
|
+
anchorEl={globalCommentPopover.anchorEl}
|
|
372
|
+
contextText={globalCommentPopover.contextText}
|
|
373
|
+
isGlobal={true}
|
|
374
|
+
onSubmit={handleGlobalCommentSubmit}
|
|
375
|
+
onClose={() => setGlobalCommentPopover(null)}
|
|
376
|
+
onAskAI={onAskAI}
|
|
377
|
+
askAIContext={{ kind: "general", label: "Document" }}
|
|
378
|
+
/>,
|
|
379
|
+
document.body,
|
|
380
|
+
)}
|
|
381
|
+
</>
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
HtmlViewer.displayName = "HtmlViewer";
|