@open-slide/core 0.0.11 → 0.0.13
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/dist/{build-DHiRlpjn.js → build-DC3FTpWO.js} +2 -1
- package/dist/cli/bin.js +43 -4
- package/dist/{config-LZM903FE.js → config-Cuw0mC5h.js} +592 -63
- package/dist/design-BUML7uvZ.js +35 -0
- package/dist/{dev-B3JzCYn7.js → dev-BuWsdYvn.js} +2 -1
- package/dist/index.d.ts +55 -4
- package/dist/index.js +110 -1
- package/dist/{preview-UikovHEt.js → preview-CIcG-lP3.js} +2 -1
- package/dist/sync-3oqN1WyK.js +139 -0
- package/dist/sync-B4eLo2H6.js +3 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +2 -1
- package/package.json +2 -1
- package/skills/apply-comments/SKILL.md +83 -0
- package/skills/create-slide/SKILL.md +81 -0
- package/skills/create-theme/SKILL.md +194 -0
- package/skills/slide-authoring/SKILL.md +288 -0
- package/src/app/{App.tsx → app.tsx} +8 -6
- package/src/app/components/{AssetView.tsx → asset-view.tsx} +41 -33
- package/src/app/components/{ClickNavZones.tsx → click-nav-zones.tsx} +1 -1
- package/src/app/components/history-provider.tsx +120 -0
- package/src/app/components/image-placeholder.tsx +121 -0
- package/src/app/components/inspector/{CommentWidget.tsx → comment-widget.tsx} +1 -1
- package/src/app/components/inspector/{InspectOverlay.tsx → inspect-overlay.tsx} +1 -1
- package/src/app/components/inspector/{InspectorPanel.tsx → inspector-panel.tsx} +164 -212
- package/src/app/components/inspector/{InspectorProvider.tsx → inspector-provider.tsx} +186 -18
- package/src/app/components/inspector/save-bar.tsx +47 -0
- package/src/app/components/panel/panel-fields.tsx +60 -0
- package/src/app/components/panel/panel-shell.tsx +78 -0
- package/src/app/components/panel/save-card.tsx +139 -0
- package/src/app/components/pdf-progress-toast.tsx +25 -0
- package/src/app/components/player.tsx +341 -0
- package/src/app/components/present/blackout-overlay.tsx +18 -0
- package/src/app/components/present/control-bar.tsx +204 -0
- package/src/app/components/present/help-overlay.tsx +56 -0
- package/src/app/components/present/jump-input.tsx +74 -0
- package/src/app/components/present/laser-pointer.tsx +40 -0
- package/src/app/components/present/overview-grid.tsx +184 -0
- package/src/app/components/present/progress-bar.tsx +26 -0
- package/src/app/components/present/use-idle.ts +44 -0
- package/src/app/components/present/use-pointer-near-bottom.ts +34 -0
- package/src/app/components/present/use-presenter-channel.ts +71 -0
- package/src/app/components/present/use-touch-swipe.ts +63 -0
- package/src/app/components/sidebar/{FolderItem.tsx → folder-item.tsx} +62 -27
- package/src/app/components/sidebar/{IconPicker.tsx → icon-picker.tsx} +13 -10
- package/src/app/components/sidebar/{Sidebar.tsx → sidebar.tsx} +40 -34
- package/src/app/components/{SlideCanvas.tsx → slide-canvas.tsx} +35 -10
- package/src/app/components/style-panel/design-provider.tsx +139 -0
- package/src/app/components/style-panel/style-panel.tsx +326 -0
- package/src/app/components/style-panel/use-design.ts +112 -0
- package/src/app/components/theme-toggle.tsx +57 -0
- package/src/app/components/thumbnail-rail.tsx +151 -0
- package/src/app/components/ui/button.tsx +51 -19
- package/src/app/components/ui/card.tsx +1 -1
- package/src/app/components/ui/dialog.tsx +25 -9
- package/src/app/components/ui/dropdown-menu.tsx +29 -12
- package/src/app/components/ui/input.tsx +13 -9
- package/src/app/components/ui/popover.tsx +5 -2
- package/src/app/components/ui/progress.tsx +2 -2
- package/src/app/components/ui/select.tsx +11 -5
- package/src/app/components/ui/separator.tsx +1 -1
- package/src/app/components/ui/slider.tsx +4 -4
- package/src/app/components/ui/sonner.tsx +11 -1
- package/src/app/components/ui/tabs.tsx +6 -6
- package/src/app/components/ui/textarea.tsx +11 -7
- package/src/app/components/ui/toggle-group.tsx +2 -2
- package/src/app/components/ui/toggle.tsx +6 -6
- package/src/app/components/ui/tooltip.tsx +5 -2
- package/src/app/lib/design.ts +64 -0
- package/src/app/lib/export-html.ts +10 -1
- package/src/app/lib/export-pdf.ts +7 -0
- package/src/app/lib/folders.ts +1 -1
- package/src/app/lib/inspector/{useEditor.ts → use-editor.ts} +2 -1
- package/src/app/lib/sdk.ts +5 -0
- package/src/app/lib/slides.ts +1 -1
- package/src/app/lib/utils.ts +1 -1
- package/src/app/main.tsx +5 -2
- package/src/app/routes/{Home.tsx → home.tsx} +266 -97
- package/src/app/routes/presenter.tsx +400 -0
- package/src/app/routes/slide.tsx +519 -0
- package/src/app/styles.css +338 -67
- package/src/app/components/PdfProgressToast.tsx +0 -23
- package/src/app/components/Player.tsx +0 -100
- package/src/app/components/ThumbnailRail.tsx +0 -68
- package/src/app/components/inspector/SaveBar.tsx +0 -77
- package/src/app/routes/Slide.tsx +0 -478
- /package/dist/{config-SXL5qIl6.d.ts → config-DweCbRkQ.d.ts} +0 -0
- /package/src/app/lib/inspector/{useComments.ts → use-comments.ts} +0 -0
- /package/src/app/lib/{useWheelPageNavigation.ts → use-wheel-page-navigation.ts} +0 -0
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
Bold,
|
|
7
7
|
ImageIcon,
|
|
8
8
|
Italic,
|
|
9
|
-
Trash2,
|
|
10
9
|
X,
|
|
11
10
|
} from 'lucide-react';
|
|
12
11
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
12
|
+
import { Field, NumberField, Section } from '@/components/panel/panel-fields';
|
|
13
|
+
import { PANEL_TRANSITION_MS, PanelShell, useAnimatedOpen } from '@/components/panel/panel-shell';
|
|
13
14
|
import { Button } from '@/components/ui/button';
|
|
14
15
|
import {
|
|
15
16
|
Dialog,
|
|
@@ -19,8 +20,6 @@ import {
|
|
|
19
20
|
DialogTitle,
|
|
20
21
|
} from '@/components/ui/dialog';
|
|
21
22
|
import { Input } from '@/components/ui/input';
|
|
22
|
-
import { Label } from '@/components/ui/label';
|
|
23
|
-
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
24
23
|
import {
|
|
25
24
|
Select,
|
|
26
25
|
SelectContent,
|
|
@@ -35,13 +34,9 @@ import { Toggle } from '@/components/ui/toggle';
|
|
|
35
34
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
36
35
|
import { type AssetEntry, useAssets } from '@/lib/assets';
|
|
37
36
|
import { findSlideSource } from '@/lib/inspector/fiber';
|
|
38
|
-
import type {
|
|
39
|
-
import type { EditOp } from '@/lib/inspector/useEditor';
|
|
37
|
+
import type { EditOp } from '@/lib/inspector/use-editor';
|
|
40
38
|
import { cn } from '@/lib/utils';
|
|
41
|
-
import { type SelectedTarget, useInspector } from './
|
|
42
|
-
|
|
43
|
-
const PANEL_W = 340;
|
|
44
|
-
const PANEL_TRANSITION_MS = 280;
|
|
39
|
+
import { type SelectedTarget, useInspector } from './inspector-provider';
|
|
45
40
|
|
|
46
41
|
type ElementSnapshot = {
|
|
47
42
|
fontSize: number;
|
|
@@ -54,10 +49,11 @@ type ElementSnapshot = {
|
|
|
54
49
|
letterSpacing: number;
|
|
55
50
|
text: string | null;
|
|
56
51
|
imageSrc: string | null;
|
|
52
|
+
placeholder: { hint: string; width?: number; height?: number } | null;
|
|
57
53
|
};
|
|
58
54
|
|
|
59
55
|
export function InspectorPanel() {
|
|
60
|
-
const { active, slideId, selected, setSelected, bufferOps, pendingCount,
|
|
56
|
+
const { active, slideId, selected, setSelected, bufferOps, pendingCount, add, applyEdit } =
|
|
61
57
|
useInspector();
|
|
62
58
|
const [snapshot, setSnapshot] = useState<ElementSnapshot | null>(null);
|
|
63
59
|
const reloadCounter = useReloadCounter();
|
|
@@ -116,24 +112,15 @@ export function InspectorPanel() {
|
|
|
116
112
|
);
|
|
117
113
|
|
|
118
114
|
// `pinned` keeps the last selection rendered through the close-out
|
|
119
|
-
// animation
|
|
120
|
-
// fires on the 0 → PANEL_W flip.
|
|
115
|
+
// animation so the panel's contents don't blank out before it collapses.
|
|
121
116
|
const targetOpen = active && !!selected && !!snapshot;
|
|
122
117
|
const [pinned, setPinned] = useState<{ s: SelectedTarget; n: ElementSnapshot } | null>(null);
|
|
123
|
-
const
|
|
118
|
+
const animVisible = useAnimatedOpen(targetOpen && !!pinned);
|
|
124
119
|
|
|
125
120
|
useEffect(() => {
|
|
126
121
|
if (selected && snapshot) setPinned({ s: selected, n: snapshot });
|
|
127
122
|
}, [selected, snapshot]);
|
|
128
123
|
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
if (targetOpen && pinned) {
|
|
131
|
-
const id = requestAnimationFrame(() => setAnimVisible(true));
|
|
132
|
-
return () => cancelAnimationFrame(id);
|
|
133
|
-
}
|
|
134
|
-
setAnimVisible(false);
|
|
135
|
-
}, [targetOpen, pinned]);
|
|
136
|
-
|
|
137
124
|
useEffect(() => {
|
|
138
125
|
if (!targetOpen && pinned) {
|
|
139
126
|
const t = setTimeout(() => setPinned(null), PANEL_TRANSITION_MS);
|
|
@@ -145,114 +132,91 @@ export function InspectorPanel() {
|
|
|
145
132
|
const { s: pinSelected, n: pinSnapshot } = pinned;
|
|
146
133
|
|
|
147
134
|
return (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
borderLeftWidth: animVisible ? 1 : 0,
|
|
154
|
-
transitionDuration: `${PANEL_TRANSITION_MS}ms`,
|
|
155
|
-
}}
|
|
156
|
-
>
|
|
157
|
-
<div style={{ width: PANEL_W }} className="flex h-full shrink-0 flex-col">
|
|
158
|
-
<header className="flex shrink-0 items-center justify-between gap-2 border-b px-3 py-2.5">
|
|
135
|
+
<PanelShell
|
|
136
|
+
uiAttr="inspector"
|
|
137
|
+
animVisible={animVisible}
|
|
138
|
+
header={
|
|
139
|
+
<>
|
|
159
140
|
<div className="flex min-w-0 items-center gap-2">
|
|
160
|
-
<span className="
|
|
141
|
+
<span className="font-heading text-[12px] font-semibold tracking-tight">Inspect</span>
|
|
142
|
+
<span aria-hidden className="h-3 w-px bg-hairline" />
|
|
143
|
+
<span className="rounded-[3px] border border-hairline bg-card px-1.5 py-px font-mono text-[10.5px] text-foreground/85">
|
|
161
144
|
<{pinSelected.anchor.tagName.toLowerCase()}>
|
|
162
145
|
</span>
|
|
163
146
|
</div>
|
|
164
147
|
<Button
|
|
165
148
|
variant="ghost"
|
|
166
|
-
size="icon"
|
|
167
|
-
className="
|
|
149
|
+
size="icon-sm"
|
|
150
|
+
className="text-muted-foreground hover:text-foreground"
|
|
168
151
|
onClick={() => setSelected(null)}
|
|
169
152
|
aria-label="Deselect"
|
|
170
153
|
>
|
|
171
154
|
<X className="size-3.5" />
|
|
172
155
|
</Button>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<Separator />
|
|
184
|
-
|
|
185
|
-
<Section title="Typography">
|
|
186
|
-
<FontSizeField snapshot={pinSnapshot} apply={apply} />
|
|
187
|
-
<FontWeightField snapshot={pinSnapshot} apply={apply} />
|
|
188
|
-
<StyleToggles snapshot={pinSnapshot} apply={apply} />
|
|
189
|
-
<LineHeightField snapshot={pinSnapshot} apply={apply} />
|
|
190
|
-
<LetterSpacingField snapshot={pinSnapshot} apply={apply} />
|
|
191
|
-
<TextAlignField snapshot={pinSnapshot} apply={apply} />
|
|
192
|
-
</Section>
|
|
193
|
-
|
|
194
|
-
<Separator />
|
|
195
|
-
|
|
196
|
-
<Section title="Color">
|
|
197
|
-
<ColorField
|
|
198
|
-
label="Text"
|
|
199
|
-
value={pinSnapshot.color}
|
|
200
|
-
onChange={(v) => apply([{ kind: 'set-style', key: 'color', value: v }])}
|
|
201
|
-
clearable={false}
|
|
202
|
-
/>
|
|
203
|
-
<ColorField
|
|
204
|
-
label="Background"
|
|
205
|
-
value={pinSnapshot.backgroundColor ?? '#ffffff'}
|
|
206
|
-
dim={!pinSnapshot.backgroundColor}
|
|
207
|
-
onChange={(v) => apply([{ kind: 'set-style', key: 'backgroundColor', value: v }])}
|
|
208
|
-
onClear={() => apply([{ kind: 'set-style', key: 'backgroundColor', value: null }])}
|
|
209
|
-
clearable
|
|
210
|
-
/>
|
|
211
|
-
</Section>
|
|
212
|
-
|
|
213
|
-
{pinSnapshot.imageSrc !== null && (
|
|
214
|
-
<>
|
|
215
|
-
<Separator />
|
|
216
|
-
<Section title="Image">
|
|
217
|
-
<ImageField slideId={slideId} src={pinSnapshot.imageSrc} apply={apply} />
|
|
218
|
-
</Section>
|
|
219
|
-
</>
|
|
220
|
-
)}
|
|
221
|
-
|
|
222
|
-
<Separator />
|
|
223
|
-
|
|
224
|
-
<div className="mt-auto">
|
|
225
|
-
<CommentsSection
|
|
226
|
-
comments={comments}
|
|
227
|
-
selected={pinSelected}
|
|
228
|
-
onAdd={add}
|
|
229
|
-
onRemove={remove}
|
|
230
|
-
/>
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
</ScrollArea>
|
|
234
|
-
</div>
|
|
235
|
-
</aside>
|
|
236
|
-
);
|
|
237
|
-
}
|
|
156
|
+
</>
|
|
157
|
+
}
|
|
158
|
+
footer={<CommentsSection selected={pinSelected} onAdd={add} />}
|
|
159
|
+
>
|
|
160
|
+
{pinSnapshot.text !== null && (
|
|
161
|
+
<Section title="Content">
|
|
162
|
+
<ContentField snapshot={pinSnapshot} apply={apply} />
|
|
163
|
+
</Section>
|
|
164
|
+
)}
|
|
238
165
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
{
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
166
|
+
<Separator />
|
|
167
|
+
|
|
168
|
+
<Section title="Typography">
|
|
169
|
+
<FontSizeField snapshot={pinSnapshot} apply={apply} />
|
|
170
|
+
<FontWeightField snapshot={pinSnapshot} apply={apply} />
|
|
171
|
+
<StyleToggles snapshot={pinSnapshot} apply={apply} />
|
|
172
|
+
<LineHeightField snapshot={pinSnapshot} apply={apply} />
|
|
173
|
+
<LetterSpacingField snapshot={pinSnapshot} apply={apply} />
|
|
174
|
+
<TextAlignField snapshot={pinSnapshot} apply={apply} />
|
|
175
|
+
</Section>
|
|
176
|
+
|
|
177
|
+
<Separator />
|
|
178
|
+
|
|
179
|
+
<Section title="Color">
|
|
180
|
+
<ColorField
|
|
181
|
+
label="Text"
|
|
182
|
+
value={pinSnapshot.color}
|
|
183
|
+
onChange={(v) => apply([{ kind: 'set-style', key: 'color', value: v }])}
|
|
184
|
+
clearable={false}
|
|
185
|
+
/>
|
|
186
|
+
<ColorField
|
|
187
|
+
label="Background"
|
|
188
|
+
value={pinSnapshot.backgroundColor ?? '#ffffff'}
|
|
189
|
+
dim={!pinSnapshot.backgroundColor}
|
|
190
|
+
onChange={(v) => apply([{ kind: 'set-style', key: 'backgroundColor', value: v }])}
|
|
191
|
+
onClear={() => apply([{ kind: 'set-style', key: 'backgroundColor', value: null }])}
|
|
192
|
+
clearable
|
|
193
|
+
/>
|
|
194
|
+
</Section>
|
|
249
195
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
196
|
+
{pinSnapshot.imageSrc !== null && (
|
|
197
|
+
<>
|
|
198
|
+
<Separator />
|
|
199
|
+
<Section title="Image">
|
|
200
|
+
<ImageField slideId={slideId} src={pinSnapshot.imageSrc} apply={apply} />
|
|
201
|
+
</Section>
|
|
202
|
+
</>
|
|
203
|
+
)}
|
|
204
|
+
|
|
205
|
+
{pinSnapshot.placeholder && (
|
|
206
|
+
<>
|
|
207
|
+
<Separator />
|
|
208
|
+
<Section title="Image placeholder">
|
|
209
|
+
<PlaceholderField
|
|
210
|
+
slideId={slideId}
|
|
211
|
+
hint={pinSnapshot.placeholder.hint}
|
|
212
|
+
line={pinSelected.line}
|
|
213
|
+
column={pinSelected.column}
|
|
214
|
+
applyEdit={applyEdit}
|
|
215
|
+
/>
|
|
216
|
+
</Section>
|
|
217
|
+
</>
|
|
218
|
+
)}
|
|
219
|
+
</PanelShell>
|
|
256
220
|
);
|
|
257
221
|
}
|
|
258
222
|
|
|
@@ -604,40 +568,6 @@ function ColorField({
|
|
|
604
568
|
);
|
|
605
569
|
}
|
|
606
570
|
|
|
607
|
-
function NumberField({
|
|
608
|
-
value,
|
|
609
|
-
onChange,
|
|
610
|
-
min,
|
|
611
|
-
max,
|
|
612
|
-
step = 1,
|
|
613
|
-
suffix,
|
|
614
|
-
}: {
|
|
615
|
-
value: number;
|
|
616
|
-
onChange: (n: number) => void;
|
|
617
|
-
min?: number;
|
|
618
|
-
max?: number;
|
|
619
|
-
step?: number;
|
|
620
|
-
suffix?: string;
|
|
621
|
-
}) {
|
|
622
|
-
return (
|
|
623
|
-
<div className="flex h-8 shrink-0 items-center rounded-md border bg-background pr-2 shadow-xs focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50">
|
|
624
|
-
<input
|
|
625
|
-
type="number"
|
|
626
|
-
value={value}
|
|
627
|
-
onChange={(e) => {
|
|
628
|
-
const n = Number(e.target.value);
|
|
629
|
-
if (Number.isFinite(n)) onChange(n);
|
|
630
|
-
}}
|
|
631
|
-
min={min}
|
|
632
|
-
max={max}
|
|
633
|
-
step={step}
|
|
634
|
-
className="h-full w-12 bg-transparent px-2 text-right text-[11px] tabular-nums outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
|
635
|
-
/>
|
|
636
|
-
{suffix && <span className="text-[10px] text-muted-foreground">{suffix}</span>}
|
|
637
|
-
</div>
|
|
638
|
-
);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
571
|
function ImageField({
|
|
642
572
|
slideId,
|
|
643
573
|
src,
|
|
@@ -694,6 +624,61 @@ function ImageField({
|
|
|
694
624
|
);
|
|
695
625
|
}
|
|
696
626
|
|
|
627
|
+
function PlaceholderField({
|
|
628
|
+
slideId,
|
|
629
|
+
hint,
|
|
630
|
+
line,
|
|
631
|
+
column,
|
|
632
|
+
applyEdit,
|
|
633
|
+
}: {
|
|
634
|
+
slideId: string;
|
|
635
|
+
hint: string;
|
|
636
|
+
line: number;
|
|
637
|
+
column: number;
|
|
638
|
+
applyEdit: (line: number, column: number, ops: EditOp[]) => Promise<void>;
|
|
639
|
+
}) {
|
|
640
|
+
const [open, setOpen] = useState(false);
|
|
641
|
+
const [submitting, setSubmitting] = useState(false);
|
|
642
|
+
return (
|
|
643
|
+
<div className="space-y-2">
|
|
644
|
+
<p className="text-[11px] leading-relaxed text-muted-foreground">
|
|
645
|
+
Hint: <span className="font-medium text-foreground">{hint}</span>
|
|
646
|
+
</p>
|
|
647
|
+
<Button
|
|
648
|
+
type="button"
|
|
649
|
+
variant="outline"
|
|
650
|
+
size="sm"
|
|
651
|
+
className="w-full"
|
|
652
|
+
disabled={submitting}
|
|
653
|
+
onClick={() => setOpen(true)}
|
|
654
|
+
>
|
|
655
|
+
<ImageIcon className="size-3.5" />
|
|
656
|
+
Replace…
|
|
657
|
+
</Button>
|
|
658
|
+
{open && (
|
|
659
|
+
<AssetPickerDialog
|
|
660
|
+
slideId={slideId}
|
|
661
|
+
onClose={() => setOpen(false)}
|
|
662
|
+
onPick={async (asset) => {
|
|
663
|
+
setOpen(false);
|
|
664
|
+
setSubmitting(true);
|
|
665
|
+
try {
|
|
666
|
+
await applyEdit(line, column, [
|
|
667
|
+
{
|
|
668
|
+
kind: 'replace-placeholder-with-image',
|
|
669
|
+
assetPath: `./assets/${asset.name}`,
|
|
670
|
+
},
|
|
671
|
+
]);
|
|
672
|
+
} finally {
|
|
673
|
+
setSubmitting(false);
|
|
674
|
+
}
|
|
675
|
+
}}
|
|
676
|
+
/>
|
|
677
|
+
)}
|
|
678
|
+
</div>
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
697
682
|
function AssetPickerDialog({
|
|
698
683
|
slideId,
|
|
699
684
|
onClose,
|
|
@@ -757,15 +742,11 @@ function AssetPickerDialog({
|
|
|
757
742
|
}
|
|
758
743
|
|
|
759
744
|
function CommentsSection({
|
|
760
|
-
comments,
|
|
761
745
|
selected,
|
|
762
746
|
onAdd,
|
|
763
|
-
onRemove,
|
|
764
747
|
}: {
|
|
765
|
-
comments: SlideComment[];
|
|
766
748
|
selected: { line: number; column: number };
|
|
767
749
|
onAdd: (line: number, column: number, text: string) => Promise<void>;
|
|
768
|
-
onRemove: (id: string) => Promise<void>;
|
|
769
750
|
}) {
|
|
770
751
|
const [draft, setDraft] = useState('');
|
|
771
752
|
const [submitting, setSubmitting] = useState(false);
|
|
@@ -783,68 +764,29 @@ function CommentsSection({
|
|
|
783
764
|
};
|
|
784
765
|
|
|
785
766
|
return (
|
|
786
|
-
<Section title=
|
|
767
|
+
<Section title="Note for the agent">
|
|
787
768
|
<div className="flex flex-col gap-2">
|
|
788
|
-
<
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
e.
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
className="h-7 px-2.5 text-[11px]"
|
|
807
|
-
>
|
|
808
|
-
Add comment
|
|
769
|
+
<div className="comment-cue rounded-[6px]">
|
|
770
|
+
<Textarea
|
|
771
|
+
value={draft}
|
|
772
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
773
|
+
onKeyDown={(e) => {
|
|
774
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
775
|
+
e.preventDefault();
|
|
776
|
+
submit();
|
|
777
|
+
}
|
|
778
|
+
}}
|
|
779
|
+
placeholder="Describe a change for the agent…"
|
|
780
|
+
className="min-h-16 resize-none text-[12px]"
|
|
781
|
+
/>
|
|
782
|
+
</div>
|
|
783
|
+
<div className="flex items-center justify-between gap-2">
|
|
784
|
+
<span className="font-mono text-[10.5px] text-muted-foreground/70">⌘↵ to send</span>
|
|
785
|
+
<Button size="sm" variant="brand" disabled={submitting || !draft.trim()} onClick={submit}>
|
|
786
|
+
Add note
|
|
809
787
|
</Button>
|
|
810
788
|
</div>
|
|
811
789
|
</div>
|
|
812
|
-
|
|
813
|
-
{comments.length === 0 ? (
|
|
814
|
-
<p className="text-[11px] text-muted-foreground">No comments yet.</p>
|
|
815
|
-
) : (
|
|
816
|
-
<>
|
|
817
|
-
<ul className="flex flex-col gap-1">
|
|
818
|
-
{comments.map((c) => (
|
|
819
|
-
<li
|
|
820
|
-
key={c.id}
|
|
821
|
-
className="group flex items-start gap-2 rounded-md border bg-background px-2.5 py-2 transition-colors hover:bg-muted/40"
|
|
822
|
-
>
|
|
823
|
-
<div className="min-w-0 flex-1">
|
|
824
|
-
<div className="font-mono text-[10px] text-muted-foreground">line {c.line}</div>
|
|
825
|
-
<div className="mt-0.5 text-xs leading-relaxed break-words">{c.note}</div>
|
|
826
|
-
</div>
|
|
827
|
-
<Button
|
|
828
|
-
variant="ghost"
|
|
829
|
-
size="icon"
|
|
830
|
-
className="size-6 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 hover:text-destructive"
|
|
831
|
-
onClick={() => onRemove(c.id)}
|
|
832
|
-
aria-label="Delete comment"
|
|
833
|
-
>
|
|
834
|
-
<Trash2 className="size-3.5" />
|
|
835
|
-
</Button>
|
|
836
|
-
</li>
|
|
837
|
-
))}
|
|
838
|
-
</ul>
|
|
839
|
-
<p className="text-[10px] text-muted-foreground">
|
|
840
|
-
Run{' '}
|
|
841
|
-
<code className="rounded bg-muted px-1 py-0.5 font-mono text-foreground">
|
|
842
|
-
/apply-comments
|
|
843
|
-
</code>{' '}
|
|
844
|
-
to apply.
|
|
845
|
-
</p>
|
|
846
|
-
</>
|
|
847
|
-
)}
|
|
848
790
|
</Section>
|
|
849
791
|
);
|
|
850
792
|
}
|
|
@@ -856,6 +798,15 @@ function readSnapshot(el: HTMLElement): ElementSnapshot {
|
|
|
856
798
|
el.tagName === 'IMG'
|
|
857
799
|
? (el as HTMLImageElement).currentSrc || (el as HTMLImageElement).src || null
|
|
858
800
|
: null;
|
|
801
|
+
const ph = el.dataset.slidePlaceholder ?? null;
|
|
802
|
+
const placeholder =
|
|
803
|
+
ph !== null
|
|
804
|
+
? {
|
|
805
|
+
hint: ph,
|
|
806
|
+
width: el.dataset.placeholderW ? Number(el.dataset.placeholderW) : undefined,
|
|
807
|
+
height: el.dataset.placeholderH ? Number(el.dataset.placeholderH) : undefined,
|
|
808
|
+
}
|
|
809
|
+
: null;
|
|
859
810
|
|
|
860
811
|
return {
|
|
861
812
|
fontSize: parseFloat(cs.fontSize) || 16,
|
|
@@ -868,6 +819,7 @@ function readSnapshot(el: HTMLElement): ElementSnapshot {
|
|
|
868
819
|
letterSpacing: parseLetterSpacing(cs.letterSpacing),
|
|
869
820
|
text,
|
|
870
821
|
imageSrc,
|
|
822
|
+
placeholder,
|
|
871
823
|
};
|
|
872
824
|
}
|
|
873
825
|
|