@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
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.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",
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"./theme-modes": "./components/themeModes.tsx",
|
|
29
30
|
"./types": "./types.ts",
|
|
30
31
|
"./theme": "./theme.css",
|
|
31
|
-
"./styles.css": "./styles.css"
|
|
32
|
+
"./styles.css": "./styles.css",
|
|
33
|
+
"./contents-rail.css": "./contents-rail.css"
|
|
32
34
|
},
|
|
33
35
|
"files": [
|
|
34
36
|
"components",
|
|
@@ -50,6 +52,7 @@
|
|
|
50
52
|
"theme.css",
|
|
51
53
|
"print.css",
|
|
52
54
|
"styles.css",
|
|
55
|
+
"contents-rail.css",
|
|
53
56
|
"plannotator.webp",
|
|
54
57
|
"HANDOFF.md",
|
|
55
58
|
"!**/*.test.ts",
|
|
@@ -57,42 +60,42 @@
|
|
|
57
60
|
"!test-setup"
|
|
58
61
|
],
|
|
59
62
|
"dependencies": {
|
|
60
|
-
"@base-ui/react": "^1.
|
|
63
|
+
"@base-ui/react": "^1.9.0",
|
|
61
64
|
"@codemirror/autocomplete": "^6.20.3",
|
|
62
|
-
"@codemirror/commands": "^6.11.
|
|
65
|
+
"@codemirror/commands": "^6.11.1",
|
|
63
66
|
"@codemirror/lang-javascript": "^6.2.5",
|
|
64
67
|
"@codemirror/lang-json": "^6.0.2",
|
|
65
68
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
66
69
|
"@codemirror/lang-python": "^6.2.1",
|
|
67
70
|
"@codemirror/lang-yaml": "^6.1.3",
|
|
68
|
-
"@codemirror/language": "^6.
|
|
69
|
-
"@codemirror/legacy-modes": "^6.5.
|
|
71
|
+
"@codemirror/language": "^6.13.1",
|
|
72
|
+
"@codemirror/legacy-modes": "^6.5.5",
|
|
70
73
|
"@codemirror/merge": "^6.12.2",
|
|
71
74
|
"@codemirror/search": "^6.7.2",
|
|
72
|
-
"@codemirror/state": "^6.7.
|
|
73
|
-
"@codemirror/view": "^6.43.
|
|
75
|
+
"@codemirror/state": "^6.7.6",
|
|
76
|
+
"@codemirror/view": "^6.43.14",
|
|
74
77
|
"@fontsource-variable/geist-mono": "5.3.0",
|
|
75
78
|
"@fontsource-variable/inter": "^5.3.0",
|
|
76
|
-
"@lezer/common": "^1.5.
|
|
77
|
-
"@lezer/highlight": "^1.2.
|
|
78
|
-
"@pierre/diffs": "1.
|
|
79
|
+
"@lezer/common": "^1.5.3",
|
|
80
|
+
"@lezer/highlight": "^1.2.5",
|
|
81
|
+
"@pierre/diffs": "1.5.2",
|
|
79
82
|
"@plannotator/atomic-editor": "^0.9.0",
|
|
80
|
-
"@plannotator/core": "0.25.
|
|
83
|
+
"@plannotator/core": "0.25.14",
|
|
81
84
|
"@plannotator/markdown-editor": "^0.5.0",
|
|
82
85
|
"@plannotator/web-highlighter": "^0.8.1",
|
|
83
86
|
"@tanstack/react-table": "^8.21.3",
|
|
84
|
-
"@viz-js/viz": "3.
|
|
87
|
+
"@viz-js/viz": "3.31.0",
|
|
85
88
|
"class-variance-authority": "^0.7.1",
|
|
86
89
|
"clsx": "^2.1.1",
|
|
87
90
|
"diff": "^8.0.4",
|
|
88
|
-
"dompurify": "^3.4.
|
|
89
|
-
"katex": "^0.
|
|
90
|
-
"lucide-react": "^1.
|
|
91
|
+
"dompurify": "^3.4.17",
|
|
92
|
+
"katex": "^0.19.0",
|
|
93
|
+
"lucide-react": "^1.59.0",
|
|
91
94
|
"marked": "^17.0.6",
|
|
92
|
-
"mermaid": "12.
|
|
93
|
-
"motion": "^
|
|
95
|
+
"mermaid": "12.2.0",
|
|
96
|
+
"motion": "^14.4.0",
|
|
94
97
|
"perfect-freehand": "^1.2.2",
|
|
95
|
-
"tailwind-merge": "^3.
|
|
98
|
+
"tailwind-merge": "^3.7.0",
|
|
96
99
|
"unique-username-generator": "^1.5.1"
|
|
97
100
|
},
|
|
98
101
|
"peerDependencies": {
|
|
@@ -104,10 +107,10 @@
|
|
|
104
107
|
"@happy-dom/global-registrator": "^20.12.0",
|
|
105
108
|
"@tailwindcss/vite": "^4.3.3",
|
|
106
109
|
"@types/bun": "^1.2.0",
|
|
107
|
-
"@types/react": "^19.
|
|
108
|
-
"@types/react-dom": "^19.
|
|
109
|
-
"react": "^19.
|
|
110
|
-
"react-dom": "^19.
|
|
110
|
+
"@types/react": "^19.3.0",
|
|
111
|
+
"@types/react-dom": "^19.3.0",
|
|
112
|
+
"react": "^19.3.1",
|
|
113
|
+
"react-dom": "^19.3.1",
|
|
111
114
|
"tailwindcss": "^4.1.18",
|
|
112
115
|
"typescript": "~5.8.2",
|
|
113
116
|
"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
|
},
|
package/shortcuts/runtime.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
matchesShortcutBinding,
|
|
6
6
|
matchesShortcutBindingGroup,
|
|
7
7
|
parseDoubleTapBinding,
|
|
8
|
+
resolveShortcutBindings,
|
|
8
9
|
} from './core';
|
|
9
10
|
|
|
10
11
|
type ShortcutActionId<TScope extends ShortcutScopeDefinition<any>> = keyof TScope['shortcuts'] & string;
|
|
@@ -86,7 +87,7 @@ export function dispatchShortcutEvent<TScope extends ShortcutScopeDefinition<any
|
|
|
86
87
|
for (const [actionId, shortcut] of getShortcutEntries(scope)) {
|
|
87
88
|
const handler = handlers[actionId];
|
|
88
89
|
if (!handler) continue;
|
|
89
|
-
if (!shortcut.
|
|
90
|
+
if (!resolveShortcutBindings(shortcut).some(binding => matchesShortcutBinding(event, binding))) continue;
|
|
90
91
|
|
|
91
92
|
const { when, handle } = normalizeShortcutHandler(handler);
|
|
92
93
|
if (when && !when(event)) continue;
|
|
@@ -189,7 +190,7 @@ export function useShortcutScope<TScope extends ShortcutScopeDefinition<any>>({
|
|
|
189
190
|
const { when } = normalizeShortcutHandler(handler);
|
|
190
191
|
if (when && !when(keyboardEvent)) continue;
|
|
191
192
|
|
|
192
|
-
for (const binding of shortcut
|
|
193
|
+
for (const binding of resolveShortcutBindings(shortcut)) {
|
|
193
194
|
const groups = binding.trim().split(/\s+/).filter(Boolean);
|
|
194
195
|
const doubleTapKey = parseDoubleTapBinding(binding);
|
|
195
196
|
if (
|
|
@@ -275,7 +276,7 @@ export function useDoubleTapShortcuts<TScope extends ShortcutScopeDefinition<any
|
|
|
275
276
|
// Pre-parse which actions have double-tap bindings
|
|
276
277
|
const doubleTapActions: Array<{ actionId: ShortcutActionId<TScope>; keyName: string; preventDefault: boolean }> = [];
|
|
277
278
|
for (const [actionId, shortcut] of getShortcutEntries(scope)) {
|
|
278
|
-
for (const binding of shortcut
|
|
279
|
+
for (const binding of resolveShortcutBindings(shortcut)) {
|
|
279
280
|
const keyName = parseDoubleTapBinding(binding);
|
|
280
281
|
if (keyName) {
|
|
281
282
|
doubleTapActions.push({ actionId, keyName, preventDefault: shortcut.preventDefault === true });
|