@plannotator/ui 0.52.1 → 0.53.1
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 -29
- package/README.md +45 -5
- package/components/AnnotationPanel.tsx +195 -118
- package/components/AnnotationToolbar.tsx +17 -14
- package/components/AnnotationToolstrip.tsx +54 -40
- package/components/BlockRenderer.tsx +20 -14
- package/components/CodeFilePopout.tsx +132 -69
- package/components/CommentPopover.tsx +8 -349
- package/components/ComposerTokenTextarea.tsx +379 -0
- package/components/ExportModal.tsx +6 -1
- package/components/HostTitle.tsx +108 -0
- package/components/HtmlSurfaceControls.tsx +2 -1
- package/components/ImageLightbox.tsx +288 -17
- package/components/ImagePeek.tsx +255 -0
- package/components/ImageResolverContext.tsx +31 -0
- package/components/InlineMarkdown.tsx +123 -15
- package/components/LocalLinkContext.tsx +49 -0
- package/components/MentionPicker.tsx +51 -0
- package/components/MenuVersionSection.tsx +51 -25
- package/components/OpenInAppButton.tsx +18 -49
- package/components/PlanHeaderMenu.tsx +2 -8
- package/components/ProviderIcons.tsx +10 -4
- package/components/RenderedMarkdown.tsx +40 -4
- package/components/Settings.tsx +33 -5
- package/components/SkillReferenceMenu.tsx +67 -18
- package/components/Tooltip.tsx +51 -4
- package/components/Viewer.tsx +220 -49
- package/components/ai/AIProviderBar.tsx +11 -0
- package/components/ai/AskSessionSetupHint.tsx +12 -0
- package/components/ai/DocumentAIChatPanel.tsx +17 -5
- package/components/ai/SessionAskNotice.tsx +4 -3
- package/components/blocks/BlockquoteBlock.tsx +159 -0
- package/components/blocks/HtmlBlock.tsx +121 -9
- package/components/blocks/QuestionBlock.tsx +183 -96
- package/components/blocks/QuestionContext.tsx +3 -0
- package/components/diagram/DiagramViewer.tsx +41 -4
- package/components/diagram/index.ts +1 -1
- package/components/html-viewer/HtmlViewer.tsx +95 -3
- package/components/html-viewer/bridge-script.asset.js +413 -7
- package/components/html-viewer/bridge-script.lite.ts +1 -1
- package/components/html-viewer/bridge-script.ts +454 -7
- package/components/html-viewer/useHtmlAnnotation.ts +51 -3
- package/components/icons/AgentIcons.tsx +9 -7
- package/components/plan-diff/PlanCleanDiffView.tsx +239 -36
- package/components/plan-diff/PlanDiffViewer.tsx +6 -1
- package/configure.ts +27 -1
- package/hooks/useAgentJobs.ts +8 -3
- package/hooks/useAnnotationHighlighter.ts +128 -23
- package/hooks/useExternalAnnotations.ts +6 -0
- package/hooks/useMentionAutocomplete.ts +1 -1
- package/hooks/useOpenInApps.ts +88 -0
- package/hooks/useSkillReferenceAutocomplete.ts +7 -3
- package/hooks/useUpdateCheck.ts +38 -98
- package/hooks/useUpdateCheckSetting.ts +43 -0
- package/hooks/useUpdateToast.ts +101 -0
- package/hooks/useVimSelection.ts +9 -0
- package/package.json +2 -2
- package/shortcuts/code-review/markdownPreview.shortcuts.ts +18 -0
- package/shortcuts/index.ts +1 -0
- package/shortcuts/runtime.ts +10 -3
- package/styles.css +1 -1
- package/types.ts +48 -0
- package/utils/agentClosed.ts +26 -0
- package/utils/agentToolSetting.ts +16 -9
- package/utils/aiProvider.ts +7 -0
- package/utils/askSessionSetupHint.ts +27 -0
- package/utils/blockTargeting.ts +14 -3
- package/utils/mentions.ts +34 -5
- package/utils/parser.ts +65 -51
- package/utils/pasteInsert.ts +234 -0
- package/utils/planDiffEngine.ts +272 -2
- package/utils/skillCatalog.ts +15 -2
- package/utils/skillReferences.ts +50 -13
- package/utils/storage.ts +5 -0
- package/utils/textCut.ts +41 -0
- package/utils/updateNotice.ts +120 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { copyTextToClipboard } from '../utils/clipboard';
|
|
4
|
+
import { claimUpdateToast, describeUpdateToast, requestUpdateNow, type UpdateAdvert } from '../utils/updateNotice';
|
|
5
|
+
|
|
6
|
+
/** First look after the page settles: past the first-run announcements' own 4 s window. */
|
|
7
|
+
export const UPDATE_TOAST_DELAY_MS = 5_000;
|
|
8
|
+
/** While something blocks it (a first-run dialog, a focused composer), look again this often. */
|
|
9
|
+
const RETRY_MS = 5_000;
|
|
10
|
+
/** Give up for this load after this long; the next load (or the menu) says it. */
|
|
11
|
+
const GIVE_UP_MS = 120_000;
|
|
12
|
+
|
|
13
|
+
function isTypingSomewhere(): boolean {
|
|
14
|
+
const el = typeof document !== 'undefined' ? document.activeElement : null;
|
|
15
|
+
if (!el || el === document.body) return false;
|
|
16
|
+
const tag = el.tagName;
|
|
17
|
+
if (tag === 'TEXTAREA' || tag === 'INPUT' || tag === 'SELECT' || (el as HTMLElement).isContentEditable) return true;
|
|
18
|
+
// An open dialog owns the screen; the toast waits for it.
|
|
19
|
+
return !!el.closest('[role="dialog"], dialog[open]');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
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 every three days
|
|
25
|
+
* (claimUpdateToast), queued behind the first-run dialogs (`blocked`) and
|
|
26
|
+
* never while a composer or dialog has focus. A blocked attempt is not
|
|
27
|
+
* consumed: it looks again, and gives up for this load after two minutes.
|
|
28
|
+
*
|
|
29
|
+
* Actions: What's new (the release on GitHub) and, for the binary the install
|
|
30
|
+
* script manages, Update (POST /api/update runs it now); elsewhere Copy
|
|
31
|
+
* command (the right command for the host).
|
|
32
|
+
*/
|
|
33
|
+
export function useUpdateToast(input: {
|
|
34
|
+
advert: UpdateAdvert | null;
|
|
35
|
+
/** A first-run dialog is showing or may still show this load. */
|
|
36
|
+
blocked: boolean;
|
|
37
|
+
/** Auto-update will install it; the post-update toast speaks instead. */
|
|
38
|
+
autoUpdateActive: boolean;
|
|
39
|
+
/** A share link or archive: nobody to tell. */
|
|
40
|
+
suppressed?: boolean;
|
|
41
|
+
position?: 'top-right' | 'bottom-right';
|
|
42
|
+
}): void {
|
|
43
|
+
const blockedRef = useRef(input.blocked);
|
|
44
|
+
blockedRef.current = input.blocked;
|
|
45
|
+
const shown = useRef(false);
|
|
46
|
+
const { advert, autoUpdateActive, suppressed, position } = input;
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!advert || autoUpdateActive || suppressed || shown.current) return;
|
|
50
|
+
const started = Date.now();
|
|
51
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
52
|
+
const attempt = () => {
|
|
53
|
+
if (blockedRef.current || isTypingSomewhere()) {
|
|
54
|
+
if (Date.now() - started < GIVE_UP_MS) timer = setTimeout(attempt, RETRY_MS);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!claimUpdateToast(advert.version, Date.now())) return;
|
|
58
|
+
shown.current = true;
|
|
59
|
+
const { title, description, primary } = describeUpdateToast(advert);
|
|
60
|
+
toast(title, {
|
|
61
|
+
description,
|
|
62
|
+
duration: 12_000,
|
|
63
|
+
...(position && { position }),
|
|
64
|
+
closeButton: true,
|
|
65
|
+
cancel: {
|
|
66
|
+
label: "What's new",
|
|
67
|
+
onClick: () => window.open(advert.releaseUrl, '_blank', 'noopener,noreferrer'),
|
|
68
|
+
},
|
|
69
|
+
action:
|
|
70
|
+
primary === 'update'
|
|
71
|
+
? {
|
|
72
|
+
label: 'Update',
|
|
73
|
+
onClick: () => {
|
|
74
|
+
void requestUpdateNow().then((result) => {
|
|
75
|
+
if (!("error" in result)) {
|
|
76
|
+
toast(`Updating to ${result.version}…`, {
|
|
77
|
+
description: 'The install script runs in the background. This review keeps working.',
|
|
78
|
+
duration: 8_000,
|
|
79
|
+
...(position && { position }),
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
toast("The update didn't start", { description: result.error, duration: 10_000, ...(position && { position }) });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
: {
|
|
88
|
+
label: 'Copy command',
|
|
89
|
+
onClick: () => {
|
|
90
|
+
void copyTextToClipboard(advert.command).then((copied) => {
|
|
91
|
+
if (copied) toast('Command copied', { description: advert.command, duration: 4_000, ...(position && { position }) });
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
classNames: { toast: '!w-auto', description: '!text-foreground/70 select-text' },
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
timer = setTimeout(attempt, UPDATE_TOAST_DELAY_MS);
|
|
99
|
+
return () => clearTimeout(timer);
|
|
100
|
+
}, [advert, autoUpdateActive, suppressed, position]);
|
|
101
|
+
}
|
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.53.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"@lezer/highlight": "^1.2.3",
|
|
78
78
|
"@pierre/diffs": "1.3.6",
|
|
79
79
|
"@plannotator/atomic-editor": "^0.9.0",
|
|
80
|
-
"@plannotator/core": "0.25.
|
|
80
|
+
"@plannotator/core": "0.25.13",
|
|
81
81
|
"@plannotator/markdown-editor": "^0.5.0",
|
|
82
82
|
"@plannotator/web-highlighter": "^0.8.1",
|
|
83
83
|
"@tanstack/react-table": "^8.21.3",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineShortcutScope } from '../core';
|
|
2
|
+
import { createShortcutScopeHook } from '../runtime';
|
|
3
|
+
|
|
4
|
+
export const reviewMarkdownPreviewShortcuts = defineShortcutScope({
|
|
5
|
+
id: 'review-markdown-preview',
|
|
6
|
+
title: 'Markdown Files',
|
|
7
|
+
shortcuts: {
|
|
8
|
+
cycleMarkdownView: {
|
|
9
|
+
description: 'Show the current Markdown file as Source, Changes or Read (next view)',
|
|
10
|
+
bindings: ['P'],
|
|
11
|
+
section: 'Markdown Files',
|
|
12
|
+
hint: 'The open file, or in All files the file at the top of the view. New and deleted files have no Changes view. Markdown files you have not set by hand follow your choice.',
|
|
13
|
+
displayOrder: 10,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const useReviewMarkdownPreviewShortcuts = createShortcutScopeHook(reviewMarkdownPreviewShortcuts);
|
package/shortcuts/index.ts
CHANGED
|
@@ -34,3 +34,4 @@ export { reviewAllFilesDiffShortcuts, useReviewAllFilesDiffShortcuts } from './c
|
|
|
34
34
|
export { reviewAiShortcuts, useReviewAiShortcuts } from './code-review/ai.shortcuts';
|
|
35
35
|
export { reviewSuggestionModalShortcuts, useReviewSuggestionModalShortcuts } from './code-review/suggestionModal.shortcuts';
|
|
36
36
|
export { reviewTourDialogShortcuts, useReviewTourDialogShortcuts } from './code-review/tourDialog.shortcuts';
|
|
37
|
+
export { reviewMarkdownPreviewShortcuts, useReviewMarkdownPreviewShortcuts } from './code-review/markdownPreview.shortcuts';
|
package/shortcuts/runtime.ts
CHANGED
|
@@ -27,6 +27,12 @@ export interface UseShortcutScopeOptions<TScope extends ShortcutScopeDefinition<
|
|
|
27
27
|
handlers: ShortcutHandlers<TScope>;
|
|
28
28
|
target?: ShortcutEventTarget;
|
|
29
29
|
stopOnMatch?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Listen in the capture phase: the scope sees a key before the focused
|
|
32
|
+
* element does (a guard that must run before a component's own handler).
|
|
33
|
+
* Default false (bubble), so a component that used the key first can say so.
|
|
34
|
+
*/
|
|
35
|
+
capture?: boolean;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
interface ShortcutSequenceCandidate {
|
|
@@ -123,6 +129,7 @@ export function useShortcutScope<TScope extends ShortcutScopeDefinition<any>>({
|
|
|
123
129
|
handlers,
|
|
124
130
|
target = 'window',
|
|
125
131
|
stopOnMatch = true,
|
|
132
|
+
capture = false,
|
|
126
133
|
}: UseShortcutScopeOptions<TScope>) {
|
|
127
134
|
const handlersRef = useRef(handlers);
|
|
128
135
|
const sequenceRef = useRef<ShortcutSequenceState | null>(null);
|
|
@@ -206,11 +213,11 @@ export function useShortcutScope<TScope extends ShortcutScopeDefinition<any>>({
|
|
|
206
213
|
if (started.length > 0) keyboardEvent.preventDefault();
|
|
207
214
|
};
|
|
208
215
|
|
|
209
|
-
eventTarget.addEventListener('keydown', handleKeyDown);
|
|
216
|
+
eventTarget.addEventListener('keydown', handleKeyDown, capture);
|
|
210
217
|
return () => {
|
|
211
|
-
eventTarget.removeEventListener('keydown', handleKeyDown);
|
|
218
|
+
eventTarget.removeEventListener('keydown', handleKeyDown, capture);
|
|
212
219
|
};
|
|
213
|
-
}, [scope, stopOnMatch, target]);
|
|
220
|
+
}, [scope, stopOnMatch, target, capture]);
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
export function createShortcutScopeHook<TScope extends ShortcutScopeDefinition<any>>(scope: TScope) {
|