@plannotator/ui 0.53.1 → 0.55.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/HANDOFF.md +274 -14
- package/README.md +114 -11
- package/components/AgentToolAnnouncementDialog.tsx +1 -1
- package/components/AgentsTab.tsx +1 -1
- package/components/AnnotationPanel.tsx +142 -24
- package/components/AnnotationSidebar.tsx +1 -1
- package/components/AnnotationToolbar.tsx +64 -9
- package/components/ApproveDropdown.tsx +7 -9
- package/components/AskSessionAnnouncementDialog.tsx +1 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/BlockRenderer.tsx +22 -5
- package/components/CodeFilePopout.tsx +139 -4
- package/components/CodeSnippetPreview.tsx +174 -0
- package/components/CommentPopover.tsx +44 -8
- package/components/DecisionControl.tsx +31 -5
- package/components/DeferredMarkdownContentsRail.tsx +38 -0
- package/components/DiagramBlock.tsx +17 -12
- package/components/DiffFileTree.tsx +7 -6
- package/components/DocumentContents.tsx +74 -0
- package/components/EditorAnnotationCard.tsx +1 -1
- package/components/ExportModal.tsx +77 -7
- package/components/HostTitle.tsx +0 -4
- package/components/ImageAnnotator/Toolbar.tsx +25 -1
- package/components/ImageAnnotator/index.tsx +24 -4
- package/components/ImageLightbox.tsx +722 -126
- package/components/ImagePeek.tsx +38 -4
- package/components/ImageRegions.tsx +369 -0
- package/components/ImageThumbnail.tsx +1 -1
- package/components/ImageZoomStage.tsx +796 -0
- package/components/InboxAnnouncementDialog.tsx +202 -0
- package/components/InlineMarkdown.tsx +167 -174
- package/components/KeyboardShortcuts.tsx +23 -0
- package/components/LazyBlockBoundary.tsx +100 -0
- package/components/ListMarker.tsx +3 -3
- package/components/LookAndFeelAnnouncementDialog.tsx +12 -2
- package/components/MarkdownContentsRail.tsx +777 -0
- package/components/MenuVersionSection.tsx +31 -9
- package/components/PlanDecisionMenuExtras.tsx +129 -0
- package/components/PlanHeaderMenu.tsx +15 -1
- package/components/ProviderIcons.tsx +8 -0
- package/components/RefHoverCard.tsx +913 -0
- package/components/SendFailureToast.tsx +126 -0
- package/components/Settings.tsx +617 -126
- package/components/TableOfContents.tsx +20 -6
- package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
- package/components/ThemeProvider.tsx +14 -1
- package/components/ThemeTab.tsx +16 -24
- package/components/Tooltip.tsx +49 -18
- package/components/Viewer.tsx +288 -43
- package/components/ai/DocumentAIChatPanel.tsx +29 -3
- package/components/ai/NewChatButton.tsx +74 -0
- package/components/blocks/CodeBlock.tsx +1 -1
- package/components/blocks/HtmlBlock.tsx +7 -1
- package/components/blocks/QuestionBlock.tsx +532 -62
- package/components/diagram/DiagramComposer.tsx +5 -1
- package/components/diagram/DiagramPending.tsx +12 -2
- package/components/diagram/DiagramViewer.tsx +19 -3
- package/components/html-embed/HtmlEmbedBlock.tsx +374 -0
- package/components/html-embed/SvgFenceBlock.tsx +46 -0
- package/components/html-embed/index.ts +3 -0
- package/components/html-embed/languages.ts +15 -0
- package/components/html-embed/loadGate.ts +107 -0
- package/components/html-viewer/HtmlViewer.tsx +82 -6
- package/components/html-viewer/bridge-script.asset.js +173 -1
- package/components/html-viewer/bridge-script.ts +173 -1
- package/components/html-viewer/useHtmlAnnotation.ts +125 -0
- package/components/imageZoom.ts +276 -0
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -1
- package/components/plan-diff/PlanDiffViewer.tsx +13 -2
- package/components/plan-diff/PlanRawDiffView.tsx +1 -1
- package/components/sidebar/FileBrowser.tsx +16 -2
- package/components/sidebar/SidebarTabs.tsx +3 -0
- package/config/index.ts +3 -0
- package/config/reviewView.ts +41 -1
- package/config/settings.ts +155 -4
- package/configure.ts +28 -0
- package/contents-rail.css +337 -0
- package/hooks/pfm/useCodeFilePopout.ts +7 -1
- package/hooks/useAIChat.ts +72 -25
- package/hooks/useAnnotationDraft.ts +71 -6
- package/hooks/useAnnotationHighlighter.ts +57 -3
- package/hooks/useCodeAnnotationDraft.ts +113 -17
- package/hooks/useCookieHostSetting.ts +96 -0
- package/hooks/useDocumentFont.ts +17 -0
- package/hooks/useDocumentTextSize.ts +20 -0
- package/hooks/useFileBrowser.ts +10 -1
- package/hooks/useHeadingScrollSpy.ts +77 -0
- package/hooks/useHoverPreview.ts +94 -0
- package/hooks/useLinkedDoc.ts +226 -13
- package/hooks/usePackageChangesSetting.ts +90 -0
- package/hooks/usePlanLookDialog.ts +67 -0
- package/hooks/usePrefersReducedMotion.ts +23 -0
- package/hooks/useSharing.ts +11 -7
- package/hooks/useSwipeStep.ts +597 -0
- package/hooks/useThemePaletteHalves.ts +37 -0
- package/hooks/useUpdateCheck.ts +22 -4
- package/hooks/useUpdateToast.ts +1 -1
- package/package.json +26 -23
- package/shortcuts/code-review/annotationToolbar.shortcuts.ts +1 -0
- package/shortcuts/code-review/explorer.shortcuts.ts +82 -0
- package/shortcuts/core.ts +63 -4
- package/shortcuts/index.ts +1 -0
- package/shortcuts/plan-review/annotationPanel.shortcuts.ts +1 -1
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +1 -0
- package/shortcuts/runtime.ts +4 -3
- package/styles.css +1 -1
- package/theme.css +179 -1
- package/themes/plannotator-graphite.css +66 -0
- package/types.ts +26 -9
- package/utils/agentSwitch.ts +41 -20
- package/utils/agentToolSetting.ts +1 -1
- package/utils/aiChatFormat.ts +23 -7
- package/utils/annotationHelpers.ts +32 -4
- package/utils/annotationOwnsHighlight.ts +4 -2
- package/utils/asciidocParser.ts +570 -0
- package/utils/autoUpdateNotice.ts +24 -0
- package/utils/belowTitleSlot.ts +174 -0
- package/utils/blockParse.ts +45 -0
- package/utils/codeLineScroll.ts +100 -0
- package/utils/commentSubmitKey.ts +48 -0
- package/utils/cssFontFamily.ts +65 -0
- package/utils/decisionSpec.ts +190 -2
- package/utils/defaultNotesApp.ts +1 -1
- package/utils/diffFonts.ts +4 -1
- package/utils/documentFont.ts +68 -0
- package/utils/documentTextSize.ts +54 -0
- package/utils/fontDetect.ts +63 -0
- package/utils/headingScrollSpy.ts +92 -0
- package/utils/inboxAnnouncement.ts +78 -0
- package/utils/lookAndFeelAnnouncement.ts +82 -5
- package/utils/markdownExtensions.ts +2 -2
- package/utils/mermaid.ts +20 -3
- package/utils/mermaidTheme.ts +3 -51
- package/utils/notion.ts +58 -0
- package/utils/parser.ts +28 -1
- package/utils/platform.ts +46 -0
- package/utils/refCard.ts +82 -0
- package/utils/richClipboard.ts +380 -0
- package/utils/richClipboardLists.ts +57 -0
- package/utils/richCopyPolicy.ts +22 -0
- package/utils/scrollAnchor.ts +82 -0
- package/utils/sectionRefs.ts +123 -0
- package/utils/selectionBoundary.ts +40 -0
- package/utils/sendFailure.ts +85 -0
- package/utils/sharing.ts +5 -2
- package/utils/themeRegistry.ts +10 -0
- package/utils/themeTokens.ts +74 -0
- package/utils/updateNotice.ts +30 -8
- package/utils/vscodePanel.ts +9 -0
package/config/settings.ts
CHANGED
|
@@ -13,8 +13,14 @@ import {
|
|
|
13
13
|
isAnnotateAgentTerminalSide,
|
|
14
14
|
type AnnotateAgentTerminalSide,
|
|
15
15
|
} from '@plannotator/core/agent-terminal';
|
|
16
|
-
import type { DiffLineBgIntensity } from '@plannotator/core/config-types';
|
|
16
|
+
import type { DiffLineBgIntensity, ReviewDefaults } from '@plannotator/core/config-types';
|
|
17
17
|
import { isFaviconStyle, type FaviconStyle } from '@plannotator/core/favicon';
|
|
18
|
+
import {
|
|
19
|
+
agentSwitchConfigValue,
|
|
20
|
+
isAgentSwitchName,
|
|
21
|
+
parseAgentSwitchConfig,
|
|
22
|
+
type AgentSwitchChoice,
|
|
23
|
+
} from '@plannotator/core/agent-switch';
|
|
18
24
|
import {
|
|
19
25
|
DEFAULT_TOKEN_HOVER_DELAY_MS,
|
|
20
26
|
isTokenHoverDelay,
|
|
@@ -23,7 +29,14 @@ import {
|
|
|
23
29
|
type TokenHoverTrigger,
|
|
24
30
|
} from '@plannotator/core/token-hover';
|
|
25
31
|
import { DEFAULT_DIAGRAM_SHADOW, isDiagramShadow } from '../utils/diagramShadow';
|
|
32
|
+
import {
|
|
33
|
+
DEFAULT_DOCUMENT_TEXT_SIZE,
|
|
34
|
+
isDocumentTextSize,
|
|
35
|
+
type DocumentTextSize,
|
|
36
|
+
} from '../utils/documentTextSize';
|
|
37
|
+
import { normalizeFontFamilyName } from '../utils/cssFontFamily';
|
|
26
38
|
import { storage } from '../utils/storage';
|
|
39
|
+
import { isLookChoiceResolved } from '../utils/lookAndFeelAnnouncement';
|
|
27
40
|
import { generateIdentity } from '../utils/generateIdentity';
|
|
28
41
|
import {
|
|
29
42
|
getDefaultThemePair,
|
|
@@ -89,11 +102,45 @@ export function readThemePairCookies(keys?: ThemePairLegacyKeys): ThemePair | un
|
|
|
89
102
|
return normalizeThemePair({ mode, light: light ?? seeded.light, dark: dark ?? seeded.dark }, seeded);
|
|
90
103
|
}
|
|
91
104
|
|
|
105
|
+
/** Pre-registry cookie keys of the agent switch, kept so an upgrade keeps the choice. */
|
|
106
|
+
const AGENT_SWITCH_COOKIE = 'plannotator-agent-switch';
|
|
107
|
+
const AGENT_SWITCH_CUSTOM_COOKIE = 'plannotator-agent-custom';
|
|
108
|
+
|
|
109
|
+
function readAgentSwitchCookies(): AgentSwitchChoice | undefined {
|
|
110
|
+
const choice = parseAgentSwitchConfig(storage.getItem(AGENT_SWITCH_COOKIE));
|
|
111
|
+
if (!choice) return undefined;
|
|
112
|
+
const customName = storage.getItem(AGENT_SWITCH_CUSTOM_COOKIE);
|
|
113
|
+
return isAgentSwitchName(customName) ? { switchTo: choice.switchTo, customName } : choice;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function writeAgentSwitchCookies(choice: AgentSwitchChoice | null): void {
|
|
117
|
+
// null = never chosen: write nothing, so the surface default keeps applying.
|
|
118
|
+
if (!choice) return;
|
|
119
|
+
storage.setItem(AGENT_SWITCH_COOKIE, choice.switchTo);
|
|
120
|
+
if (choice.customName) storage.setItem(AGENT_SWITCH_CUSTOM_COOKIE, choice.customName);
|
|
121
|
+
// Custom with its name cleared: drop the old name, or the next read would
|
|
122
|
+
// pair it with "custom" and switch to that old agent again.
|
|
123
|
+
else if (choice.switchTo === 'custom') storage.removeItem(AGENT_SWITCH_CUSTOM_COOKIE);
|
|
124
|
+
}
|
|
125
|
+
|
|
92
126
|
const DIFF_LINE_BG_INTENSITY_VALUES = ['subtle', 'normal', 'strong'] as const;
|
|
93
127
|
function isDiffLineBgIntensity(v: unknown): v is DiffLineBgIntensity {
|
|
94
128
|
return typeof v === 'string' && (DIFF_LINE_BG_INTENSITY_VALUES as readonly string[]).includes(v);
|
|
95
129
|
}
|
|
96
130
|
|
|
131
|
+
function parseReviewDefaults(value: unknown): ReviewDefaults | undefined {
|
|
132
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
133
|
+
const defaults: ReviewDefaults = {};
|
|
134
|
+
for (const [providerId, providerValue] of Object.entries(value)) {
|
|
135
|
+
if (!providerId || !providerValue || typeof providerValue !== 'object' || Array.isArray(providerValue)) continue;
|
|
136
|
+
const defaultDiffType = (providerValue as Record<string, unknown>).defaultDiffType;
|
|
137
|
+
if (typeof defaultDiffType === 'string' && defaultDiffType) {
|
|
138
|
+
defaults[providerId] = { defaultDiffType };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return defaults;
|
|
142
|
+
}
|
|
143
|
+
|
|
97
144
|
export interface SettingDef<T> {
|
|
98
145
|
defaultValue: T | (() => T);
|
|
99
146
|
fromCookie: () => T | undefined;
|
|
@@ -160,10 +207,18 @@ export const SETTINGS = {
|
|
|
160
207
|
},
|
|
161
208
|
|
|
162
209
|
gridEnabled: {
|
|
163
|
-
// Default
|
|
164
|
-
//
|
|
165
|
-
|
|
210
|
+
// Default OFF: plans open in the Clean look (the document edge to edge).
|
|
211
|
+
// Grid (the classic floating card on grid paper) is a choice, made in the
|
|
212
|
+
// first-run chooser or in Settings.
|
|
213
|
+
defaultValue: false as boolean,
|
|
166
214
|
fromCookie: () => {
|
|
215
|
+
// ConfigStore writes the default into this cookie on first load, so every
|
|
216
|
+
// browser that ran an older build stored 'true' (the old default) without
|
|
217
|
+
// anyone choosing it. The stored value counts only once the person has
|
|
218
|
+
// resolved the Grid/Clean choice under the Clean default (the v2 marker;
|
|
219
|
+
// a v1 marker with Grid stored is moved to Clean once, see
|
|
220
|
+
// utils/lookAndFeelAnnouncement); until then they get Clean.
|
|
221
|
+
if (!isLookChoiceResolved()) return undefined;
|
|
167
222
|
const v = storage.getItem('plannotator-grid-enabled');
|
|
168
223
|
return v === 'true' ? true : v === 'false' ? false : undefined;
|
|
169
224
|
},
|
|
@@ -192,6 +247,54 @@ export const SETTINGS = {
|
|
|
192
247
|
serverKey: undefined, fromServer: undefined, toServer: undefined,
|
|
193
248
|
},
|
|
194
249
|
|
|
250
|
+
/**
|
|
251
|
+
* The document's body text size in plan review and annotate, in px (#2066;
|
|
252
|
+
* see `utils/documentTextSize`). Default 15, today's size. Cookie-only, like
|
|
253
|
+
* the other display knobs; an unknown value reads as the default.
|
|
254
|
+
*/
|
|
255
|
+
documentTextSize: {
|
|
256
|
+
defaultValue: DEFAULT_DOCUMENT_TEXT_SIZE as DocumentTextSize,
|
|
257
|
+
fromCookie: () => {
|
|
258
|
+
const raw = storage.getItem('plannotator-document-text-size');
|
|
259
|
+
if (raw === null || raw.trim() === '') return undefined;
|
|
260
|
+
const parsed = Number(raw);
|
|
261
|
+
return isDocumentTextSize(parsed) ? parsed : undefined;
|
|
262
|
+
},
|
|
263
|
+
toCookie: (value: DocumentTextSize) =>
|
|
264
|
+
storage.setItem('plannotator-document-text-size', String(value)),
|
|
265
|
+
serverKey: undefined, fromServer: undefined, toServer: undefined,
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The document's font in plan review and annotate (#1657; see
|
|
270
|
+
* `utils/documentFont`): a generic preset or an installed family name.
|
|
271
|
+
* Empty = the theme's font. Cookie-only, like Text size.
|
|
272
|
+
*/
|
|
273
|
+
documentFontFamily: {
|
|
274
|
+
defaultValue: '' as string,
|
|
275
|
+
fromCookie: () =>
|
|
276
|
+
normalizeFontFamilyName(storage.getItem('plannotator-document-font-family') ?? '') || undefined,
|
|
277
|
+
toCookie: (value: string) => storage.setItem('plannotator-document-font-family', value),
|
|
278
|
+
serverKey: undefined, fromServer: undefined, toServer: undefined,
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Enter saves a comment and Shift+Enter adds a new line (#1934). Default
|
|
283
|
+
* OFF: comments save with Cmd/Ctrl+Enter only, as they always have, and
|
|
284
|
+
* Cmd/Ctrl+Enter keeps saving with it on. Read by every comment composer
|
|
285
|
+
* through `isCommentSubmitKey` (`utils/commentSubmitKey`). Cookie-only.
|
|
286
|
+
*/
|
|
287
|
+
enterSavesComment: {
|
|
288
|
+
defaultValue: false as boolean,
|
|
289
|
+
fromCookie: () => {
|
|
290
|
+
const value = storage.getItem('plannotator-enter-saves-comment');
|
|
291
|
+
return value === 'true' ? true : value === 'false' ? false : undefined;
|
|
292
|
+
},
|
|
293
|
+
toCookie: (value: boolean) =>
|
|
294
|
+
storage.setItem('plannotator-enter-saves-comment', String(value)),
|
|
295
|
+
serverKey: undefined, fromServer: undefined, toServer: undefined,
|
|
296
|
+
},
|
|
297
|
+
|
|
195
298
|
vimModeEnabled: {
|
|
196
299
|
// Vim bindings deliberately default OFF. Unmodified letter keys must remain
|
|
197
300
|
// inert for existing users until they explicitly opt into modal document
|
|
@@ -360,6 +463,25 @@ export const SETTINGS = {
|
|
|
360
463
|
serverKey: undefined, fromServer: undefined, toServer: undefined,
|
|
361
464
|
},
|
|
362
465
|
|
|
466
|
+
reviewDefaults: {
|
|
467
|
+
defaultValue: {} as ReviewDefaults,
|
|
468
|
+
fromCookie: () => {
|
|
469
|
+
const value = storage.getItem('plannotator-review-defaults');
|
|
470
|
+
if (!value) return undefined;
|
|
471
|
+
try {
|
|
472
|
+
return parseReviewDefaults(JSON.parse(value));
|
|
473
|
+
} catch {
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
toCookie: (value: ReviewDefaults) =>
|
|
478
|
+
storage.setItem('plannotator-review-defaults', JSON.stringify(value)),
|
|
479
|
+
serverKey: 'reviewDefaults',
|
|
480
|
+
fromServer: (serverConfig: Record<string, unknown>) =>
|
|
481
|
+
parseReviewDefaults(serverConfig.reviewDefaults),
|
|
482
|
+
toServer: (value: ReviewDefaults) => ({ reviewDefaults: value }),
|
|
483
|
+
},
|
|
484
|
+
|
|
363
485
|
defaultDiffType: {
|
|
364
486
|
defaultValue: 'since-base' as 'since-base' | 'local-vs-remote' | 'uncommitted' | 'unstaged' | 'staged' | 'merge-base' | 'all',
|
|
365
487
|
fromCookie: () => {
|
|
@@ -655,6 +777,35 @@ export const SETTINGS = {
|
|
|
655
777
|
}
|
|
656
778
|
},
|
|
657
779
|
},
|
|
780
|
+
/**
|
|
781
|
+
* OpenCode's agent switch after plan approval / review feedback (#1390).
|
|
782
|
+
* null = never chosen: each surface applies its own default (plan approval
|
|
783
|
+
* switches to "build", review feedback stays), see utils/agentSwitch.
|
|
784
|
+
*
|
|
785
|
+
* Server-synced to `agentSwitch` in ~/.plannotator/config.json because some
|
|
786
|
+
* webviews (Glimpse's WKWebView) keep no cookies between runs, and a lost
|
|
787
|
+
* choice silently hands the session to the build agent. The cookie keys
|
|
788
|
+
* are the pre-registry ones, so an upgrade keeps an existing choice.
|
|
789
|
+
*/
|
|
790
|
+
agentSwitch: {
|
|
791
|
+
defaultValue: null as AgentSwitchChoice | null,
|
|
792
|
+
fromCookie: () => readAgentSwitchCookies(),
|
|
793
|
+
toCookie: (v: AgentSwitchChoice | null) => writeAgentSwitchCookies(v),
|
|
794
|
+
serverKey: 'agentSwitch',
|
|
795
|
+
fromServer: (sc: Record<string, unknown>) => {
|
|
796
|
+
const choice = parseAgentSwitchConfig(sc.agentSwitch);
|
|
797
|
+
// Custom with no name means no switch: never pair it with an old name.
|
|
798
|
+
if (!choice || choice.customName || choice.switchTo === 'custom') return choice;
|
|
799
|
+
// A plain choice keeps the name typed in the Custom field earlier, so
|
|
800
|
+
// picking Custom again shows it (the cookie is the only place it lives).
|
|
801
|
+
const customName = storage.getItem(AGENT_SWITCH_CUSTOM_COOKIE);
|
|
802
|
+
return isAgentSwitchName(customName) ? { ...choice, customName } : choice;
|
|
803
|
+
},
|
|
804
|
+
toServer: (v: AgentSwitchChoice | null) => {
|
|
805
|
+
const value = v ? agentSwitchConfigValue(v) : undefined;
|
|
806
|
+
return value === undefined ? {} : { agentSwitch: value };
|
|
807
|
+
},
|
|
808
|
+
},
|
|
658
809
|
/**
|
|
659
810
|
* Where the annotate-mode Agent TUI docks: 'left' (where it always docked),
|
|
660
811
|
* 'right', or 'hidden' (no slot until the user opens it for the session).
|
package/configure.ts
CHANGED
|
@@ -18,6 +18,9 @@ import { configStore } from './config';
|
|
|
18
18
|
import type { ServerSyncFn } from './config/configStore';
|
|
19
19
|
import type { ExternalAnnotationEvent, VaultNode } from './types';
|
|
20
20
|
import { setComponentTooltips } from './components/HostTitle';
|
|
21
|
+
import { setRichCopyEnabled } from './utils/richCopyPolicy';
|
|
22
|
+
import { setDigitHexIssueRefs } from './components/InlineMarkdown';
|
|
23
|
+
import { setRefCardTransport, type RefCardTransport } from './utils/refCard';
|
|
21
24
|
|
|
22
25
|
// One-stop type barrel: every seam contract a host implements is importable
|
|
23
26
|
// from this module, next to configurePlannotatorUI itself.
|
|
@@ -44,6 +47,7 @@ export type {
|
|
|
44
47
|
IdentityGenerator,
|
|
45
48
|
AlertIconRenderer,
|
|
46
49
|
FenceThemeResolver,
|
|
50
|
+
RefCardTransport,
|
|
47
51
|
};
|
|
48
52
|
|
|
49
53
|
type ExternalAnnotationBase = { id: string; source?: string };
|
|
@@ -127,6 +131,27 @@ export interface PlannotatorUIConfig {
|
|
|
127
131
|
* as before. The Plannotator Inbox passes true.
|
|
128
132
|
*/
|
|
129
133
|
componentTooltips?: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Render a digit-only 6/8-digit `#123456` (word boundary, no leading zero)
|
|
136
|
+
* as an issue ref instead of a hex colour swatch, when a ref link can be
|
|
137
|
+
* built (a repo on a known forge). Default: false, swatches exactly as
|
|
138
|
+
* before. The Plannotator Inbox passes true (#1932).
|
|
139
|
+
*/
|
|
140
|
+
digitHexIssueRefs?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Make the selection toolbar's Copy button (Viewer and HtmlViewer) copy rich
|
|
143
|
+
* text: text/html plus text/plain in one clipboard write, and show Copy
|
|
144
|
+
* where `hideCopyButton` hid it (the Viewer on desktop). Default: false,
|
|
145
|
+
* Copy exactly as before. Plannotator's plan review, annotate, code review
|
|
146
|
+
* and Inbox pass true (#1972, `utils/richCopyPolicy`).
|
|
147
|
+
*/
|
|
148
|
+
richCopy?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Fetch an issue / PR / MR hover card's data (`components/RefHoverCard`).
|
|
151
|
+
* Default: none, so no card is drawn and nothing is requested. The
|
|
152
|
+
* Plannotator Inbox passes one over its `/api/inbox/ref` route.
|
|
153
|
+
*/
|
|
154
|
+
refCardTransport?: RefCardTransport;
|
|
130
155
|
/** Re-hydrate settings from the installed (SYNCHRONOUS) storageBackend after install. */
|
|
131
156
|
loadSettingsFromBackend?: boolean;
|
|
132
157
|
}
|
|
@@ -152,6 +177,9 @@ export function configurePlannotatorUI(config: PlannotatorUIConfig): void {
|
|
|
152
177
|
if (config.alertIconRenderer) setAlertIconRenderer(config.alertIconRenderer);
|
|
153
178
|
if (config.fenceTheme) setFenceThemeResolver(config.fenceTheme);
|
|
154
179
|
if (config.componentTooltips !== undefined) setComponentTooltips(config.componentTooltips);
|
|
180
|
+
if (config.digitHexIssueRefs !== undefined) setDigitHexIssueRefs(config.digitHexIssueRefs);
|
|
181
|
+
if (config.richCopy !== undefined) setRichCopyEnabled(config.richCopy);
|
|
182
|
+
if (config.refCardTransport) setRefCardTransport(config.refCardTransport);
|
|
155
183
|
// Re-hydrate AFTER storageBackend is installed (load-bearing order — gated last).
|
|
156
184
|
if (config.loadSettingsFromBackend) configStore.loadFromBackend();
|
|
157
185
|
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @plannotator/ui/contents-rail.css: the document contents rail
|
|
3
|
+
* (components/MarkdownContentsRail.tsx). Its own stylesheet, NOT part of
|
|
4
|
+
* theme.css or styles.css: import it OUTSIDE any CSS cascade layer, after the
|
|
5
|
+
* host's base reset. A layered copy loses to a later unlayered reset (the
|
|
6
|
+
* marker's margin-inline-start: auto, the tick, title and label paddings and
|
|
7
|
+
* margins, the depth indents), and the ticks then sit over the prose.
|
|
8
|
+
*
|
|
9
|
+
* Moved from the Workspaces app's `ws-markdown-contents-*` rules (approved
|
|
10
|
+
* 2026-10-03). A stable marker dock sits in the document's existing right
|
|
11
|
+
* padding without participating in layout. The Contents surface opens to its
|
|
12
|
+
* LEFT and may overlay the document, never a docked comments column. Pointer
|
|
13
|
+
* events stay on the real navigation buttons; selection and scrolling remain
|
|
14
|
+
* untouched. Plain CSS, no Tailwind. Tokens are the host's (Workspaces defines
|
|
15
|
+
* them all); each has a fallback.
|
|
16
|
+
*/
|
|
17
|
+
@keyframes pn-contents-rail-enter {
|
|
18
|
+
from {
|
|
19
|
+
opacity: 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.pn-contents-rail {
|
|
24
|
+
--pn-contents-dock-width: 36px;
|
|
25
|
+
--pn-contents-panel-gap: 6px;
|
|
26
|
+
--pn-contents-panel-width: 256px;
|
|
27
|
+
|
|
28
|
+
position: fixed;
|
|
29
|
+
z-index: 20;
|
|
30
|
+
translate: 0 -50%;
|
|
31
|
+
width: calc(
|
|
32
|
+
var(--pn-contents-dock-width) + var(--pn-contents-panel-gap) + var(--pn-contents-panel-width)
|
|
33
|
+
);
|
|
34
|
+
pointer-events: none;
|
|
35
|
+
animation: pn-contents-rail-enter var(--motion-duration-fast, 120ms)
|
|
36
|
+
var(--ease-enter, cubic-bezier(0.22, 1, 0.36, 1)) both;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.pn-contents-rail-dock {
|
|
40
|
+
position: absolute;
|
|
41
|
+
top: 0;
|
|
42
|
+
right: 0;
|
|
43
|
+
bottom: 0;
|
|
44
|
+
width: var(--pn-contents-dock-width);
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* The floating surface (Workspaces' `floating-surface`: popover fill, no drawn
|
|
49
|
+
* border, the edge is the hairline ring in the menu shadow). */
|
|
50
|
+
.pn-contents-rail-surface {
|
|
51
|
+
position: absolute;
|
|
52
|
+
top: -40px;
|
|
53
|
+
bottom: -8px;
|
|
54
|
+
left: 0;
|
|
55
|
+
width: var(--pn-contents-panel-width);
|
|
56
|
+
border-radius: var(--geometry-floating-radius, 10px);
|
|
57
|
+
background: var(--popover);
|
|
58
|
+
color: var(--popover-foreground);
|
|
59
|
+
box-shadow: var(
|
|
60
|
+
--shadow-menu,
|
|
61
|
+
0 0 0 1px color-mix(in oklch, var(--foreground) 8%, transparent),
|
|
62
|
+
0 12px 32px -8px color-mix(in oklch, var(--foreground) 22%, transparent)
|
|
63
|
+
);
|
|
64
|
+
transform: translateX(4px) scale(0.98);
|
|
65
|
+
transform-origin: right center;
|
|
66
|
+
opacity: 0;
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
transition:
|
|
69
|
+
opacity var(--motion-duration-basic, 150ms) cubic-bezier(0.23, 1, 0.32, 1),
|
|
70
|
+
transform var(--motion-duration-basic, 150ms) cubic-bezier(0.23, 1, 0.32, 1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* The section eyebrow (Workspaces' EYEBROW: 12 px, weight 500). */
|
|
74
|
+
.pn-contents-rail-title {
|
|
75
|
+
position: absolute;
|
|
76
|
+
top: -32px;
|
|
77
|
+
left: 8px;
|
|
78
|
+
display: flex;
|
|
79
|
+
width: calc(var(--pn-contents-panel-width) - 16px);
|
|
80
|
+
height: 28px;
|
|
81
|
+
align-items: center;
|
|
82
|
+
padding-inline: 4px;
|
|
83
|
+
color: var(--foreground);
|
|
84
|
+
font-size: 12px;
|
|
85
|
+
font-weight: 500;
|
|
86
|
+
line-height: 20px;
|
|
87
|
+
transform: translateX(4px);
|
|
88
|
+
opacity: 0;
|
|
89
|
+
pointer-events: none;
|
|
90
|
+
transition:
|
|
91
|
+
opacity var(--motion-duration-basic, 150ms) cubic-bezier(0.23, 1, 0.32, 1),
|
|
92
|
+
transform var(--motion-duration-basic, 150ms) cubic-bezier(0.23, 1, 0.32, 1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* A host's one control at the right of the open panel's title (panelAction). */
|
|
96
|
+
.pn-contents-rail-action {
|
|
97
|
+
position: absolute;
|
|
98
|
+
z-index: 2;
|
|
99
|
+
top: -32px;
|
|
100
|
+
right: calc(var(--pn-contents-dock-width) + var(--pn-contents-panel-gap) + 8px);
|
|
101
|
+
display: flex;
|
|
102
|
+
height: 28px;
|
|
103
|
+
align-items: center;
|
|
104
|
+
pointer-events: auto;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-surface,
|
|
108
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-title {
|
|
109
|
+
transform: translateX(0) scale(1);
|
|
110
|
+
opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-title {
|
|
114
|
+
transition-delay: var(--motion-duration-basic, 150ms), var(--motion-duration-basic, 150ms);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.pn-contents-rail-list {
|
|
118
|
+
position: relative;
|
|
119
|
+
z-index: 1;
|
|
120
|
+
width: 100%;
|
|
121
|
+
/* The closed rail renders at most ten sampled document spans, so it never
|
|
122
|
+
* needs an internal scroll position. Shorter maps retain their natural
|
|
123
|
+
* height and stay centered by the rail's fixed midpoint anchor. */
|
|
124
|
+
max-height: 160px;
|
|
125
|
+
overflow-x: hidden;
|
|
126
|
+
overflow-y: hidden;
|
|
127
|
+
overscroll-behavior: contain;
|
|
128
|
+
scrollbar-width: none;
|
|
129
|
+
touch-action: none;
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
transition: max-height var(--motion-duration-basic, 150ms) var(--ease-exit, cubic-bezier(0.4, 0, 0.2, 1));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-list {
|
|
135
|
+
max-height: min(62vh, 560px);
|
|
136
|
+
overflow-y: auto;
|
|
137
|
+
transition-duration: var(--motion-duration-relaxed, 300ms);
|
|
138
|
+
transition-timing-function: var(--ease-enter, cubic-bezier(0.22, 1, 0.36, 1));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.pn-contents-rail-list::-webkit-scrollbar {
|
|
142
|
+
display: none;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.pn-contents-rail-marker {
|
|
146
|
+
--pn-contents-line-width: 26px;
|
|
147
|
+
|
|
148
|
+
position: relative;
|
|
149
|
+
display: flex;
|
|
150
|
+
width: var(--pn-contents-dock-width);
|
|
151
|
+
height: 16px;
|
|
152
|
+
flex-direction: row-reverse;
|
|
153
|
+
align-items: center;
|
|
154
|
+
justify-content: flex-start;
|
|
155
|
+
overflow: hidden;
|
|
156
|
+
margin-inline-start: auto;
|
|
157
|
+
padding: 0;
|
|
158
|
+
border: 0;
|
|
159
|
+
background: transparent;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
pointer-events: auto;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-marker {
|
|
165
|
+
width: 100%;
|
|
166
|
+
overflow: visible;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.pn-contents-rail-tick {
|
|
170
|
+
display: flex;
|
|
171
|
+
width: var(--pn-contents-dock-width);
|
|
172
|
+
flex: none;
|
|
173
|
+
align-items: center;
|
|
174
|
+
justify-content: flex-start;
|
|
175
|
+
padding-inline-start: 4px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.pn-contents-rail-line {
|
|
179
|
+
--marker-progress: 0;
|
|
180
|
+
|
|
181
|
+
display: block;
|
|
182
|
+
width: var(--pn-contents-line-width);
|
|
183
|
+
height: 2px;
|
|
184
|
+
transform: scaleX(calc(0.6154 + 0.3846 * var(--marker-progress)));
|
|
185
|
+
transform-origin: left center;
|
|
186
|
+
background: var(--muted-foreground);
|
|
187
|
+
opacity: 0.4;
|
|
188
|
+
pointer-events: none;
|
|
189
|
+
transition: none;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* The frequently scanned closed map updates immediately. The local lens is
|
|
193
|
+
* motion reserved for the explicitly opened Contents state. */
|
|
194
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-line {
|
|
195
|
+
transition:
|
|
196
|
+
transform var(--motion-duration-basic, 150ms)
|
|
197
|
+
linear(0, 0.398 10%, 0.682 20%, 0.843 30%, 0.925 40%, 0.972 50%, 1.004 60%, 1.008 70%, 1.003 80%, 1),
|
|
198
|
+
opacity var(--motion-duration-basic, 150ms) ease,
|
|
199
|
+
background-color var(--motion-duration-basic, 150ms) ease;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* H1:H2 is 12px:10px with the shorter line inset 2px toward the document, at
|
|
203
|
+
* a deliberately longer scale, then the same step for H3 and H4. */
|
|
204
|
+
.pn-contents-rail-marker[data-level="2"] {
|
|
205
|
+
--pn-contents-line-width: 22px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.pn-contents-rail-marker[data-level="3"] {
|
|
209
|
+
--pn-contents-line-width: 18px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.pn-contents-rail-marker[data-level="4"] {
|
|
213
|
+
--pn-contents-line-width: 14px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.pn-contents-rail-marker[data-current] .pn-contents-rail-line {
|
|
217
|
+
background: var(--primary);
|
|
218
|
+
opacity: 0.68;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.pn-contents-rail-marker[data-highlighted] .pn-contents-rail-line,
|
|
222
|
+
.pn-contents-rail-marker:focus-visible .pn-contents-rail-line {
|
|
223
|
+
--marker-progress: 1;
|
|
224
|
+
background: var(--foreground);
|
|
225
|
+
opacity: 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.pn-contents-rail-marker:has(+ .pn-contents-rail-marker[data-highlighted]) .pn-contents-rail-line,
|
|
229
|
+
.pn-contents-rail-marker:has(+ .pn-contents-rail-marker:focus-visible) .pn-contents-rail-line,
|
|
230
|
+
.pn-contents-rail-marker[data-highlighted] + .pn-contents-rail-marker .pn-contents-rail-line,
|
|
231
|
+
.pn-contents-rail-marker:focus-visible + .pn-contents-rail-marker .pn-contents-rail-line {
|
|
232
|
+
--marker-progress: 0.7;
|
|
233
|
+
opacity: 0.78;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.pn-contents-rail-marker:has(+ .pn-contents-rail-marker + .pn-contents-rail-marker[data-highlighted]) .pn-contents-rail-line,
|
|
237
|
+
.pn-contents-rail-marker:has(+ .pn-contents-rail-marker + .pn-contents-rail-marker:focus-visible) .pn-contents-rail-line,
|
|
238
|
+
.pn-contents-rail-marker[data-highlighted] + .pn-contents-rail-marker + .pn-contents-rail-marker .pn-contents-rail-line,
|
|
239
|
+
.pn-contents-rail-marker:focus-visible + .pn-contents-rail-marker + .pn-contents-rail-marker .pn-contents-rail-line {
|
|
240
|
+
--marker-progress: 0.4;
|
|
241
|
+
opacity: 0.58;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.pn-contents-rail-marker:has(+ .pn-contents-rail-marker + .pn-contents-rail-marker + .pn-contents-rail-marker[data-highlighted]) .pn-contents-rail-line,
|
|
245
|
+
.pn-contents-rail-marker:has(+ .pn-contents-rail-marker + .pn-contents-rail-marker + .pn-contents-rail-marker:focus-visible) .pn-contents-rail-line,
|
|
246
|
+
.pn-contents-rail-marker[data-highlighted] + .pn-contents-rail-marker + .pn-contents-rail-marker + .pn-contents-rail-marker .pn-contents-rail-line,
|
|
247
|
+
.pn-contents-rail-marker:focus-visible + .pn-contents-rail-marker + .pn-contents-rail-marker + .pn-contents-rail-marker .pn-contents-rail-line {
|
|
248
|
+
--marker-progress: 0.2;
|
|
249
|
+
opacity: 0.46;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.pn-contents-rail-label {
|
|
253
|
+
display: block;
|
|
254
|
+
width: calc(var(--pn-contents-panel-width) - 16px);
|
|
255
|
+
height: 16px;
|
|
256
|
+
min-width: 0;
|
|
257
|
+
flex: none;
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
margin-inline-end: calc(var(--pn-contents-panel-gap) + 8px);
|
|
260
|
+
padding-inline: 4px;
|
|
261
|
+
border-radius: var(--geometry-structure-radius, 4px);
|
|
262
|
+
background: transparent;
|
|
263
|
+
color: var(--muted-foreground);
|
|
264
|
+
font-size: inherit;
|
|
265
|
+
font-weight: 400;
|
|
266
|
+
line-height: 16px;
|
|
267
|
+
text-align: left;
|
|
268
|
+
text-overflow: ellipsis;
|
|
269
|
+
white-space: nowrap;
|
|
270
|
+
opacity: 0;
|
|
271
|
+
transform: translateX(-4px);
|
|
272
|
+
pointer-events: none;
|
|
273
|
+
transition:
|
|
274
|
+
opacity var(--motion-duration-basic, 150ms) cubic-bezier(0.23, 1, 0.32, 1),
|
|
275
|
+
transform var(--motion-duration-basic, 150ms) cubic-bezier(0.23, 1, 0.32, 1),
|
|
276
|
+
color var(--motion-duration-fast, 120ms) ease,
|
|
277
|
+
background-color var(--motion-duration-fast, 120ms) ease;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.pn-contents-rail-marker[data-depth="1"] .pn-contents-rail-label {
|
|
281
|
+
padding-inline-start: 16px;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.pn-contents-rail-marker[data-depth="2"] .pn-contents-rail-label {
|
|
285
|
+
padding-inline-start: 28px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.pn-contents-rail-marker[data-depth="3"] .pn-contents-rail-label {
|
|
289
|
+
padding-inline-start: 40px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-label {
|
|
293
|
+
opacity: 1;
|
|
294
|
+
transform: translateX(0);
|
|
295
|
+
transition-delay: var(--motion-duration-basic, 150ms), var(--motion-duration-basic, 150ms), 0ms, 0ms;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.pn-contents-rail-marker[data-current] .pn-contents-rail-label {
|
|
299
|
+
color: var(--foreground);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.pn-contents-rail-marker[data-highlighted] .pn-contents-rail-label,
|
|
303
|
+
.pn-contents-rail-marker:focus-visible .pn-contents-rail-label {
|
|
304
|
+
background: var(--surface-hover, color-mix(in oklch, var(--foreground) 6%, transparent));
|
|
305
|
+
color: var(--foreground);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.pn-contents-rail:focus-within .pn-contents-rail-surface,
|
|
309
|
+
.pn-contents-rail:focus-within .pn-contents-rail-title,
|
|
310
|
+
.pn-contents-rail:focus-within .pn-contents-rail-label,
|
|
311
|
+
.pn-contents-rail:focus-within .pn-contents-rail-list {
|
|
312
|
+
transition-duration: 0ms;
|
|
313
|
+
transition-delay: 0ms;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@media (prefers-reduced-motion: reduce) {
|
|
317
|
+
.pn-contents-rail {
|
|
318
|
+
animation: none;
|
|
319
|
+
}
|
|
320
|
+
.pn-contents-rail-line {
|
|
321
|
+
transition: none;
|
|
322
|
+
}
|
|
323
|
+
.pn-contents-rail-surface,
|
|
324
|
+
.pn-contents-rail-title,
|
|
325
|
+
.pn-contents-rail-label,
|
|
326
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-surface,
|
|
327
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-title,
|
|
328
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-label {
|
|
329
|
+
transform: none;
|
|
330
|
+
transition: opacity var(--motion-duration-fast, 120ms) ease;
|
|
331
|
+
transition-delay: 0ms;
|
|
332
|
+
}
|
|
333
|
+
.pn-contents-rail-list,
|
|
334
|
+
.pn-contents-rail[data-revealed] .pn-contents-rail-list {
|
|
335
|
+
transition: none;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback } from "react";
|
|
1
|
+
import { useState, useCallback, useRef } from "react";
|
|
2
2
|
import { parseCodePath } from "@plannotator/core/code-file";
|
|
3
3
|
|
|
4
4
|
interface CodeFileState {
|
|
@@ -9,6 +9,7 @@ interface CodeFileState {
|
|
|
9
9
|
requestedPath?: string;
|
|
10
10
|
line?: number;
|
|
11
11
|
lineEnd?: number;
|
|
12
|
+
revealKey?: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
interface UseCodeFilePopoutOptions {
|
|
@@ -29,6 +30,8 @@ export interface UseCodeFilePopoutReturn {
|
|
|
29
30
|
requestedPath?: string;
|
|
30
31
|
line?: number;
|
|
31
32
|
lineEnd?: number;
|
|
33
|
+
/** Changes on every open, so reopening the same file:line scrolls to it again. */
|
|
34
|
+
revealKey?: number;
|
|
32
35
|
} | null;
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -38,6 +41,7 @@ export function useCodeFilePopout(
|
|
|
38
41
|
const { buildUrl } = options;
|
|
39
42
|
const [state, setState] = useState<CodeFileState | null>(null);
|
|
40
43
|
const [isLoading, setIsLoading] = useState(false);
|
|
44
|
+
const revealCount = useRef(0);
|
|
41
45
|
|
|
42
46
|
const close = useCallback(() => {
|
|
43
47
|
setState(null);
|
|
@@ -75,6 +79,7 @@ export function useCodeFilePopout(
|
|
|
75
79
|
prerenderedHTML: data.prerenderedHTML,
|
|
76
80
|
line: data.line ?? parsed.line,
|
|
77
81
|
lineEnd: data.lineEnd ?? parsed.lineEnd,
|
|
82
|
+
revealKey: ++revealCount.current,
|
|
78
83
|
});
|
|
79
84
|
setIsLoading(false);
|
|
80
85
|
} catch {
|
|
@@ -105,6 +110,7 @@ export function useCodeFilePopout(
|
|
|
105
110
|
requestedPath: state.requestedPath,
|
|
106
111
|
line: state.line,
|
|
107
112
|
lineEnd: state.lineEnd,
|
|
113
|
+
revealKey: state.revealKey,
|
|
108
114
|
}
|
|
109
115
|
: null,
|
|
110
116
|
};
|