@plannotator/ui 0.55.0 → 0.56.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 +43 -1
- package/README.md +81 -4
- package/components/AnnotationPanel.tsx +2 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/CodeFilePopout.tsx +6 -4
- package/components/ComposerTokenTextarea.tsx +43 -2
- package/components/DocBadges.tsx +47 -35
- package/components/ImageZoomStage.tsx +32 -4
- package/components/Settings.tsx +2 -1
- package/components/TableOfContents.tsx +2 -1
- package/components/Viewer.tsx +43 -17
- package/components/chat/ChatComposer.tsx +192 -0
- package/components/chat/ChatFrame.tsx +524 -0
- package/components/chat/ChatTranscript.tsx +422 -0
- package/components/chat/chatMarkdown.ts +436 -0
- package/components/chat/chatRecipes.tsx +39 -0
- package/components/chat/chatWindowStore.ts +196 -0
- package/components/chat/index.ts +72 -0
- package/components/diagram/DiagramCanvas.tsx +7 -0
- package/components/diagram/DiagramComposer.tsx +15 -3
- package/components/diagram/DiagramViewer.tsx +1 -0
- package/components/docBadgesWidth.ts +29 -0
- package/components/imageZoom.ts +10 -0
- package/components/inbox-thread/FileName.tsx +118 -0
- package/components/inbox-thread/GuideCard.tsx +39 -0
- package/components/inbox-thread/ReaskNote.tsx +25 -0
- package/components/inbox-thread/SkillsLine.tsx +40 -0
- package/components/inbox-thread/Tip.tsx +105 -0
- package/components/inbox-thread/icons.tsx +262 -0
- package/components/inbox-thread/reask.ts +222 -0
- package/hooks/useActiveSection.ts +46 -63
- package/hooks/useAnnotationHighlighter.ts +40 -1
- package/hooks/useFileBrowser.ts +4 -3
- package/inbox-thread.css +4542 -0
- package/package.json +10 -3
- package/styles.css +1 -1
- package/theme.css +1 -0
- package/themes/tokyo-night-moon.css +34 -0
- package/utils/aiChatFormat.ts +32 -11
- package/utils/headingScrollSpy.ts +19 -6
- package/utils/syntaxTheme.ts +1 -0
- package/utils/themeRegistry.ts +11 -0
package/theme.css
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
@import "./themes/solar-dusk.css";
|
|
25
25
|
@import "./themes/terminal.css";
|
|
26
26
|
@import "./themes/tokyo-night.css";
|
|
27
|
+
@import "./themes/tokyo-night-moon.css";
|
|
27
28
|
@import "./themes/tinacious.css";
|
|
28
29
|
@import "./themes/cursor.css";
|
|
29
30
|
@import "./themes/cursor-hc.css";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Tokyo Night Moon — folke/tokyonight.nvim "moon" (dark only) */
|
|
2
|
+
.theme-tokyo-night-moon,
|
|
3
|
+
.theme-tokyo-night-moon.light {
|
|
4
|
+
--background: #222436;
|
|
5
|
+
--foreground: #c8d3f5;
|
|
6
|
+
--card: #1e2030;
|
|
7
|
+
--card-foreground: #c8d3f5;
|
|
8
|
+
--popover: #1e2030;
|
|
9
|
+
--popover-foreground: #c8d3f5;
|
|
10
|
+
--primary: #82aaff;
|
|
11
|
+
--primary-foreground: #1e2030;
|
|
12
|
+
--secondary: #444a73;
|
|
13
|
+
--secondary-foreground: #c8d3f5;
|
|
14
|
+
--muted: #2f334d;
|
|
15
|
+
--muted-foreground: #828bb8;
|
|
16
|
+
--accent: #86e1fc;
|
|
17
|
+
--accent-foreground: #1e2030;
|
|
18
|
+
--destructive: #ff757f;
|
|
19
|
+
--destructive-foreground: #1e2030;
|
|
20
|
+
--border: #444a73;
|
|
21
|
+
--input: #2f334d;
|
|
22
|
+
--ring: #82aaff;
|
|
23
|
+
--success: #c3e88d;
|
|
24
|
+
--success-foreground: #1e2030;
|
|
25
|
+
--warning: #ffc777;
|
|
26
|
+
--warning-foreground: #1e2030;
|
|
27
|
+
|
|
28
|
+
--font-sans: system-ui, sans-serif;
|
|
29
|
+
--font-mono: ui-monospace, monospace;
|
|
30
|
+
--radius: 0.625rem;
|
|
31
|
+
|
|
32
|
+
--code-bg: #191b29;
|
|
33
|
+
--focus-highlight: #86e1fc;
|
|
34
|
+
}
|
package/utils/aiChatFormat.ts
CHANGED
|
@@ -13,8 +13,14 @@ export const CHAT_SANITIZE_CONFIG = {
|
|
|
13
13
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'a', 'blockquote',
|
|
14
14
|
'table', 'thead', 'tbody', 'tr', 'th', 'td',
|
|
15
15
|
],
|
|
16
|
-
// `align` is what marked writes on th/td for aligned GFM columns.
|
|
17
|
-
|
|
16
|
+
// `align` is what marked writes on th/td for aligned GFM columns. No `class`
|
|
17
|
+
// (an answer's raw HTML could style itself with the app's utility classes;
|
|
18
|
+
// nothing in `.ai-markdown` reads one) and no `rel` (marked never writes it
|
|
19
|
+
// and `target` is not allowed).
|
|
20
|
+
ALLOWED_ATTR: ['href', 'align'],
|
|
21
|
+
// DOMPurify keeps data-* and aria-* unless told not to.
|
|
22
|
+
ALLOW_DATA_ATTR: false,
|
|
23
|
+
ALLOW_ARIA_ATTR: false,
|
|
18
24
|
};
|
|
19
25
|
|
|
20
26
|
/** The markdown step of {@link renderChatMarkdown}, before sanitizing. */
|
|
@@ -22,16 +28,31 @@ export function chatMarkdownToHtml(text: string): string {
|
|
|
22
28
|
return marked.parse(text, { async: false, breaks: true }) as string;
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Never throws: deeply nested input can overflow the stack in marked, and a
|
|
33
|
+
* throw inside a render would unmount the whole page. On any failure the
|
|
34
|
+
* answer is shown as plain text, line breaks kept, as a React text child
|
|
35
|
+
* (React escapes it).
|
|
36
|
+
*/
|
|
25
37
|
export function renderChatMarkdown(text: string): ReactNode {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
try {
|
|
39
|
+
const clean = DOMPurify.sanitize(chatMarkdownToHtml(text), {
|
|
40
|
+
ALLOWED_TAGS: [...CHAT_SANITIZE_CONFIG.ALLOWED_TAGS],
|
|
41
|
+
ALLOWED_ATTR: [...CHAT_SANITIZE_CONFIG.ALLOWED_ATTR],
|
|
42
|
+
ALLOW_DATA_ATTR: CHAT_SANITIZE_CONFIG.ALLOW_DATA_ATTR,
|
|
43
|
+
ALLOW_ARIA_ATTR: CHAT_SANITIZE_CONFIG.ALLOW_ARIA_ATTR,
|
|
44
|
+
});
|
|
45
|
+
return createElement('div', {
|
|
46
|
+
className: 'ai-markdown',
|
|
47
|
+
dangerouslySetInnerHTML: { __html: clean },
|
|
48
|
+
});
|
|
49
|
+
} catch {
|
|
50
|
+
return createElement(
|
|
51
|
+
'div',
|
|
52
|
+
{ className: 'ai-markdown', style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } },
|
|
53
|
+
String(text),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
35
56
|
}
|
|
36
57
|
|
|
37
58
|
export function formatRelativeTime(ts: number): string {
|
|
@@ -9,14 +9,24 @@
|
|
|
9
9
|
* activation line is 80 px below the top (it was 52), plan review's
|
|
10
10
|
* `TableOfContents` scroll offset, so a pick lands where plan review's does.
|
|
11
11
|
*
|
|
12
|
-
* Plan review's own list
|
|
13
|
-
*
|
|
12
|
+
* Plan review's own list uses `hooks/useActiveSection` (live heading
|
|
13
|
+
* positions at the same activation line). One mechanism per surface: the rail, and a
|
|
14
14
|
* Contents list on the rail's surface (`DocumentContents`), use this one.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/** How far below the scroll element's top a heading becomes the current one. */
|
|
18
18
|
export const CONTENTS_ACTIVATION_OFFSET = 80;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* How far below the activation line a heading still counts as reached. A pick
|
|
22
|
+
* scrolls to the heading's top less the offset, and that top is often
|
|
23
|
+
* fractional (1522.22 px); the browser snaps the scroll offset to a whole
|
|
24
|
+
* device pixel (1442, not 1442.22), so the picked heading can end up a fraction
|
|
25
|
+
* of a pixel below the line and the heading above it would be marked. One
|
|
26
|
+
* pixel covers the snap, as the document's end does below.
|
|
27
|
+
*/
|
|
28
|
+
export const CONTENTS_SNAP_TOLERANCE = 1;
|
|
29
|
+
|
|
20
30
|
/** A heading drawn in the scroll element, with its top inside the scrolled content. */
|
|
21
31
|
export interface HeadingBinding {
|
|
22
32
|
/** The heading's index in the caller's own list. */
|
|
@@ -75,9 +85,9 @@ export function currentBinding(
|
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
/**
|
|
78
|
-
* The heading being read: the last one at or above the activation line
|
|
79
|
-
* the last heading once the
|
|
80
|
-
* bindings.
|
|
88
|
+
* The heading being read: the last one at or above the activation line (or
|
|
89
|
+
* within `CONTENTS_SNAP_TOLERANCE` below it), or the last heading once the
|
|
90
|
+
* document is scrolled to its end. `null` with no bindings.
|
|
81
91
|
*/
|
|
82
92
|
export function activeHeadingBinding(
|
|
83
93
|
bindings: readonly HeadingBinding[],
|
|
@@ -88,5 +98,8 @@ export function activeHeadingBinding(
|
|
|
88
98
|
const atDocumentEnd = maxScrollTop > 0 && viewport.scrollTop >= maxScrollTop - 1;
|
|
89
99
|
return atDocumentEnd
|
|
90
100
|
? bindings.at(-1)!
|
|
91
|
-
: currentBinding(
|
|
101
|
+
: currentBinding(
|
|
102
|
+
bindings,
|
|
103
|
+
viewport.scrollTop + CONTENTS_ACTIVATION_OFFSET + CONTENTS_SNAP_TOLERANCE,
|
|
104
|
+
);
|
|
92
105
|
}
|
package/utils/syntaxTheme.ts
CHANGED
|
@@ -50,6 +50,7 @@ export const SHIKI_THEME_MAP: Record<string, { dark: string | null; light: strin
|
|
|
50
50
|
'solarized': { dark: 'solarized-dark', light: 'solarized-light' },
|
|
51
51
|
'synthwave-84': { dark: 'synthwave-84', light: null },
|
|
52
52
|
'tokyo-night': { dark: 'tokyo-night', light: null },
|
|
53
|
+
'tokyo-night-moon': { dark: 'tokyo-night', light: null },
|
|
53
54
|
'vesper': { dark: 'vesper', light: null },
|
|
54
55
|
'vitesse': { dark: 'vitesse-dark', light: 'vitesse-light' },
|
|
55
56
|
'vitesse-black': { dark: 'vitesse-black', light: null },
|
package/utils/themeRegistry.ts
CHANGED
|
@@ -544,6 +544,17 @@ export const BUILT_IN_THEMES: ThemeInfo[] = [
|
|
|
544
544
|
light: { primary: '#2e7de9', secondary: '#a1a6c5', accent: '#007197', background: '#e1e2e7', foreground: '#3760bf' },
|
|
545
545
|
},
|
|
546
546
|
},
|
|
547
|
+
{
|
|
548
|
+
id: 'tokyo-night-moon',
|
|
549
|
+
name: 'Tokyo Night Moon',
|
|
550
|
+
builtIn: true,
|
|
551
|
+
modeSupport: 'dark-only',
|
|
552
|
+
syntaxHighlighting: true,
|
|
553
|
+
colors: {
|
|
554
|
+
dark: { primary: '#82aaff', secondary: '#444a73', accent: '#86e1fc', background: '#222436', foreground: '#c8d3f5' },
|
|
555
|
+
light: { primary: '#82aaff', secondary: '#444a73', accent: '#86e1fc', background: '#222436', foreground: '#c8d3f5' },
|
|
556
|
+
},
|
|
557
|
+
},
|
|
547
558
|
{
|
|
548
559
|
id: 'vesper',
|
|
549
560
|
name: 'Vesper',
|