@plannotator/ui 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/components/AISettingsTab.tsx +5 -4
- package/components/ActionMenu.tsx +5 -1
- package/components/AgentsTab.tsx +10 -23
- package/components/AnnotationPanel.tsx +9 -5
- package/components/AnnotationToolbar.tsx +5 -13
- package/components/AnnotationToolstrip.tsx +2 -2
- package/components/ApproveDropdown.tsx +1 -1
- package/components/BlockRenderer.tsx +1 -1
- package/components/CodeFilePopout.tsx +5 -4
- package/components/CommentPopover.tsx +391 -65
- package/components/DocBadges.tsx +29 -11
- package/components/ExportModal.tsx +16 -8
- package/components/GraphvizBlock.tsx +1 -1
- package/components/InlineMarkdown.tsx +30 -13
- package/components/KeyboardShortcuts.tsx +37 -2
- package/components/Landing.tsx +7 -7
- package/components/MarkdownDiff.tsx +60 -0
- package/components/MenuVersionSection.tsx +4 -4
- package/components/MermaidBlock.tsx +1 -1
- package/components/ModeToggle.tsx +7 -6
- package/components/OpenInAppButton.tsx +2 -5
- package/components/PinpointOverlay.tsx +9 -7
- package/components/PlanHeaderMenu.tsx +8 -8
- package/components/PopoutDialog.tsx +6 -1
- package/components/ResizeHandle.tsx +1 -0
- package/components/Settings.tsx +172 -12
- package/components/SkillReferenceMenu.tsx +260 -0
- package/components/StickyHeaderLane.tsx +7 -0
- package/components/ThemeProvider.tsx +131 -32
- package/components/ThemeTab.tsx +123 -77
- package/components/ToolbarButtons.tsx +29 -8
- package/components/Viewer.tsx +396 -130
- package/components/VimKeyHud.tsx +695 -0
- package/components/VimModeAnnouncementDialog.tsx +557 -0
- package/components/VimModeOverlay.tsx +235 -0
- package/components/VimTargetReticle.tsx +284 -0
- package/components/ai/DocumentAIChatPanel.tsx +1 -1
- package/components/blocks/CodeBlock.tsx +18 -18
- package/components/blocks/TablePopout.tsx +7 -8
- package/components/blocks/TableToolbar.tsx +7 -8
- package/components/goal-setup/GoalSetupSurface.tsx +16 -3
- package/components/html-viewer/HtmlViewer.tsx +450 -47
- package/components/html-viewer/annotationNumbering.ts +37 -0
- package/components/html-viewer/bridge-script.ts +4051 -298
- package/components/html-viewer/composerYield.ts +51 -0
- package/components/html-viewer/srcdoc.ts +18 -3
- package/components/html-viewer/useHtmlAnnotation.ts +457 -32
- package/components/icons/themeIcons.tsx +1 -1
- package/components/plan-diff/PlanCleanDiffView.tsx +9 -9
- package/components/plan-diff/PlanDiffBadge.tsx +22 -1
- package/components/settings/HooksTab.tsx +12 -8
- package/components/sidebar/FileBrowser.tsx +4 -1
- package/components/themeModes.tsx +28 -0
- package/config/configStore.ts +76 -1
- package/config/settings.ts +152 -0
- package/configure.ts +9 -0
- package/globals.d.ts +7 -1
- package/hooks/useAIChat.ts +5 -2
- package/hooks/useAIProviderActivation.ts +47 -0
- package/hooks/useAIProviderConfig.ts +5 -1
- package/hooks/useAgentSettings.ts +64 -23
- package/hooks/useAgents.ts +4 -4
- package/hooks/useAnnotationHighlighter.ts +100 -3
- package/hooks/useArchive.ts +2 -1
- package/hooks/useFenceTheme.ts +17 -0
- package/hooks/useLinkedDoc.ts +68 -1
- package/hooks/usePinpoint.ts +76 -75
- package/hooks/usePlanDiff.ts +73 -2
- package/hooks/useSkillReferenceAutocomplete.ts +239 -0
- package/hooks/useUpdateCheck.ts +1 -2
- package/hooks/useVimDocumentFocus.ts +116 -0
- package/hooks/useVimSelection.ts +1063 -0
- package/package.json +7 -6
- package/print.css +14 -13
- package/shortcuts/core.ts +38 -13
- package/shortcuts/index.ts +10 -0
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +7 -0
- package/shortcuts/plan-review/vimSelection.shortcuts.ts +251 -0
- package/shortcuts/runtime.ts +111 -12
- package/styles.css +1 -1
- package/theme.css +504 -0
- package/themes/colorblind.css +89 -0
- package/themes/plannotator.css +2 -2
- package/types.ts +93 -10
- package/utils/agentSwitch.ts +33 -7
- package/utils/blockTargeting.ts +462 -178
- package/utils/clipboard.ts +110 -0
- package/utils/codeBlockMark.ts +50 -0
- package/utils/codeHighlight.ts +293 -0
- package/utils/codexModels.ts +79 -0
- package/utils/domSelection.ts +84 -0
- package/utils/htmlChrome.ts +73 -0
- package/utils/inputMethod.ts +79 -6
- package/utils/parser.ts +517 -21
- package/utils/preferenceTtl.ts +15 -0
- package/utils/sharing.ts +0 -1
- package/utils/skillCatalog.ts +269 -0
- package/utils/skillReferences.ts +475 -0
- package/utils/syntaxTheme.ts +83 -0
- package/utils/themeRegistry.ts +154 -0
- package/utils/vimHud.ts +263 -0
- package/utils/vimModeAnnouncement.ts +23 -0
- package/utils/vimNavigation.ts +417 -0
- package/utils/vimReticle.ts +88 -0
- package/utils/vimScroll.ts +162 -0
package/shortcuts/runtime.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import type { ShortcutDefinition, ShortcutScopeDefinition } from './core';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
matchesKeyName,
|
|
5
|
+
matchesShortcutBinding,
|
|
6
|
+
matchesShortcutBindingGroup,
|
|
7
|
+
parseDoubleTapBinding,
|
|
8
|
+
} from './core';
|
|
4
9
|
|
|
5
10
|
type ShortcutActionId<TScope extends ShortcutScopeDefinition<any>> = keyof TScope['shortcuts'] & string;
|
|
6
11
|
|
|
@@ -24,6 +29,19 @@ export interface UseShortcutScopeOptions<TScope extends ShortcutScopeDefinition<
|
|
|
24
29
|
stopOnMatch?: boolean;
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
interface ShortcutSequenceCandidate {
|
|
33
|
+
readonly actionId: string;
|
|
34
|
+
readonly groups: readonly string[];
|
|
35
|
+
readonly nextIndex: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ShortcutSequenceState {
|
|
39
|
+
readonly candidates: readonly ShortcutSequenceCandidate[];
|
|
40
|
+
readonly expiresAt: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const SHORTCUT_SEQUENCE_WINDOW_MS = 500;
|
|
44
|
+
|
|
27
45
|
function normalizeShortcutHandler(handler: ShortcutHandler): ShortcutHandlerConfig {
|
|
28
46
|
if (typeof handler === 'function') {
|
|
29
47
|
return { handle: handler };
|
|
@@ -32,6 +50,17 @@ function normalizeShortcutHandler(handler: ShortcutHandler): ShortcutHandlerConf
|
|
|
32
50
|
return handler;
|
|
33
51
|
}
|
|
34
52
|
|
|
53
|
+
function getShortcutEntries<TScope extends ShortcutScopeDefinition<any>>(
|
|
54
|
+
scope: TScope,
|
|
55
|
+
): Array<[ShortcutActionId<TScope>, ShortcutDefinition]> {
|
|
56
|
+
// SAFETY: ShortcutScopeDefinition constrains every own value in `shortcuts`
|
|
57
|
+
// to ShortcutDefinition; Object.entries preserves those own keys and values.
|
|
58
|
+
return Object.entries(scope.shortcuts) as Array<[
|
|
59
|
+
ShortcutActionId<TScope>,
|
|
60
|
+
ShortcutDefinition,
|
|
61
|
+
]>;
|
|
62
|
+
}
|
|
63
|
+
|
|
35
64
|
// TODO(migration): no cross-scope arbitration. When two scopes bind the
|
|
36
65
|
// same key (e.g. `Escape` in both an outer editor scope and an inner
|
|
37
66
|
// dialog scope), both `useShortcutScope` listeners fire on a single
|
|
@@ -48,10 +77,7 @@ export function dispatchShortcutEvent<TScope extends ShortcutScopeDefinition<any
|
|
|
48
77
|
const stopOnMatch = options?.stopOnMatch ?? true;
|
|
49
78
|
let handled = false;
|
|
50
79
|
|
|
51
|
-
for (const [actionId, shortcut] of
|
|
52
|
-
ShortcutActionId<TScope>,
|
|
53
|
-
ShortcutDefinition,
|
|
54
|
-
]>) {
|
|
80
|
+
for (const [actionId, shortcut] of getShortcutEntries(scope)) {
|
|
55
81
|
const handler = handlers[actionId];
|
|
56
82
|
if (!handler) continue;
|
|
57
83
|
if (!shortcut.bindings.some(binding => matchesShortcutBinding(event, binding))) continue;
|
|
@@ -84,6 +110,14 @@ function getEventTarget(target: ShortcutEventTarget): EventTarget | null {
|
|
|
84
110
|
return target;
|
|
85
111
|
}
|
|
86
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Attach a registry scope to a keyboard event target.
|
|
115
|
+
*
|
|
116
|
+
* Ordinary bindings dispatch immediately. Non-modifier sequential bindings
|
|
117
|
+
* advance within a 500 ms window, suppress their prefix keystrokes, and reset
|
|
118
|
+
* after a match, timeout, or unrelated key. Handlers are read through a ref so
|
|
119
|
+
* listener lifetime follows only the scope, target, and dispatch options.
|
|
120
|
+
*/
|
|
87
121
|
export function useShortcutScope<TScope extends ShortcutScopeDefinition<any>>({
|
|
88
122
|
scope,
|
|
89
123
|
handlers,
|
|
@@ -91,6 +125,7 @@ export function useShortcutScope<TScope extends ShortcutScopeDefinition<any>>({
|
|
|
91
125
|
stopOnMatch = true,
|
|
92
126
|
}: UseShortcutScopeOptions<TScope>) {
|
|
93
127
|
const handlersRef = useRef(handlers);
|
|
128
|
+
const sequenceRef = useRef<ShortcutSequenceState | null>(null);
|
|
94
129
|
|
|
95
130
|
useEffect(() => {
|
|
96
131
|
handlersRef.current = handlers;
|
|
@@ -101,12 +136,79 @@ export function useShortcutScope<TScope extends ShortcutScopeDefinition<any>>({
|
|
|
101
136
|
if (!eventTarget || !('addEventListener' in eventTarget)) return;
|
|
102
137
|
|
|
103
138
|
const handleKeyDown = (event: Event) => {
|
|
104
|
-
|
|
139
|
+
if (!(event instanceof KeyboardEvent)) return;
|
|
140
|
+
const keyboardEvent = event;
|
|
141
|
+
if (dispatchShortcutEvent(scope, handlersRef.current, keyboardEvent, { stopOnMatch })) {
|
|
142
|
+
sequenceRef.current = null;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
const active = sequenceRef.current && sequenceRef.current.expiresAt >= now
|
|
148
|
+
? sequenceRef.current.candidates
|
|
149
|
+
: [];
|
|
150
|
+
const advanced: ShortcutSequenceCandidate[] = [];
|
|
151
|
+
|
|
152
|
+
for (const candidate of active) {
|
|
153
|
+
const group = candidate.groups[candidate.nextIndex];
|
|
154
|
+
if (!group || !matchesShortcutBindingGroup(keyboardEvent, group)) continue;
|
|
155
|
+
if (candidate.nextIndex === candidate.groups.length - 1) {
|
|
156
|
+
const shortcut = scope.shortcuts[candidate.actionId];
|
|
157
|
+
const handler = handlersRef.current[candidate.actionId];
|
|
158
|
+
if (!shortcut || !handler) continue;
|
|
159
|
+
const { when, handle } = normalizeShortcutHandler(handler);
|
|
160
|
+
if (when && !when(keyboardEvent)) continue;
|
|
161
|
+
if (shortcut.preventDefault) keyboardEvent.preventDefault();
|
|
162
|
+
handle(keyboardEvent);
|
|
163
|
+
sequenceRef.current = null;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
advanced.push({ ...candidate, nextIndex: candidate.nextIndex + 1 });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (advanced.length > 0) {
|
|
170
|
+
sequenceRef.current = {
|
|
171
|
+
candidates: advanced,
|
|
172
|
+
expiresAt: now + SHORTCUT_SEQUENCE_WINDOW_MS,
|
|
173
|
+
};
|
|
174
|
+
keyboardEvent.preventDefault();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const started: ShortcutSequenceCandidate[] = [];
|
|
179
|
+
for (const [actionId, shortcut] of getShortcutEntries(scope)) {
|
|
180
|
+
const handler = handlersRef.current[actionId];
|
|
181
|
+
if (!handler) continue;
|
|
182
|
+
const { when } = normalizeShortcutHandler(handler);
|
|
183
|
+
if (when && !when(keyboardEvent)) continue;
|
|
184
|
+
|
|
185
|
+
for (const binding of shortcut.bindings) {
|
|
186
|
+
const groups = binding.trim().split(/\s+/).filter(Boolean);
|
|
187
|
+
const doubleTapKey = parseDoubleTapBinding(binding);
|
|
188
|
+
if (
|
|
189
|
+
groups.length < 2
|
|
190
|
+
|| groups.includes('hold')
|
|
191
|
+
|| (doubleTapKey !== null && ['Alt', 'Shift', 'Mod'].includes(doubleTapKey))
|
|
192
|
+
|| !matchesShortcutBindingGroup(keyboardEvent, groups[0] ?? '')
|
|
193
|
+
) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
started.push({ actionId, groups, nextIndex: 1 });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
sequenceRef.current = started.length > 0
|
|
201
|
+
? {
|
|
202
|
+
candidates: started,
|
|
203
|
+
expiresAt: now + SHORTCUT_SEQUENCE_WINDOW_MS,
|
|
204
|
+
}
|
|
205
|
+
: null;
|
|
206
|
+
if (started.length > 0) keyboardEvent.preventDefault();
|
|
105
207
|
};
|
|
106
208
|
|
|
107
|
-
eventTarget.addEventListener('keydown', handleKeyDown
|
|
209
|
+
eventTarget.addEventListener('keydown', handleKeyDown);
|
|
108
210
|
return () => {
|
|
109
|
-
eventTarget.removeEventListener('keydown', handleKeyDown
|
|
211
|
+
eventTarget.removeEventListener('keydown', handleKeyDown);
|
|
110
212
|
};
|
|
111
213
|
}, [scope, stopOnMatch, target]);
|
|
112
214
|
}
|
|
@@ -165,10 +267,7 @@ export function useDoubleTapShortcuts<TScope extends ShortcutScopeDefinition<any
|
|
|
165
267
|
useEffect(() => {
|
|
166
268
|
// Pre-parse which actions have double-tap bindings
|
|
167
269
|
const doubleTapActions: Array<{ actionId: ShortcutActionId<TScope>; keyName: string; preventDefault: boolean }> = [];
|
|
168
|
-
for (const [actionId, shortcut] of
|
|
169
|
-
ShortcutActionId<TScope>,
|
|
170
|
-
ShortcutDefinition,
|
|
171
|
-
]>) {
|
|
270
|
+
for (const [actionId, shortcut] of getShortcutEntries(scope)) {
|
|
172
271
|
for (const binding of shortcut.bindings) {
|
|
173
272
|
const keyName = parseDoubleTapBinding(binding);
|
|
174
273
|
if (keyName) {
|