@open-slide/core 1.1.0 → 1.2.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.
Files changed (31) hide show
  1. package/dist/{build-DSqSio-T.js → build-6BeQ3cxb.js} +1 -1
  2. package/dist/cli/bin.js +3 -3
  3. package/dist/{config-KdiYeWtK.js → config-AxZ5OE1u.js} +673 -211
  4. package/dist/{config-C7vMYzFD.d.ts → config-CtT8K4VF.d.ts} +1 -1
  5. package/dist/{dev-B_GVbr11.js → dev-C9eLmUEq.js} +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/locale/index.d.ts +1 -1
  8. package/dist/locale/index.js +96 -20
  9. package/dist/{preview-D_mxhj7w.js → preview-Cunm-f4i.js} +1 -1
  10. package/dist/{types-DYgVpIGo.d.ts → types-CRHIeoNq.d.ts} +28 -4
  11. package/dist/vite/index.d.ts +2 -2
  12. package/dist/vite/index.js +1 -1
  13. package/package.json +1 -1
  14. package/skills/current-slide/SKILL.md +110 -0
  15. package/skills/slide-authoring/SKILL.md +48 -1
  16. package/src/app/components/inspector/image-crop-dialog.tsx +64 -20
  17. package/src/app/components/inspector/inspector-panel.tsx +44 -13
  18. package/src/app/components/inspector/inspector-provider.tsx +60 -7
  19. package/src/app/components/notes-drawer.tsx +117 -0
  20. package/src/app/components/player.tsx +11 -7
  21. package/src/app/components/present/overview-grid.tsx +2 -2
  22. package/src/app/components/thumbnail-rail.tsx +119 -24
  23. package/src/app/components/ui/context-menu.tsx +237 -0
  24. package/src/app/lib/inspector/use-notes.ts +134 -0
  25. package/src/app/routes/home.tsx +34 -12
  26. package/src/app/routes/slide.tsx +209 -74
  27. package/src/locale/en.ts +26 -4
  28. package/src/locale/ja.ts +26 -4
  29. package/src/locale/types.ts +29 -4
  30. package/src/locale/zh-cn.ts +26 -4
  31. package/src/locale/zh-tw.ts +26 -4
@@ -27,7 +27,8 @@ import type { Folder, FolderIcon, SlideModule } from '../lib/sdk';
27
27
  import { loadSlide, slideIds } from '../lib/slides';
28
28
 
29
29
  export function Home() {
30
- const { manifest, create, update, remove, assign, renameSlide, deleteSlide } = useFolders();
30
+ const { manifest, loading, create, update, remove, assign, renameSlide, deleteSlide } =
31
+ useFolders();
31
32
  const [searchParams, setSearchParams] = useSearchParams();
32
33
  const selectedId = searchParams.get('f') ?? DRAFT_ID;
33
34
  const t = useLocale();
@@ -168,23 +169,27 @@ export function Home() {
168
169
  <h1 className="font-heading text-[32px] font-semibold leading-[1.05] tracking-[-0.025em] md:text-[44px]">
169
170
  {title}
170
171
  </h1>
171
- <span className="folio ml-1 self-end pb-2">
172
- {(isSearching ? filteredSlides.length : visibleSlides.length)
173
- .toString()
174
- .padStart(2, '0')}
175
- {isSearching && (
176
- <span className="opacity-40">
177
- /{visibleSlides.length.toString().padStart(2, '0')}
178
- </span>
179
- )}
180
- </span>
172
+ {!loading && (
173
+ <span className="folio ml-1 self-end pb-2">
174
+ {(isSearching ? filteredSlides.length : visibleSlides.length)
175
+ .toString()
176
+ .padStart(2, '0')}
177
+ {isSearching && (
178
+ <span className="opacity-40">
179
+ /{visibleSlides.length.toString().padStart(2, '0')}
180
+ </span>
181
+ )}
182
+ </span>
183
+ )}
181
184
  <div className="ml-auto w-full md:w-auto">
182
185
  <SearchInput value={query} onChange={setQuery} />
183
186
  </div>
184
187
  </div>
185
188
  </header>
186
189
 
187
- {visibleSlides.length === 0 ? (
190
+ {loading ? (
191
+ <HomeLoading />
192
+ ) : visibleSlides.length === 0 ? (
188
193
  <EmptyState isDraft={isDraft} folderName={selectedFolder?.name} />
189
194
  ) : filteredSlides.length === 0 ? (
190
195
  <NoResultsState query={query} onClear={() => setQuery('')} />
@@ -271,6 +276,23 @@ function SearchInput({ value, onChange }: { value: string; onChange: (value: str
271
276
  );
272
277
  }
273
278
 
279
+ function HomeLoading() {
280
+ const t = useLocale();
281
+ return (
282
+ <div className="grid place-items-center px-8 py-24 text-muted-foreground">
283
+ <div className="flex flex-col items-center gap-4">
284
+ <div className="relative h-px w-56 overflow-hidden bg-hairline">
285
+ <span
286
+ aria-hidden
287
+ className="line-loader-bar absolute inset-y-[-0.5px] left-0 w-1/4 bg-foreground"
288
+ />
289
+ </div>
290
+ <span className="eyebrow text-[11.5px]">{t.slide.loadingEyebrow}</span>
291
+ </div>
292
+ </div>
293
+ );
294
+ }
295
+
274
296
  function NoResultsState({ query, onClear }: { query: string; onClear: () => void }) {
275
297
  const t = useLocale();
276
298
  return (
@@ -24,17 +24,20 @@ import {
24
24
  DropdownMenuTrigger,
25
25
  } from '@/components/ui/dropdown-menu';
26
26
  import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
27
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
27
28
  import { useFolders } from '@/lib/folders';
28
- import { useLocale } from '@/lib/use-locale';
29
+ import { format, useLocale } from '@/lib/use-locale';
29
30
  import { useWheelPageNavigation } from '@/lib/use-wheel-page-navigation';
30
31
  import { cn } from '@/lib/utils';
31
32
  import { ClickNavZones } from '../components/click-nav-zones';
33
+ import { NotesDrawer } from '../components/notes-drawer';
32
34
  import { PdfProgressToast } from '../components/pdf-progress-toast';
33
35
  import { Player } from '../components/player';
34
36
  import { SlideCanvas } from '../components/slide-canvas';
35
- import { ThumbnailRail } from '../components/thumbnail-rail';
37
+ import { type ThumbnailActions, ThumbnailRail } from '../components/thumbnail-rail';
36
38
  import { exportSlideAsHtml } from '../lib/export-html';
37
39
  import { exportSlideAsPdf } from '../lib/export-pdf';
40
+ import { remapNotesSessionCacheAfterReorder } from '../lib/inspector/use-notes';
38
41
  import type { SlideModule } from '../lib/sdk';
39
42
  import { loadSlide } from '../lib/slides';
40
43
 
@@ -78,6 +81,33 @@ export function Slide() {
78
81
  const index = Number.isFinite(rawIndex) ? Math.max(0, Math.min(pageCount - 1, rawIndex)) : 0;
79
82
  const view = searchParams.get('view') === 'assets' ? 'assets' : 'slides';
80
83
 
84
+ useEffect(() => {
85
+ if (!import.meta.hot) return;
86
+ if (!slideId || !slide || pageCount === 0) return;
87
+ import.meta.hot.send('open-slide:current', {
88
+ slideId,
89
+ pageIndex: index,
90
+ totalPages: pageCount,
91
+ slideTitle: slide.meta?.title ?? slideId,
92
+ view,
93
+ });
94
+ }, [slideId, index, pageCount, slide, view]);
95
+
96
+ const goTo = useCallback(
97
+ (i: number) => {
98
+ const clamped = Math.max(0, Math.min(pageCount - 1, i));
99
+ setSearchParams(
100
+ (prev) => {
101
+ const next = new URLSearchParams(prev);
102
+ next.set('p', String(clamped + 1));
103
+ return next;
104
+ },
105
+ { replace: true },
106
+ );
107
+ },
108
+ [pageCount, setSearchParams],
109
+ );
110
+
81
111
  const reorderPage = useCallback(
82
112
  async (from: number, to: number) => {
83
113
  if (from === to) return;
@@ -91,21 +121,14 @@ export function Slide() {
91
121
  const [movedIdx] = order.splice(from, 1);
92
122
  order.splice(to, 0, movedIdx);
93
123
 
124
+ remapNotesSessionCacheAfterReorder(slideId, order);
125
+
94
126
  // Keep the user looking at the same page they were on before the drag.
95
127
  let nextIndex = index;
96
128
  if (index === from) nextIndex = to;
97
129
  else if (from < index && to >= index) nextIndex = index - 1;
98
130
  else if (from > index && to <= index) nextIndex = index + 1;
99
- if (nextIndex !== index) {
100
- setSearchParams(
101
- (prev) => {
102
- const params = new URLSearchParams(prev);
103
- params.set('p', String(nextIndex + 1));
104
- return params;
105
- },
106
- { replace: true },
107
- );
108
- }
131
+ if (nextIndex !== index) goTo(nextIndex);
109
132
 
110
133
  try {
111
134
  const res = await fetch(`/__slides/${encodeURIComponent(slideId)}/reorder`, {
@@ -119,25 +142,81 @@ export function Slide() {
119
142
  }
120
143
  } catch (err) {
121
144
  setPages(before);
145
+ const inverse = order.map((_, i) => order.indexOf(i));
146
+ remapNotesSessionCacheAfterReorder(slideId, inverse);
122
147
  toast.error(`Reorder failed: ${String((err as Error).message ?? err)}`);
123
148
  }
124
149
  },
125
- [pages, index, slideId, setSearchParams],
150
+ [pages, index, slideId, goTo],
126
151
  );
127
152
 
128
- const goTo = useCallback(
129
- (i: number) => {
130
- const clamped = Math.max(0, Math.min(pageCount - 1, i));
131
- setSearchParams(
132
- (prev) => {
133
- const next = new URLSearchParams(prev);
134
- next.set('p', String(clamped + 1));
135
- return next;
136
- },
137
- { replace: true },
138
- );
153
+ const duplicatePage = useCallback(
154
+ async (i: number) => {
155
+ const before = pages;
156
+ if (i < 0 || i >= before.length) return;
157
+ const nextPages = [...before];
158
+ nextPages.splice(i + 1, 0, before[i]);
159
+ setPages(nextPages);
160
+ if (index > i) goTo(index + 1);
161
+
162
+ try {
163
+ const res = await fetch(`/__slides/${encodeURIComponent(slideId)}/pages/${i}/duplicate`, {
164
+ method: 'POST',
165
+ });
166
+ if (!res.ok) {
167
+ const detail = await res.json().catch(() => ({ error: res.statusText }));
168
+ throw new Error(detail.error ?? `HTTP ${res.status}`);
169
+ }
170
+ toast.success(format(t.thumbnailRail.toastDuplicated, { n: i + 1 }));
171
+ } catch (err) {
172
+ setPages(before);
173
+ toast.error(
174
+ `${t.thumbnailRail.toastDuplicateFailed}: ${String((err as Error).message ?? err)}`,
175
+ );
176
+ }
139
177
  },
140
- [pageCount, setSearchParams],
178
+ [pages, index, slideId, goTo, t.thumbnailRail],
179
+ );
180
+
181
+ const deletePage = useCallback(
182
+ async (i: number) => {
183
+ const before = pages;
184
+ if (i < 0 || i >= before.length || before.length <= 1) return;
185
+ const nextPages = before.slice(0, i).concat(before.slice(i + 1));
186
+ setPages(nextPages);
187
+ if (index >= i && index > 0) {
188
+ const target = index === i ? Math.min(index, nextPages.length - 1) : index - 1;
189
+ goTo(target);
190
+ }
191
+
192
+ try {
193
+ const res = await fetch(`/__slides/${encodeURIComponent(slideId)}/pages/${i}`, {
194
+ method: 'DELETE',
195
+ });
196
+ if (!res.ok) {
197
+ const detail = await res.json().catch(() => ({ error: res.statusText }));
198
+ throw new Error(detail.error ?? `HTTP ${res.status}`);
199
+ }
200
+ toast.success(format(t.thumbnailRail.toastDeleted, { n: i + 1 }));
201
+ } catch (err) {
202
+ setPages(before);
203
+ toast.error(
204
+ `${t.thumbnailRail.toastDeleteFailed}: ${String((err as Error).message ?? err)}`,
205
+ );
206
+ }
207
+ },
208
+ [pages, index, slideId, goTo, t.thumbnailRail],
209
+ );
210
+
211
+ const thumbnailActions = useMemo<ThumbnailActions | undefined>(
212
+ () =>
213
+ import.meta.env.DEV
214
+ ? {
215
+ onDuplicate: duplicatePage,
216
+ onDelete: deletePage,
217
+ }
218
+ : undefined,
219
+ [duplicatePage, deletePage],
141
220
  );
142
221
 
143
222
  useEffect(() => {
@@ -255,6 +334,7 @@ export function Slide() {
255
334
  return (
256
335
  <HistoryProvider>
257
336
  <InspectorProvider slideId={slideId}>
337
+ <SelectionReporter />
258
338
  <div className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">
259
339
  {/* Editorial toolbar — three zones, hairline separators, mono-folio center */}
260
340
  <header className="relative flex h-12 shrink-0 items-center justify-between border-b border-hairline bg-sidebar/85 px-2 backdrop-blur-md md:px-3">
@@ -288,6 +368,7 @@ export function Slide() {
288
368
  </TabsList>
289
369
  </Tabs>
290
370
  )}
371
+ {import.meta.env.DEV && <AgentConnectedBadge />}
291
372
  </div>
292
373
 
293
374
  {/* Centered title — the rail and mobile pill carry the page count. */}
@@ -400,57 +481,69 @@ export function Slide() {
400
481
  </div>
401
482
  ) : (
402
483
  <DesignProvider slideId={slideId}>
403
- <div className="flex min-h-0 flex-1 flex-col md:flex-row">
404
- <div className="hidden w-[16.5rem] shrink-0 md:block">
405
- <ThumbnailRail
406
- pages={pages}
407
- design={slide.design}
408
- current={index}
409
- onSelect={goTo}
410
- onReorder={import.meta.env.DEV ? reorderPage : undefined}
411
- />
412
- </div>
413
- <main
414
- ref={slideViewportRef}
415
- data-inspector-root
416
- className="paper relative min-h-0 min-w-0 flex-1 bg-canvas p-2 md:p-10"
417
- >
418
- <SlideWheelNavigation
419
- targetRef={slideViewportRef}
420
- onPrev={() => goTo(index - 1)}
421
- onNext={() => goTo(index + 1)}
422
- canPrev={index > 0}
423
- canNext={index < pageCount - 1}
424
- />
425
- <SlideCanvas design={slide.design}>
426
- <CurrentPage />
427
- </SlideCanvas>
428
- <ClickNavZones
429
- onPrev={() => goTo(index - 1)}
430
- onNext={() => goTo(index + 1)}
431
- canPrev={index > 0}
432
- canNext={index < pageCount - 1}
433
- />
434
- <InspectOverlay />
435
- <SaveBar />
436
- {import.meta.env.DEV && <CommentWidget />}
437
- </main>
438
- {/* Mobile-only horizontal rail. Sits below the canvas and
484
+ <div className="flex min-h-0 flex-1 flex-col">
485
+ <div className="flex min-h-0 flex-1 flex-col md:flex-row">
486
+ <div className="hidden w-[16.5rem] shrink-0 md:block">
487
+ <ThumbnailRail
488
+ pages={pages}
489
+ design={slide.design}
490
+ current={index}
491
+ onSelect={goTo}
492
+ onReorder={import.meta.env.DEV ? reorderPage : undefined}
493
+ actions={thumbnailActions}
494
+ />
495
+ </div>
496
+ <main
497
+ ref={slideViewportRef}
498
+ data-inspector-root
499
+ className="paper relative min-h-0 min-w-0 flex-1 bg-canvas p-2 md:p-10"
500
+ >
501
+ <SlideWheelNavigation
502
+ targetRef={slideViewportRef}
503
+ onPrev={() => goTo(index - 1)}
504
+ onNext={() => goTo(index + 1)}
505
+ canPrev={index > 0}
506
+ canNext={index < pageCount - 1}
507
+ />
508
+ <SlideCanvas design={slide.design}>
509
+ <CurrentPage />
510
+ </SlideCanvas>
511
+ <ClickNavZones
512
+ onPrev={() => goTo(index - 1)}
513
+ onNext={() => goTo(index + 1)}
514
+ canPrev={index > 0}
515
+ canNext={index < pageCount - 1}
516
+ />
517
+ <InspectOverlay />
518
+ <SaveBar />
519
+ {import.meta.env.DEV && <CommentWidget />}
520
+ </main>
521
+ {/* Mobile-only horizontal rail. Sits below the canvas and
439
522
  pads its bottom for the iOS home indicator / Safari URL bar. */}
440
- <div
441
- className="shrink-0 border-t border-hairline md:hidden"
442
- style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
443
- >
444
- <ThumbnailRail
445
- pages={pages}
446
- design={slide.design}
447
- current={index}
448
- onSelect={goTo}
449
- orientation="horizontal"
450
- />
523
+ <div
524
+ className="shrink-0 border-t border-hairline md:hidden"
525
+ style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
526
+ >
527
+ <ThumbnailRail
528
+ pages={pages}
529
+ design={slide.design}
530
+ current={index}
531
+ onSelect={goTo}
532
+ orientation="horizontal"
533
+ actions={thumbnailActions}
534
+ />
535
+ </div>
536
+ <InspectorPanel />
537
+ <DesignPanel open={designOpen} onClose={() => setDesignOpen(false)} />
451
538
  </div>
452
- <InspectorPanel />
453
- <DesignPanel open={designOpen} onClose={() => setDesignOpen(false)} />
539
+ {import.meta.env.DEV && (
540
+ <NotesDrawer
541
+ slideId={slideId}
542
+ index={index}
543
+ total={pageCount}
544
+ initial={slide.notes?.[index]}
545
+ />
546
+ )}
454
547
  </div>
455
548
  </DesignProvider>
456
549
  )}
@@ -460,6 +553,48 @@ export function Slide() {
460
553
  );
461
554
  }
462
555
 
556
+ function AgentConnectedBadge() {
557
+ const t = useLocale();
558
+ return (
559
+ <TooltipProvider delayDuration={200}>
560
+ <Tooltip>
561
+ <TooltipTrigger asChild>
562
+ <button
563
+ type="button"
564
+ className="ml-1 flex shrink-0 cursor-help items-center gap-1.5 rounded-[3px] border border-hairline bg-card px-1.5 py-0.5 text-[10.5px] text-foreground/85 outline-none focus-visible:ring-2 focus-visible:ring-ring/30"
565
+ >
566
+ <span aria-hidden className="relative flex size-1.5 items-center justify-center">
567
+ <span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-500 opacity-60" />
568
+ <span className="relative inline-flex size-1.5 rounded-full bg-emerald-500" />
569
+ </span>
570
+ {t.slide.agentConnected}
571
+ </button>
572
+ </TooltipTrigger>
573
+ <TooltipContent side="bottom" align="start" className="max-w-[280px] leading-relaxed">
574
+ {t.slide.agentConnectedTooltip}
575
+ </TooltipContent>
576
+ </Tooltip>
577
+ </TooltipProvider>
578
+ );
579
+ }
580
+
581
+ function SelectionReporter() {
582
+ const { selected } = useInspector();
583
+ useEffect(() => {
584
+ if (!import.meta.hot) return;
585
+ const selection = selected
586
+ ? {
587
+ line: selected.line,
588
+ column: selected.column,
589
+ tagName: selected.anchor.tagName.toLowerCase(),
590
+ text: (selected.anchor.textContent ?? '').replace(/\s+/g, ' ').trim().slice(0, 120),
591
+ }
592
+ : null;
593
+ import.meta.hot.send('open-slide:current', { selection });
594
+ }, [selected]);
595
+ return null;
596
+ }
597
+
463
598
  function SlideWheelNavigation({
464
599
  targetRef,
465
600
  onPrev,
package/src/locale/en.ts CHANGED
@@ -83,6 +83,9 @@ export const en: Locale = {
83
83
  slide: {
84
84
  home: 'Home',
85
85
  backToHome: 'Back to home',
86
+ agentConnected: 'Agent connected',
87
+ agentConnectedTooltip:
88
+ 'The dev server is publishing your current slide and inspector selection to your agent. Ask "this slide" or "this element" in chat and it will resolve. Disappears in production builds.',
86
89
  download: 'Download',
87
90
  exportAsHtml: 'Export as HTML',
88
91
  exportAsPdf: 'Export as PDF',
@@ -156,6 +159,9 @@ export const en: Locale = {
156
159
  inspector: {
157
160
  inspect: 'Inspect',
158
161
  deselect: 'Deselect',
162
+ agentWatching: 'Agent is watching',
163
+ agentWatchingTooltip:
164
+ 'Your agent already sees the selected element via the dev server — just ask it in chat. Leave comments here only when you want to queue a few before asking.',
159
165
  contentSection: 'Content',
160
166
  typographySection: 'Typography',
161
167
  colorSection: 'Color',
@@ -192,10 +198,10 @@ export const en: Locale = {
192
198
  cropFitContain: 'Fit',
193
199
  cropApply: 'Apply',
194
200
  cropResetAria: 'Reset crop',
195
- noteForAgent: 'Note for the agent',
196
- noteAgentPlaceholder: 'Describe a change for the agent…',
197
- noteShortcutHint: '⌘↵ to send',
198
- addNote: 'Add note',
201
+ leaveComment: 'Leave a comment',
202
+ commentPlaceholder: 'Describe a change for the agent…',
203
+ commentShortcutHint: '⌘↵ to add',
204
+ addComment: 'Add comment',
199
205
  unsavedChanges: {
200
206
  one: '{count} unsaved change',
201
207
  other: '{count} unsaved changes',
@@ -288,6 +294,13 @@ export const en: Locale = {
288
294
  thumbnailRail: {
289
295
  pages: 'Pages',
290
296
  goToPageAria: 'Go to page {n}',
297
+ duplicatePage: 'Duplicate',
298
+ deletePage: 'Delete',
299
+ pageActionsAria: 'Page {n} actions',
300
+ toastDuplicated: 'Duplicated page {n}',
301
+ toastDeleted: 'Deleted page {n}',
302
+ toastDuplicateFailed: 'Could not duplicate page',
303
+ toastDeleteFailed: 'Could not delete page',
291
304
  },
292
305
 
293
306
  pdfToast: {
@@ -309,4 +322,13 @@ export const en: Locale = {
309
322
  prevAria: 'Previous page',
310
323
  nextAria: 'Next page',
311
324
  },
325
+
326
+ notesDrawer: {
327
+ toggle: 'Notes',
328
+ pageLabel: 'page {n}/{total}',
329
+ placeholder: 'Write speaker notes for this slide…',
330
+ statusSaving: 'Saving…',
331
+ statusSaved: 'Saved',
332
+ statusError: 'Save failed: {msg}',
333
+ },
312
334
  };
package/src/locale/ja.ts CHANGED
@@ -81,6 +81,9 @@ export const ja: Locale = {
81
81
  },
82
82
 
83
83
  slide: {
84
+ agentConnected: 'エージェント接続中',
85
+ agentConnectedTooltip:
86
+ '現在のスライドと Inspector の選択状態を dev server がエージェントに公開しています。チャットで「このスライド」「この要素」と言えば認識されます。本番ビルドでは表示されません。',
84
87
  home: 'ホーム',
85
88
  backToHome: 'ホームへ戻る',
86
89
  download: 'ダウンロード',
@@ -193,10 +196,13 @@ export const ja: Locale = {
193
196
  cropFitContain: '全体表示',
194
197
  cropApply: '適用',
195
198
  cropResetAria: 'トリミングをリセット',
196
- noteForAgent: 'エージェントへのメモ',
197
- noteAgentPlaceholder: 'エージェントに依頼する変更を記述…',
198
- noteShortcutHint: '⌘↵ で送信',
199
- addNote: 'メモを追加',
199
+ agentWatching: 'エージェント監視中',
200
+ agentWatchingTooltip:
201
+ 'エージェントは選択中の要素を dev server 経由で把握しています。直接チャットで頼めます。ここにコメントを残すのは、複数の依頼をまとめて出したいときだけで OK。',
202
+ leaveComment: 'コメントを残す',
203
+ commentPlaceholder: 'エージェントに依頼する変更を記述…',
204
+ commentShortcutHint: '⌘↵ で追加',
205
+ addComment: 'コメントを追加',
200
206
  unsavedChanges: {
201
207
  one: '未保存の変更 {count} 件',
202
208
  other: '未保存の変更 {count} 件',
@@ -292,6 +298,13 @@ export const ja: Locale = {
292
298
  thumbnailRail: {
293
299
  pages: 'ページ',
294
300
  goToPageAria: 'ページ {n} へ移動',
301
+ duplicatePage: '複製',
302
+ deletePage: '削除',
303
+ pageActionsAria: 'ページ {n} の操作',
304
+ toastDuplicated: 'ページ {n} を複製しました',
305
+ toastDeleted: 'ページ {n} を削除しました',
306
+ toastDuplicateFailed: 'ページを複製できませんでした',
307
+ toastDeleteFailed: 'ページを削除できませんでした',
295
308
  },
296
309
 
297
310
  pdfToast: {
@@ -313,4 +326,13 @@ export const ja: Locale = {
313
326
  prevAria: '前のページ',
314
327
  nextAria: '次のページ',
315
328
  },
329
+
330
+ notesDrawer: {
331
+ toggle: '発表者ノート',
332
+ pageLabel: '{n} / {total} ページ',
333
+ placeholder: 'このスライドの発表者ノートを記入…',
334
+ statusSaving: '保存中…',
335
+ statusSaved: '保存済み',
336
+ statusError: '保存に失敗しました: {msg}',
337
+ },
316
338
  };
@@ -86,6 +86,8 @@ export type Locale = {
86
86
  slide: {
87
87
  home: string;
88
88
  backToHome: string;
89
+ agentConnected: string;
90
+ agentConnectedTooltip: string;
89
91
  download: string;
90
92
  exportAsHtml: string;
91
93
  exportAsPdf: string;
@@ -161,6 +163,8 @@ export type Locale = {
161
163
  inspector: {
162
164
  inspect: string;
163
165
  deselect: string;
166
+ agentWatching: string;
167
+ agentWatchingTooltip: string;
164
168
  contentSection: string;
165
169
  typographySection: string;
166
170
  colorSection: string;
@@ -198,10 +202,10 @@ export type Locale = {
198
202
  cropFitContain: string;
199
203
  cropApply: string;
200
204
  cropResetAria: string;
201
- noteForAgent: string;
202
- noteAgentPlaceholder: string;
203
- noteShortcutHint: string;
204
- addNote: string;
205
+ leaveComment: string;
206
+ commentPlaceholder: string;
207
+ commentShortcutHint: string;
208
+ addComment: string;
205
209
  /** templates: "{count} unsaved change" / "{count} unsaved changes" */
206
210
  unsavedChanges: Plural;
207
211
  /** templates: "{count} comment" / "{count} comments" */
@@ -307,6 +311,16 @@ export type Locale = {
307
311
  pages: string;
308
312
  /** template: "Go to page {n}" */
309
313
  goToPageAria: string;
314
+ duplicatePage: string;
315
+ deletePage: string;
316
+ /** template: "Page {n} actions" */
317
+ pageActionsAria: string;
318
+ /** template: "Duplicated page {n}" */
319
+ toastDuplicated: string;
320
+ /** template: "Deleted page {n}" */
321
+ toastDeleted: string;
322
+ toastDuplicateFailed: string;
323
+ toastDeleteFailed: string;
310
324
  };
311
325
 
312
326
  pdfToast: {
@@ -329,4 +343,15 @@ export type Locale = {
329
343
  prevAria: string;
330
344
  nextAria: string;
331
345
  };
346
+
347
+ notesDrawer: {
348
+ toggle: string;
349
+ /** template: "page {n}/{total}" */
350
+ pageLabel: string;
351
+ placeholder: string;
352
+ statusSaving: string;
353
+ statusSaved: string;
354
+ /** template: "Save failed: {msg}" */
355
+ statusError: string;
356
+ };
332
357
  };
@@ -81,6 +81,9 @@ export const zhCN: Locale = {
81
81
  },
82
82
 
83
83
  slide: {
84
+ agentConnected: 'Agent 已连接',
85
+ agentConnectedTooltip:
86
+ 'Dev server 正在把你目前在哪张 slide、Inspector 选了哪个元素发布给 agent。直接到聊天说"这张 slide"或"这个元素"就行。Production build 不会出现。',
84
87
  home: '首页',
85
88
  backToHome: '返回首页',
86
89
  download: '下载',
@@ -192,10 +195,13 @@ export const zhCN: Locale = {
192
195
  cropFitContain: '完整显示',
193
196
  cropApply: '应用',
194
197
  cropResetAria: '重置裁剪',
195
- noteForAgent: '给代理的备注',
196
- noteAgentPlaceholder: '描述你希望代理执行的更改…',
197
- noteShortcutHint: '⌘↵ 发送',
198
- addNote: '添加备注',
198
+ agentWatching: 'Agent 正在关注',
199
+ agentWatchingTooltip:
200
+ 'Agent 已经通过 dev server 看到你选的元素了,直接到聊天请它修改就行。想累积几个再一次问才需要在这里留 comments。',
201
+ leaveComment: '留个 comment',
202
+ commentPlaceholder: '描述你希望代理执行的更改…',
203
+ commentShortcutHint: '⌘↵ 添加',
204
+ addComment: '添加 comment',
199
205
  unsavedChanges: {
200
206
  one: '{count} 项未保存的更改',
201
207
  other: '{count} 项未保存的更改',
@@ -288,6 +294,13 @@ export const zhCN: Locale = {
288
294
  thumbnailRail: {
289
295
  pages: '页面',
290
296
  goToPageAria: '前往第 {n} 页',
297
+ duplicatePage: '复制',
298
+ deletePage: '删除',
299
+ pageActionsAria: '第 {n} 页的操作',
300
+ toastDuplicated: '已复制第 {n} 页',
301
+ toastDeleted: '已删除第 {n} 页',
302
+ toastDuplicateFailed: '无法复制页面',
303
+ toastDeleteFailed: '无法删除页面',
291
304
  },
292
305
 
293
306
  pdfToast: {
@@ -309,4 +322,13 @@ export const zhCN: Locale = {
309
322
  prevAria: '上一页',
310
323
  nextAria: '下一页',
311
324
  },
325
+
326
+ notesDrawer: {
327
+ toggle: '演讲备注',
328
+ pageLabel: '第 {n} / {total} 页',
329
+ placeholder: '为这一页撰写演讲备注…',
330
+ statusSaving: '保存中…',
331
+ statusSaved: '已保存',
332
+ statusError: '保存失败:{msg}',
333
+ },
312
334
  };