@plannotator/ui 0.53.0 → 0.54.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 +284 -27
- package/README.md +84 -14
- package/components/AgentToolAnnouncementDialog.tsx +1 -1
- package/components/AgentsTab.tsx +1 -1
- package/components/AnnotationPanel.tsx +177 -34
- package/components/AnnotationSidebar.tsx +1 -1
- package/components/AnnotationToolbar.tsx +64 -9
- package/components/AnnotationToolstrip.tsx +45 -40
- package/components/ApproveDropdown.tsx +7 -9
- package/components/AskSessionAnnouncementDialog.tsx +1 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/BlockRenderer.tsx +19 -5
- package/components/CodeFilePopout.tsx +140 -5
- package/components/CommentPopover.tsx +46 -8
- package/components/DecisionControl.tsx +31 -5
- package/components/DiagramBlock.tsx +17 -12
- package/components/EditorAnnotationCard.tsx +1 -1
- package/components/ExportModal.tsx +77 -7
- package/components/HostTitle.tsx +0 -4
- package/components/HtmlSurfaceControls.tsx +2 -1
- package/components/ImageAnnotator/Toolbar.tsx +25 -1
- package/components/ImageAnnotator/index.tsx +24 -4
- package/components/ImageLightbox.tsx +658 -120
- 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 +152 -24
- 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/MentionPicker.tsx +51 -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/SkillReferenceMenu.tsx +39 -12
- 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 +316 -47
- 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 +522 -62
- package/components/blocks/QuestionContext.tsx +3 -0
- 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 +69 -6
- package/components/html-viewer/bridge-script.asset.js +504 -12
- package/components/html-viewer/bridge-script.lite.ts +1 -1
- package/components/html-viewer/bridge-script.ts +545 -12
- package/components/html-viewer/useHtmlAnnotation.ts +125 -0
- package/components/imageZoom.ts +265 -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/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/useLinkedDoc.ts +226 -13
- package/hooks/useMentionAutocomplete.ts +1 -1
- package/hooks/usePackageChangesSetting.ts +90 -0
- package/hooks/usePlanLookDialog.ts +67 -0
- package/hooks/useSharing.ts +11 -7
- package/hooks/useSkillReferenceAutocomplete.ts +7 -3
- package/hooks/useThemePaletteHalves.ts +37 -0
- package/hooks/useUpdateCheck.ts +22 -4
- package/hooks/useUpdateToast.ts +1 -1
- package/hooks/useVimSelection.ts +9 -0
- package/package.json +23 -22
- 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 +14 -6
- 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/annotationOwnsHighlight.ts +4 -2
- package/utils/asciidocParser.ts +570 -0
- package/utils/autoUpdateNotice.ts +24 -0
- package/utils/belowTitleSlot.ts +174 -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/inboxAnnouncement.ts +78 -0
- package/utils/lookAndFeelAnnouncement.ts +82 -5
- package/utils/markdownExtensions.ts +2 -2
- package/utils/mentions.ts +34 -5
- 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/hooks/useSharing.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
12
|
-
import { Annotation, type ImageAttachment } from '../types';
|
|
12
|
+
import { Annotation, type DocumentRenderAs, type ImageAttachment } from '../types';
|
|
13
13
|
import {
|
|
14
14
|
type SharePayload,
|
|
15
15
|
parseShareHash,
|
|
@@ -109,8 +109,10 @@ export function useSharing(
|
|
|
109
109
|
resolveRawHtmlForShare?: () => Promise<string | null>,
|
|
110
110
|
setRawHtml?: (h: string) => void,
|
|
111
111
|
setShareHtml?: (h: string) => void,
|
|
112
|
-
setRenderAs?: (m:
|
|
112
|
+
setRenderAs?: (m: DocumentRenderAs) => void,
|
|
113
113
|
contentRevision = 0,
|
|
114
|
+
/** Render kind of the shared document; `asciidoc` travels as `r: 'asciidoc'`. */
|
|
115
|
+
renderAs?: DocumentRenderAs,
|
|
114
116
|
): UseSharingResult {
|
|
115
117
|
const [isSharedSession, setIsSharedSession] = useState(false);
|
|
116
118
|
const [isLoadingShared, setIsLoadingShared] = useState(true);
|
|
@@ -136,6 +138,7 @@ export function useSharing(
|
|
|
136
138
|
pasteApiUrl,
|
|
137
139
|
rawHtml,
|
|
138
140
|
contentRevision,
|
|
141
|
+
renderAs,
|
|
139
142
|
]);
|
|
140
143
|
const latestShareRequestContextRef = useRef(shareRequestContext);
|
|
141
144
|
latestShareRequestContextRef.current = shareRequestContext;
|
|
@@ -178,7 +181,7 @@ export function useSharing(
|
|
|
178
181
|
setMarkdown('');
|
|
179
182
|
} else {
|
|
180
183
|
setMarkdown(payload.p);
|
|
181
|
-
setRenderAs?.('markdown');
|
|
184
|
+
setRenderAs?.(payload.r === 'asciidoc' ? 'asciidoc' : 'markdown');
|
|
182
185
|
setRawHtml?.('');
|
|
183
186
|
setShareHtml?.('');
|
|
184
187
|
}
|
|
@@ -226,7 +229,7 @@ export function useSharing(
|
|
|
226
229
|
setMarkdown('');
|
|
227
230
|
} else {
|
|
228
231
|
setMarkdown(payload.p);
|
|
229
|
-
setRenderAs?.('markdown');
|
|
232
|
+
setRenderAs?.(payload.r === 'asciidoc' ? 'asciidoc' : 'markdown');
|
|
230
233
|
setRawHtml?.('');
|
|
231
234
|
setShareHtml?.('');
|
|
232
235
|
}
|
|
@@ -290,7 +293,7 @@ export function useSharing(
|
|
|
290
293
|
const refreshShareUrl = useCallback(async () => {
|
|
291
294
|
const requestContext = shareRequestContext;
|
|
292
295
|
try {
|
|
293
|
-
const url = await generateShareUrl(markdown, annotations, globalAttachments, shareBaseUrl, rawHtml);
|
|
296
|
+
const url = await generateShareUrl(markdown, annotations, globalAttachments, shareBaseUrl, rawHtml, renderAs);
|
|
294
297
|
if (latestShareRequestContextRef.current !== requestContext) return;
|
|
295
298
|
setShareUrl(url ?? '');
|
|
296
299
|
setShareUrlSize(url ? formatUrlSize(url) : '');
|
|
@@ -300,7 +303,7 @@ export function useSharing(
|
|
|
300
303
|
setShareUrl('');
|
|
301
304
|
setShareUrlSize('');
|
|
302
305
|
}
|
|
303
|
-
}, [markdown, annotations, globalAttachments, shareBaseUrl, rawHtml, shareRequestContext]);
|
|
306
|
+
}, [markdown, annotations, globalAttachments, shareBaseUrl, rawHtml, renderAs, shareRequestContext]);
|
|
304
307
|
|
|
305
308
|
// Auto-refresh share URL when dependencies change
|
|
306
309
|
useEffect(() => {
|
|
@@ -372,6 +375,7 @@ export function useSharing(
|
|
|
372
375
|
globalAttachments,
|
|
373
376
|
{ pasteApiUrl, shareBaseUrl },
|
|
374
377
|
htmlForShare,
|
|
378
|
+
renderAs,
|
|
375
379
|
);
|
|
376
380
|
|
|
377
381
|
if (latestShareRequestContextRef.current !== requestContext) return null;
|
|
@@ -398,7 +402,7 @@ export function useSharing(
|
|
|
398
402
|
} finally {
|
|
399
403
|
if (latestShareRequestContextRef.current === requestContext) setIsGeneratingShortUrl(false);
|
|
400
404
|
}
|
|
401
|
-
}, [markdown, annotations, globalAttachments, shareBaseUrl, pasteApiUrl, rawHtml, resolveRawHtmlForShare, shareRequestContext]);
|
|
405
|
+
}, [markdown, annotations, globalAttachments, shareBaseUrl, pasteApiUrl, rawHtml, resolveRawHtmlForShare, renderAs, shareRequestContext]);
|
|
402
406
|
|
|
403
407
|
// Import annotations from a teammate's share URL (supports both hash-based and short /p/<id> URLs)
|
|
404
408
|
const importFromShareUrl = useCallback(async (url: string): Promise<ImportResult> => {
|
|
@@ -59,14 +59,18 @@ export interface UseSkillReferenceAutocompleteResult {
|
|
|
59
59
|
* active row and dismisses the menu when the user has engaged with it (a row
|
|
60
60
|
* is active or a query was typed); on a bare-trigger menu with no engagement
|
|
61
61
|
* it passes through, so Escape still closes the composer in one press.
|
|
62
|
+
* `bareEscapeDismisses` (default off) makes Escape close any open menu first,
|
|
63
|
+
* for a composer where Escape costs more than a press (the Inbox's compose
|
|
64
|
+
* dialog asks whether to keep a draft).
|
|
62
65
|
*/
|
|
63
66
|
export function useSkillReferenceAutocomplete(options: {
|
|
64
67
|
text: string;
|
|
65
68
|
setText: (text: string) => void;
|
|
66
69
|
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
|
|
67
70
|
enabled: boolean;
|
|
71
|
+
bareEscapeDismisses?: boolean;
|
|
68
72
|
}): UseSkillReferenceAutocompleteResult {
|
|
69
|
-
const { text, setText, textareaRef, enabled } = options;
|
|
73
|
+
const { text, setText, textareaRef, enabled, bareEscapeDismisses = false } = options;
|
|
70
74
|
// Seed from the memory cache only when the surface opted in — a disabled
|
|
71
75
|
// composer must stay inert even after another surface warmed the catalog.
|
|
72
76
|
const [catalog, setCatalog] = useState<SkillCatalogEntry[]>(() =>
|
|
@@ -205,7 +209,7 @@ export function useSkillReferenceAutocomplete(options: {
|
|
|
205
209
|
// Only consume Escape when the user engaged with the menu (typed a
|
|
206
210
|
// query or activated a row). A bare-trigger menu the user ignored
|
|
207
211
|
// must not cost an extra Escape on the way to closing the composer.
|
|
208
|
-
if (boundedActive === null && trigger.query.length === 0) return false;
|
|
212
|
+
if (!bareEscapeDismisses && boundedActive === null && trigger.query.length === 0) return false;
|
|
209
213
|
e.preventDefault();
|
|
210
214
|
e.stopPropagation();
|
|
211
215
|
setActiveIndex(null);
|
|
@@ -215,7 +219,7 @@ export function useSkillReferenceAutocomplete(options: {
|
|
|
215
219
|
return false;
|
|
216
220
|
}
|
|
217
221
|
},
|
|
218
|
-
[boundedActive, items.length, open, select, trigger],
|
|
222
|
+
[bareEscapeDismisses, boundedActive, items.length, open, select, trigger],
|
|
219
223
|
);
|
|
220
224
|
|
|
221
225
|
const humanOnlyReferences = useMemo(
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useTheme } from '../components/ThemeProvider';
|
|
3
|
+
import { themesForHalf, type ThemeHalf, type ThemeInfo } from '../utils/themeRegistry';
|
|
4
|
+
|
|
5
|
+
export interface ThemePaletteHalves {
|
|
6
|
+
/** Which half the picker assigns to. */
|
|
7
|
+
half: ThemeHalf;
|
|
8
|
+
setHalf: (half: ThemeHalf) => void;
|
|
9
|
+
/** The palette each half holds now. */
|
|
10
|
+
pair: Record<ThemeHalf, string>;
|
|
11
|
+
/** The palettes the current half can take, from the registry (so a new palette shows by itself). */
|
|
12
|
+
themes: ThemeInfo[];
|
|
13
|
+
nameOf: (id: string) => string;
|
|
14
|
+
/** Assign a palette to a half (ThemeProvider's setHalfTheme). */
|
|
15
|
+
choose: (half: ThemeHalf, id: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The palette picker's state, without its layout: the half being assigned
|
|
20
|
+
* (it follows the mode on screen, so opening Settings in dark mode edits the
|
|
21
|
+
* dark half first), the pair, and the palettes that half can take. ThemeTab
|
|
22
|
+
* draws it for plan review, annotate and code review; the Inbox draws the
|
|
23
|
+
* same choices in its own Settings rows.
|
|
24
|
+
*/
|
|
25
|
+
export function useThemePaletteHalves(): ThemePaletteHalves {
|
|
26
|
+
const { lightTheme, darkTheme, setHalfTheme, availableThemes, preferredMode } = useTheme();
|
|
27
|
+
const [half, setHalf] = useState<ThemeHalf>(preferredMode);
|
|
28
|
+
useEffect(() => setHalf(preferredMode), [preferredMode]);
|
|
29
|
+
return {
|
|
30
|
+
half,
|
|
31
|
+
setHalf,
|
|
32
|
+
pair: { light: lightTheme, dark: darkTheme },
|
|
33
|
+
themes: themesForHalf(availableThemes, half),
|
|
34
|
+
nameOf: (id: string) => availableThemes.find(theme => theme.id === id)?.name ?? id,
|
|
35
|
+
choose: setHalfTheme,
|
|
36
|
+
};
|
|
37
|
+
}
|
package/hooks/useUpdateCheck.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo } from 'react';
|
|
2
2
|
import { getItem, setItem } from '../utils/storage';
|
|
3
|
-
import type { UpdateAction, UpdateAdvert } from '../utils/updateNotice';
|
|
3
|
+
import type { UpdateAction, UpdateAdvert, UpdateCheckFailingAdvert } from '../utils/updateNotice';
|
|
4
4
|
|
|
5
5
|
declare const __APP_VERSION__: string;
|
|
6
6
|
|
|
@@ -21,6 +21,11 @@ export interface UpdateInfo {
|
|
|
21
21
|
action?: UpdateAction;
|
|
22
22
|
/** The command for this host, with the remembered install flags. */
|
|
23
23
|
command?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Checking for updates has failed for hours (no update known): the Options
|
|
26
|
+
* menu says so with `command`. `updateAvailable` is false then.
|
|
27
|
+
*/
|
|
28
|
+
checkFailing?: { since: string; error: string };
|
|
24
29
|
dismiss: () => void;
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -35,13 +40,13 @@ function isDismissedVersion(latestVersion: string): boolean {
|
|
|
35
40
|
/**
|
|
36
41
|
* The Options menu's update state, from the server's advert (`updateAvailable`
|
|
37
42
|
* on /api/plan and /api/diff). The browser never asks GitHub: the machine
|
|
38
|
-
* checks
|
|
43
|
+
* checks every few minutes (packages/shared/update-check.ts) and the server passes
|
|
39
44
|
* the answer on, or nothing when update checks are off, in a dev build, or on
|
|
40
45
|
* a surface with no server.
|
|
41
46
|
*
|
|
42
47
|
* `?preview-update=0.29.0` still simulates an update, for screenshots.
|
|
43
48
|
*/
|
|
44
|
-
export function useUpdateCheck(advert?: UpdateAdvert | null): UpdateInfo | null {
|
|
49
|
+
export function useUpdateCheck(advert?: UpdateAdvert | null, failing?: UpdateCheckFailingAdvert | null): UpdateInfo | null {
|
|
45
50
|
const preview = useMemo(() => {
|
|
46
51
|
if (typeof window === 'undefined') return null;
|
|
47
52
|
const version = new URLSearchParams(window.location.search).get('preview-update');
|
|
@@ -56,7 +61,20 @@ export function useUpdateCheck(advert?: UpdateAdvert | null): UpdateInfo | null
|
|
|
56
61
|
setDismissedVersion(latestVersion);
|
|
57
62
|
}, [latestVersion]);
|
|
58
63
|
|
|
59
|
-
if (!latestVersion)
|
|
64
|
+
if (!latestVersion) {
|
|
65
|
+
if (!failing) return null;
|
|
66
|
+
const current = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0';
|
|
67
|
+
return {
|
|
68
|
+
currentVersion: current,
|
|
69
|
+
latestVersion: current,
|
|
70
|
+
updateAvailable: false,
|
|
71
|
+
dismissed: true,
|
|
72
|
+
releaseUrl: 'https://github.com/backnotprop/plannotator/releases',
|
|
73
|
+
command: failing.command,
|
|
74
|
+
checkFailing: { since: failing.since, error: failing.error },
|
|
75
|
+
dismiss,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
60
78
|
const currentVersion = advert?.current ?? (typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0');
|
|
61
79
|
return {
|
|
62
80
|
currentVersion,
|
package/hooks/useUpdateToast.ts
CHANGED
|
@@ -21,7 +21,7 @@ function isTypingSomewhere(): boolean {
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* The review windows' "a new version is out" toast (design §4.5): from the
|
|
24
|
-
* server's advert only, at most once per version and once
|
|
24
|
+
* server's advert only, at most once per version and once a day
|
|
25
25
|
* (claimUpdateToast), queued behind the first-run dialogs (`blocked`) and
|
|
26
26
|
* never while a composer or dialog has focus. A blocked attempt is not
|
|
27
27
|
* consumed: it looks again, and gives up for this load after two minutes.
|
package/hooks/useVimSelection.ts
CHANGED
|
@@ -317,8 +317,16 @@ export function useVimSelection({
|
|
|
317
317
|
return next;
|
|
318
318
|
}, [containerRef, setState]);
|
|
319
319
|
|
|
320
|
+
// Whether vim was on since the last reset. The disabled branch below resets
|
|
321
|
+
// only when vim actually goes from on to off: on a mount where vim was never
|
|
322
|
+
// on there is nothing to reset, and `window.getSelection()` in
|
|
323
|
+
// `clearSelectionWithin` forces a layout on every document open.
|
|
324
|
+
const wasEnabledRef = useRef(enabled);
|
|
325
|
+
|
|
320
326
|
useEffect(() => {
|
|
321
327
|
if (!enabled) {
|
|
328
|
+
if (!wasEnabledRef.current) return;
|
|
329
|
+
wasEnabledRef.current = false;
|
|
322
330
|
setState(createInitialVimSelectionState());
|
|
323
331
|
setFocused(false);
|
|
324
332
|
setHelpOpen(false);
|
|
@@ -326,6 +334,7 @@ export function useVimSelection({
|
|
|
326
334
|
clearSelectionWithin(containerRef.current);
|
|
327
335
|
return;
|
|
328
336
|
}
|
|
337
|
+
wasEnabledRef.current = true;
|
|
329
338
|
if (!focused) return;
|
|
330
339
|
if (restoringFocusRef.current) {
|
|
331
340
|
restoringFocusRef.current = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"./components/ImageAnnotator": "./components/ImageAnnotator/index.tsx",
|
|
13
13
|
"./components/html-viewer": "./components/html-viewer/index.ts",
|
|
14
14
|
"./components/diagram": "./components/diagram/index.ts",
|
|
15
|
+
"./components/diagramLanguages": "./components/diagramLanguages.ts",
|
|
15
16
|
"./components/diagram/*": "./components/diagram/*.tsx",
|
|
16
17
|
"./components/html-viewer/bridge-script": "./components/html-viewer/bridge-script.ts",
|
|
17
18
|
"./components/html-viewer/bridge-script.asset.js": "./components/html-viewer/bridge-script.asset.js",
|
|
@@ -57,42 +58,42 @@
|
|
|
57
58
|
"!test-setup"
|
|
58
59
|
],
|
|
59
60
|
"dependencies": {
|
|
60
|
-
"@base-ui/react": "^1.
|
|
61
|
+
"@base-ui/react": "^1.9.0",
|
|
61
62
|
"@codemirror/autocomplete": "^6.20.3",
|
|
62
|
-
"@codemirror/commands": "^6.11.
|
|
63
|
+
"@codemirror/commands": "^6.11.1",
|
|
63
64
|
"@codemirror/lang-javascript": "^6.2.5",
|
|
64
65
|
"@codemirror/lang-json": "^6.0.2",
|
|
65
66
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
66
67
|
"@codemirror/lang-python": "^6.2.1",
|
|
67
68
|
"@codemirror/lang-yaml": "^6.1.3",
|
|
68
|
-
"@codemirror/language": "^6.
|
|
69
|
-
"@codemirror/legacy-modes": "^6.5.
|
|
69
|
+
"@codemirror/language": "^6.13.1",
|
|
70
|
+
"@codemirror/legacy-modes": "^6.5.5",
|
|
70
71
|
"@codemirror/merge": "^6.12.2",
|
|
71
72
|
"@codemirror/search": "^6.7.2",
|
|
72
|
-
"@codemirror/state": "^6.7.
|
|
73
|
-
"@codemirror/view": "^6.43.
|
|
73
|
+
"@codemirror/state": "^6.7.6",
|
|
74
|
+
"@codemirror/view": "^6.43.14",
|
|
74
75
|
"@fontsource-variable/geist-mono": "5.3.0",
|
|
75
76
|
"@fontsource-variable/inter": "^5.3.0",
|
|
76
|
-
"@lezer/common": "^1.5.
|
|
77
|
-
"@lezer/highlight": "^1.2.
|
|
78
|
-
"@pierre/diffs": "1.
|
|
77
|
+
"@lezer/common": "^1.5.3",
|
|
78
|
+
"@lezer/highlight": "^1.2.5",
|
|
79
|
+
"@pierre/diffs": "1.5.2",
|
|
79
80
|
"@plannotator/atomic-editor": "^0.9.0",
|
|
80
|
-
"@plannotator/core": "0.25.
|
|
81
|
+
"@plannotator/core": "0.25.14",
|
|
81
82
|
"@plannotator/markdown-editor": "^0.5.0",
|
|
82
83
|
"@plannotator/web-highlighter": "^0.8.1",
|
|
83
84
|
"@tanstack/react-table": "^8.21.3",
|
|
84
|
-
"@viz-js/viz": "3.
|
|
85
|
+
"@viz-js/viz": "3.31.0",
|
|
85
86
|
"class-variance-authority": "^0.7.1",
|
|
86
87
|
"clsx": "^2.1.1",
|
|
87
88
|
"diff": "^8.0.4",
|
|
88
|
-
"dompurify": "^3.4.
|
|
89
|
-
"katex": "^0.
|
|
90
|
-
"lucide-react": "^1.
|
|
89
|
+
"dompurify": "^3.4.17",
|
|
90
|
+
"katex": "^0.18.11",
|
|
91
|
+
"lucide-react": "^1.59.0",
|
|
91
92
|
"marked": "^17.0.6",
|
|
92
|
-
"mermaid": "12.
|
|
93
|
-
"motion": "^
|
|
93
|
+
"mermaid": "12.2.0",
|
|
94
|
+
"motion": "^14.4.0",
|
|
94
95
|
"perfect-freehand": "^1.2.2",
|
|
95
|
-
"tailwind-merge": "^3.
|
|
96
|
+
"tailwind-merge": "^3.7.0",
|
|
96
97
|
"unique-username-generator": "^1.5.1"
|
|
97
98
|
},
|
|
98
99
|
"peerDependencies": {
|
|
@@ -104,10 +105,10 @@
|
|
|
104
105
|
"@happy-dom/global-registrator": "^20.12.0",
|
|
105
106
|
"@tailwindcss/vite": "^4.3.3",
|
|
106
107
|
"@types/bun": "^1.2.0",
|
|
107
|
-
"@types/react": "^19.
|
|
108
|
-
"@types/react-dom": "^19.
|
|
109
|
-
"react": "^19.
|
|
110
|
-
"react-dom": "^19.
|
|
108
|
+
"@types/react": "^19.3.0",
|
|
109
|
+
"@types/react-dom": "^19.3.0",
|
|
110
|
+
"react": "^19.3.1",
|
|
111
|
+
"react-dom": "^19.3.1",
|
|
111
112
|
"tailwindcss": "^4.1.18",
|
|
112
113
|
"typescript": "~5.8.2",
|
|
113
114
|
"vite": "^6.2.0"
|
|
@@ -8,6 +8,7 @@ export const reviewAnnotationToolbarShortcuts = defineShortcutScope({
|
|
|
8
8
|
submitComment: {
|
|
9
9
|
description: 'Submit comment',
|
|
10
10
|
bindings: ['Mod+Enter'],
|
|
11
|
+
hint: 'With Settings → General → “Enter saves a comment” on, Enter saves too and Shift+Enter adds a new line.',
|
|
11
12
|
section: 'Annotations',
|
|
12
13
|
displayOrder: 10,
|
|
13
14
|
},
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { defineShortcutScope } from '../core';
|
|
2
|
+
import { createShortcutScopeHook } from '../runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Moving around the repository in code review (approved design record
|
|
6
|
+
* "review-explorer", step 3). VS Code's own default keys per system, so a
|
|
7
|
+
* reviewer's hands already know them. None of them takes a key code review
|
|
8
|
+
* used before; ⌘P / Ctrl+P is the browser's Print, replaced here in code
|
|
9
|
+
* review only.
|
|
10
|
+
*/
|
|
11
|
+
export const reviewExplorerShortcuts = defineShortcutScope({
|
|
12
|
+
id: 'review-explorer',
|
|
13
|
+
title: 'Go to',
|
|
14
|
+
shortcuts: {
|
|
15
|
+
goToFile: {
|
|
16
|
+
description: 'Go to File',
|
|
17
|
+
bindings: ['Mod+P'],
|
|
18
|
+
section: 'Go to',
|
|
19
|
+
hint: 'Fuzzy search over every file of the repository at the revision the diff reads. Changed files are marked and win ties; with nothing typed, the files you opened last.',
|
|
20
|
+
preventDefault: true,
|
|
21
|
+
displayOrder: 10,
|
|
22
|
+
},
|
|
23
|
+
goToDefinition: {
|
|
24
|
+
description: 'Go to Definition',
|
|
25
|
+
bindings: ['F12', 'Mod+F12'],
|
|
26
|
+
section: 'Go to',
|
|
27
|
+
hint: 'The symbol under the pointer (or the last one you clicked). One clear definition opens its file at the line; several open the references panel. Mod+F12 is for browsers that keep F12 for their developer tools. Needs the local checkout.',
|
|
28
|
+
preventDefault: true,
|
|
29
|
+
displayOrder: 20,
|
|
30
|
+
},
|
|
31
|
+
goToReferences: {
|
|
32
|
+
description: 'Go to References',
|
|
33
|
+
bindings: ['Shift+F12'],
|
|
34
|
+
section: 'Go to',
|
|
35
|
+
hint: 'Opens the references panel for the symbol under the pointer. A result opens its file at the line.',
|
|
36
|
+
preventDefault: true,
|
|
37
|
+
displayOrder: 30,
|
|
38
|
+
},
|
|
39
|
+
navigateBack: {
|
|
40
|
+
description: 'Go back',
|
|
41
|
+
bindings: ['Ctrl+-'],
|
|
42
|
+
platformBindings: {
|
|
43
|
+
mac: ['Ctrl+-'],
|
|
44
|
+
windows: ['Alt+ArrowLeft'],
|
|
45
|
+
linux: ['Ctrl+Alt+-'],
|
|
46
|
+
},
|
|
47
|
+
section: 'Go to',
|
|
48
|
+
hint: 'Back to where the last jump (Go to File, Go to Definition, a reference) started. Stays inside the review.',
|
|
49
|
+
preventDefault: true,
|
|
50
|
+
displayOrder: 40,
|
|
51
|
+
},
|
|
52
|
+
navigateForward: {
|
|
53
|
+
description: 'Go forward',
|
|
54
|
+
bindings: ['Ctrl+Shift+-'],
|
|
55
|
+
platformBindings: {
|
|
56
|
+
mac: ['Ctrl+Shift+-'],
|
|
57
|
+
windows: ['Alt+ArrowRight'],
|
|
58
|
+
linux: ['Ctrl+Shift+-'],
|
|
59
|
+
},
|
|
60
|
+
section: 'Go to',
|
|
61
|
+
displayOrder: 50,
|
|
62
|
+
preventDefault: true,
|
|
63
|
+
},
|
|
64
|
+
nextChange: {
|
|
65
|
+
description: 'Next change',
|
|
66
|
+
bindings: ['Alt+F5'],
|
|
67
|
+
section: 'Go to',
|
|
68
|
+
hint: 'The next run of changed lines in the open file; past the last one, the next file with changes.',
|
|
69
|
+
preventDefault: true,
|
|
70
|
+
displayOrder: 60,
|
|
71
|
+
},
|
|
72
|
+
previousChange: {
|
|
73
|
+
description: 'Previous change',
|
|
74
|
+
bindings: ['Shift+Alt+F5'],
|
|
75
|
+
section: 'Go to',
|
|
76
|
+
preventDefault: true,
|
|
77
|
+
displayOrder: 70,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const useReviewExplorerShortcuts = createShortcutScopeHook(reviewExplorerShortcuts);
|
package/shortcuts/core.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import { isMac } from '../utils/platform';
|
|
1
|
+
import { isMac, isWindows } from '../utils/platform';
|
|
2
2
|
|
|
3
3
|
export type ShortcutPlatform = 'mac' | 'non-mac' | 'cross-platform';
|
|
4
4
|
|
|
5
|
+
/** The three desktop systems a binding can differ on (VS Code's split). */
|
|
6
|
+
export type ShortcutOS = 'mac' | 'windows' | 'linux';
|
|
7
|
+
|
|
5
8
|
export interface ShortcutDefinition {
|
|
6
9
|
description: string;
|
|
7
10
|
/** Alternative bindings for the same action, shown as “or” in the shortcuts UI. */
|
|
8
11
|
bindings: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Per-system bindings that replace `bindings` on that system (VS Code's
|
|
14
|
+
* defaults differ by OS, e.g. Back is ⌃- on macOS, Alt+← on Windows and
|
|
15
|
+
* Ctrl+Alt+- on Linux). `bindings` stays the fallback and what tools that
|
|
16
|
+
* know nothing of systems read.
|
|
17
|
+
*/
|
|
18
|
+
platformBindings?: Partial<Record<ShortcutOS, string[]>>;
|
|
9
19
|
section: string;
|
|
10
20
|
hint?: string;
|
|
11
21
|
preventDefault?: boolean;
|
|
@@ -41,6 +51,9 @@ export interface ShortcutSurface {
|
|
|
41
51
|
|
|
42
52
|
const NAMED_TOKENS = new Set([
|
|
43
53
|
'Mod',
|
|
54
|
+
// The physical Control key on every system (⌃ on macOS), unlike `Mod`,
|
|
55
|
+
// which is ⌘ on macOS. For VS Code's macOS bindings such as ⌃-.
|
|
56
|
+
'Ctrl',
|
|
44
57
|
'Shift',
|
|
45
58
|
'Alt',
|
|
46
59
|
'Enter',
|
|
@@ -69,13 +82,15 @@ const NAMED_TOKENS = new Set([
|
|
|
69
82
|
'}',
|
|
70
83
|
'?',
|
|
71
84
|
'$',
|
|
85
|
+
// Matched by its key code, so Shift (which turns it into `_`) still reads as it.
|
|
86
|
+
'-',
|
|
72
87
|
]);
|
|
73
88
|
|
|
74
89
|
for (let n = 1; n <= 12; n += 1) {
|
|
75
90
|
NAMED_TOKENS.add(`F${n}`);
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
const MODIFIER_TOKENS = new Set(['Mod', 'Shift', 'Alt']);
|
|
93
|
+
const MODIFIER_TOKENS = new Set(['Mod', 'Ctrl', 'Shift', 'Alt']);
|
|
79
94
|
const SHIFTED_LITERAL_TOKENS = new Set(['{', '}', '?', '$']);
|
|
80
95
|
type ShortcutKeyEvent = Pick<
|
|
81
96
|
KeyboardEvent,
|
|
@@ -184,7 +199,11 @@ export function validateShortcutRegistry(registry: ShortcutRegistry): string[] {
|
|
|
184
199
|
errors.push(`Shortcut ${id} must define at least one binding.`);
|
|
185
200
|
}
|
|
186
201
|
|
|
187
|
-
|
|
202
|
+
const allBindings = [
|
|
203
|
+
...shortcut.bindings,
|
|
204
|
+
...Object.values(shortcut.platformBindings ?? {}).flat(),
|
|
205
|
+
];
|
|
206
|
+
for (const binding of allBindings) {
|
|
188
207
|
if (!binding.trim()) {
|
|
189
208
|
errors.push(`Shortcut ${id} contains an empty binding.`);
|
|
190
209
|
continue;
|
|
@@ -264,9 +283,36 @@ export function getShortcutPlatform(): Exclude<ShortcutPlatform, 'cross-platform
|
|
|
264
283
|
return isMac ? 'mac' : 'non-mac';
|
|
265
284
|
}
|
|
266
285
|
|
|
286
|
+
export function getShortcutOS(): ShortcutOS {
|
|
287
|
+
return isMac ? 'mac' : isWindows ? 'windows' : 'linux';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** The bindings that apply on a system: its own when it has some, else `bindings`. */
|
|
291
|
+
export function resolveShortcutBindings(
|
|
292
|
+
shortcut: Pick<ShortcutDefinition, 'bindings' | 'platformBindings'>,
|
|
293
|
+
os: ShortcutOS = getShortcutOS(),
|
|
294
|
+
): string[] {
|
|
295
|
+
return shortcut.platformBindings?.[os] ?? shortcut.bindings;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const OS_LABELS: Record<ShortcutOS, string> = { mac: 'macOS', windows: 'Windows', linux: 'Linux' };
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* A shortcut's bindings as text for a reference that serves every system (the
|
|
302
|
+
* docs): `Cmd+P or Ctrl+P` style text, or one entry per system when they
|
|
303
|
+
* differ ("Ctrl+- (macOS) · Alt+ArrowLeft (Windows) · Ctrl+Alt+- (Linux)").
|
|
304
|
+
*/
|
|
305
|
+
export function formatShortcutDefinitionText(shortcut: Pick<ShortcutDefinition, 'bindings' | 'platformBindings'>): string {
|
|
306
|
+
if (!shortcut.platformBindings) return formatShortcutBindingsText(shortcut.bindings);
|
|
307
|
+
return (['mac', 'windows', 'linux'] as const)
|
|
308
|
+
.map((os) => `${formatShortcutBindingsText(resolveShortcutBindings(shortcut, os), os === 'mac' ? 'mac' : 'non-mac')} (${OS_LABELS[os]})`)
|
|
309
|
+
.join(' · ');
|
|
310
|
+
}
|
|
311
|
+
|
|
267
312
|
function formatKeycapToken(token: string, platform: Exclude<ShortcutPlatform, 'cross-platform'>): string {
|
|
268
313
|
if (platform === 'mac') {
|
|
269
314
|
if (token === 'Mod') return '⌘';
|
|
315
|
+
if (token === 'Ctrl') return '⌃';
|
|
270
316
|
if (token === 'Alt') return '⌥';
|
|
271
317
|
if (token === 'Shift') return '⇧';
|
|
272
318
|
if (token === 'Enter') return '⏎';
|
|
@@ -293,6 +339,7 @@ function formatTextToken(token: string, platform: ShortcutPlatform): string {
|
|
|
293
339
|
return platform === 'mac' ? 'Option' : 'Alt';
|
|
294
340
|
}
|
|
295
341
|
|
|
342
|
+
if (token === 'Ctrl') return 'Ctrl';
|
|
296
343
|
if (token === 'Escape') return 'Escape';
|
|
297
344
|
if (token === 'hold') return 'hold';
|
|
298
345
|
return token;
|
|
@@ -359,6 +406,10 @@ function matchesKeyToken(event: ShortcutKeyEvent, token: string): boolean {
|
|
|
359
406
|
return event.key === ' ' || event.key === 'Spacebar' || event.code === 'Space';
|
|
360
407
|
}
|
|
361
408
|
|
|
409
|
+
if (token === '-') {
|
|
410
|
+
return event.code === 'Minus' || event.key === '-' || event.key === '_';
|
|
411
|
+
}
|
|
412
|
+
|
|
362
413
|
if (token === 'A-Z') {
|
|
363
414
|
return /^[A-Z]$/.test(key);
|
|
364
415
|
}
|
|
@@ -393,6 +444,7 @@ export function matchesShortcutBinding(event: ShortcutKeyEvent, binding: string)
|
|
|
393
444
|
if (tokens.length === 0) return false;
|
|
394
445
|
|
|
395
446
|
const requiresMod = tokens.includes('Mod');
|
|
447
|
+
const requiresCtrl = tokens.includes('Ctrl');
|
|
396
448
|
const requiresShift = tokens.includes('Shift');
|
|
397
449
|
const requiresAlt = tokens.includes('Alt');
|
|
398
450
|
const keyTokens = tokens.filter(token => !MODIFIER_TOKENS.has(token));
|
|
@@ -403,7 +455,14 @@ export function matchesShortcutBinding(event: ShortcutKeyEvent, binding: string)
|
|
|
403
455
|
|| (!requiresShift && keyToken === 'A-Z' && event.shiftKey)
|
|
404
456
|
|| (!requiresShift && SHIFTED_LITERAL_TOKENS.has(keyToken) && event.key === keyToken);
|
|
405
457
|
|
|
406
|
-
|
|
458
|
+
// `Mod` is ⌘ or Ctrl (as before); `Ctrl` is the physical Control key only.
|
|
459
|
+
if (requiresMod) {
|
|
460
|
+
if (!(event.metaKey || event.ctrlKey)) return false;
|
|
461
|
+
} else if (requiresCtrl) {
|
|
462
|
+
if (!event.ctrlKey || event.metaKey) return false;
|
|
463
|
+
} else if (event.metaKey || event.ctrlKey) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
407
466
|
if (!shiftMatches) return false;
|
|
408
467
|
if (requiresAlt !== event.altKey) return false;
|
|
409
468
|
|
package/shortcuts/index.ts
CHANGED
|
@@ -35,3 +35,4 @@ export { reviewAiShortcuts, useReviewAiShortcuts } from './code-review/ai.shortc
|
|
|
35
35
|
export { reviewSuggestionModalShortcuts, useReviewSuggestionModalShortcuts } from './code-review/suggestionModal.shortcuts';
|
|
36
36
|
export { reviewTourDialogShortcuts, useReviewTourDialogShortcuts } from './code-review/tourDialog.shortcuts';
|
|
37
37
|
export { reviewMarkdownPreviewShortcuts, useReviewMarkdownPreviewShortcuts } from './code-review/markdownPreview.shortcuts';
|
|
38
|
+
export { reviewExplorerShortcuts, useReviewExplorerShortcuts } from './code-review/explorer.shortcuts';
|
|
@@ -9,7 +9,7 @@ export const annotationPanelShortcuts = defineShortcutScope({
|
|
|
9
9
|
description: 'Save annotation edit',
|
|
10
10
|
bindings: ['Mod+Enter'],
|
|
11
11
|
section: 'Annotations',
|
|
12
|
-
hint: 'Available while editing an annotation in the side panel.',
|
|
12
|
+
hint: 'Available while editing an annotation in the side panel. With Settings → General → “Enter saves a comment” on, Enter saves too.',
|
|
13
13
|
displayOrder: 50,
|
|
14
14
|
},
|
|
15
15
|
cancelEdit: {
|
|
@@ -7,6 +7,7 @@ export const commentPopoverShortcuts = defineShortcutScope({
|
|
|
7
7
|
submit: {
|
|
8
8
|
description: 'Submit comment',
|
|
9
9
|
bindings: ['Mod+Enter'],
|
|
10
|
+
hint: 'With Settings → General → “Enter saves a comment” on, Enter saves too and Shift+Enter adds a new line.',
|
|
10
11
|
section: 'Annotations',
|
|
11
12
|
displayOrder: 30,
|
|
12
13
|
},
|