@open-slide/core 1.3.0 → 1.5.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/dist/{build-_276DMmJ.js → build-DZhbjQpQ.js} +1 -1
- package/dist/cli/bin.js +3 -3
- package/dist/{config-D9cZ1A0X.d.ts → config-BQdTMho4.d.ts} +2 -1
- package/dist/{config-BAwKWNtW.js → config-iKjqaX08.js} +2528 -1640
- package/dist/{dev-BoqeVXVq.js → dev-BjLGk5nN.js} +1 -1
- package/dist/{en-CDKzoZvf.js → en-DDGqyNaW.js} +27 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +82 -13
- package/dist/{preview-BLPxspc9.js → preview-jwLWHWkQ.js} +1 -1
- package/dist/{types-JYG1cmwC.d.ts → types-Dpr8nbih.d.ts} +27 -1
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/package.json +1 -1
- package/skills/slide-authoring/SKILL.md +19 -4
- package/src/app/app.tsx +2 -0
- package/src/app/components/asset-view.tsx +111 -18
- package/src/app/components/inspector/inspect-overlay.tsx +49 -3
- package/src/app/components/inspector/inspector-panel.tsx +267 -25
- package/src/app/components/inspector/inspector-provider.tsx +390 -49
- package/src/app/components/panel/panel-shell.tsx +5 -3
- package/src/app/components/player.tsx +25 -5
- package/src/app/components/present/control-bar.tsx +12 -0
- package/src/app/components/present/laser-pointer.tsx +3 -4
- package/src/app/components/present/progress-bar.tsx +4 -4
- package/src/app/components/sidebar/folder-item.tsx +14 -3
- package/src/app/components/sidebar/sidebar.tsx +10 -0
- package/src/app/lib/assets.ts +21 -0
- package/src/app/lib/export-pdf.ts +6 -0
- package/src/app/lib/inspector/use-editor.ts +9 -1
- package/src/app/lib/sdk.ts +2 -0
- package/src/app/lib/slides.ts +9 -0
- package/src/app/lib/use-slide-module.ts +48 -0
- package/src/app/routes/assets.tsx +9 -0
- package/src/app/routes/home-shell.tsx +23 -2
- package/src/app/routes/home.tsx +101 -3
- package/src/app/routes/presenter.tsx +2 -20
- package/src/app/routes/slide.tsx +117 -39
- package/src/app/virtual.d.ts +1 -0
- package/src/locale/en.ts +28 -5
- package/src/locale/ja.ts +28 -5
- package/src/locale/types.ts +27 -1
- package/src/locale/zh-cn.ts +28 -6
- package/src/locale/zh-tw.ts +28 -6
|
@@ -9,14 +9,12 @@ import {
|
|
|
9
9
|
usePresenterChannel,
|
|
10
10
|
} from '../components/present/use-presenter-channel';
|
|
11
11
|
import { SlideCanvas } from '../components/slide-canvas';
|
|
12
|
-
import type { SlideModule } from '../lib/sdk';
|
|
13
12
|
import { CANVAS_HEIGHT, CANVAS_WIDTH } from '../lib/sdk';
|
|
14
|
-
import {
|
|
13
|
+
import { useSlideModule } from '../lib/use-slide-module';
|
|
15
14
|
|
|
16
15
|
export function Presenter() {
|
|
17
16
|
const { slideId = '' } = useParams();
|
|
18
|
-
const
|
|
19
|
-
const [error, setError] = useState<string | null>(null);
|
|
17
|
+
const { slide, error } = useSlideModule(slideId);
|
|
20
18
|
|
|
21
19
|
// Presenter view is a passive mirror of the projection window. It only
|
|
22
20
|
// tracks the index it last heard about; navigation buttons send commands
|
|
@@ -29,22 +27,6 @@ export function Presenter() {
|
|
|
29
27
|
const requestedRef = useRef(false);
|
|
30
28
|
const t = useLocale();
|
|
31
29
|
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
let cancelled = false;
|
|
34
|
-
setSlide(null);
|
|
35
|
-
setError(null);
|
|
36
|
-
loadSlide(slideId)
|
|
37
|
-
.then((mod) => {
|
|
38
|
-
if (!cancelled) setSlide(mod);
|
|
39
|
-
})
|
|
40
|
-
.catch((e) => {
|
|
41
|
-
if (!cancelled) setError(String(e?.message ?? e));
|
|
42
|
-
});
|
|
43
|
-
return () => {
|
|
44
|
-
cancelled = true;
|
|
45
|
-
};
|
|
46
|
-
}, [slideId]);
|
|
47
|
-
|
|
48
30
|
const channel = usePresenterChannel(slideId, (msg) => {
|
|
49
31
|
if (msg.type === 'state') {
|
|
50
32
|
setState(msg.state);
|
package/src/app/routes/slide.tsx
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import config from 'virtual:open-slide/config';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Check,
|
|
4
|
+
ChevronDown,
|
|
5
|
+
ChevronLeft,
|
|
6
|
+
Download,
|
|
7
|
+
FileCode2,
|
|
8
|
+
FileText,
|
|
9
|
+
Link2,
|
|
10
|
+
Loader2,
|
|
11
|
+
Maximize,
|
|
12
|
+
MonitorSpeaker,
|
|
13
|
+
Pencil,
|
|
14
|
+
Play,
|
|
15
|
+
} from 'lucide-react';
|
|
3
16
|
import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
17
|
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
|
5
18
|
import { toast } from 'sonner';
|
|
@@ -33,44 +46,35 @@ import { cn } from '@/lib/utils';
|
|
|
33
46
|
import { ClickNavZones } from '../components/click-nav-zones';
|
|
34
47
|
import { NotesDrawer } from '../components/notes-drawer';
|
|
35
48
|
import { PdfProgressToast } from '../components/pdf-progress-toast';
|
|
36
|
-
import { Player } from '../components/player';
|
|
49
|
+
import { openPresenterWindow, Player } from '../components/player';
|
|
37
50
|
import { SlideCanvas } from '../components/slide-canvas';
|
|
38
51
|
import { type ThumbnailActions, ThumbnailRail } from '../components/thumbnail-rail';
|
|
39
52
|
import { exportSlideAsHtml } from '../lib/export-html';
|
|
40
|
-
import { exportSlideAsPdf } from '../lib/export-pdf';
|
|
53
|
+
import { exportSlideAsPdf, isSafari } from '../lib/export-pdf';
|
|
41
54
|
import { remapNotesSessionCacheAfterReorder } from '../lib/inspector/use-notes';
|
|
42
55
|
import type { SlideModule } from '../lib/sdk';
|
|
43
|
-
import {
|
|
56
|
+
import { useSlideModule } from '../lib/use-slide-module';
|
|
44
57
|
|
|
45
58
|
const { showSlideUi, showSlideBrowser, allowHtmlDownload } = config.build;
|
|
46
59
|
|
|
47
60
|
export function Slide() {
|
|
48
61
|
const { slideId = '' } = useParams();
|
|
49
62
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
50
|
-
const
|
|
51
|
-
const [
|
|
52
|
-
const [playing, setPlaying] = useState(false);
|
|
63
|
+
const { slide, error } = useSlideModule(slideId);
|
|
64
|
+
const [playMode, setPlayMode] = useState<'window' | 'fullscreen' | null>(null);
|
|
53
65
|
const [exporting, setExporting] = useState(false);
|
|
66
|
+
const [linkCopied, setLinkCopied] = useState(false);
|
|
67
|
+
const linkCopiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
54
68
|
const [designOpen, setDesignOpen] = useState(false);
|
|
55
|
-
const { renameSlide } = useFolders();
|
|
56
|
-
const slideViewportRef = useRef<HTMLElement>(null);
|
|
57
|
-
const t = useLocale();
|
|
58
69
|
|
|
59
70
|
useEffect(() => {
|
|
60
|
-
let cancelled = false;
|
|
61
|
-
setSlide(null);
|
|
62
|
-
setError(null);
|
|
63
|
-
loadSlide(slideId)
|
|
64
|
-
.then((mod) => {
|
|
65
|
-
if (!cancelled) setSlide(mod);
|
|
66
|
-
})
|
|
67
|
-
.catch((e) => {
|
|
68
|
-
if (!cancelled) setError(String(e?.message ?? e));
|
|
69
|
-
});
|
|
70
71
|
return () => {
|
|
71
|
-
|
|
72
|
+
if (linkCopiedTimerRef.current) clearTimeout(linkCopiedTimerRef.current);
|
|
72
73
|
};
|
|
73
|
-
}, [
|
|
74
|
+
}, []);
|
|
75
|
+
const { renameSlide } = useFolders();
|
|
76
|
+
const slideViewportRef = useRef<HTMLElement>(null);
|
|
77
|
+
const t = useLocale();
|
|
74
78
|
|
|
75
79
|
const modulePages = useMemo(() => slide?.default ?? [], [slide]);
|
|
76
80
|
const [pages, setPages] = useState<typeof modulePages>(modulePages);
|
|
@@ -221,7 +225,7 @@ export function Slide() {
|
|
|
221
225
|
);
|
|
222
226
|
|
|
223
227
|
useEffect(() => {
|
|
224
|
-
if (
|
|
228
|
+
if (playMode) return;
|
|
225
229
|
const onKey = (e: KeyboardEvent) => {
|
|
226
230
|
if (e.target instanceof HTMLElement && e.target.matches('input, textarea')) return;
|
|
227
231
|
if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === 'PageDown') {
|
|
@@ -231,12 +235,12 @@ export function Slide() {
|
|
|
231
235
|
e.preventDefault();
|
|
232
236
|
goTo(index - 1);
|
|
233
237
|
} else if (e.key === 'f' || e.key === 'F') {
|
|
234
|
-
|
|
238
|
+
setPlayMode('fullscreen');
|
|
235
239
|
}
|
|
236
240
|
};
|
|
237
241
|
window.addEventListener('keydown', onKey);
|
|
238
242
|
return () => window.removeEventListener('keydown', onKey);
|
|
239
|
-
}, [index, goTo,
|
|
243
|
+
}, [index, goTo, playMode]);
|
|
240
244
|
|
|
241
245
|
if (error) {
|
|
242
246
|
return (
|
|
@@ -315,16 +319,17 @@ export function Slide() {
|
|
|
315
319
|
);
|
|
316
320
|
}
|
|
317
321
|
|
|
318
|
-
if (
|
|
322
|
+
if (playMode) {
|
|
319
323
|
return (
|
|
320
324
|
<Player
|
|
321
325
|
pages={pages}
|
|
322
326
|
design={slide.design}
|
|
323
327
|
index={index}
|
|
324
328
|
onIndexChange={goTo}
|
|
325
|
-
onExit={() =>
|
|
329
|
+
onExit={() => setPlayMode(null)}
|
|
326
330
|
controls
|
|
327
331
|
slideId={slideId}
|
|
332
|
+
fullscreen={playMode === 'fullscreen'}
|
|
328
333
|
/>
|
|
329
334
|
);
|
|
330
335
|
}
|
|
@@ -380,6 +385,41 @@ export function Slide() {
|
|
|
380
385
|
</div>
|
|
381
386
|
|
|
382
387
|
<div className="flex items-center gap-1">
|
|
388
|
+
{view === 'slides' && (
|
|
389
|
+
<button
|
|
390
|
+
type="button"
|
|
391
|
+
aria-label={t.slide.copyLink}
|
|
392
|
+
title={t.slide.copyLink}
|
|
393
|
+
className={cn(buttonVariants({ variant: 'ghost', size: 'icon-sm' }))}
|
|
394
|
+
onClick={async () => {
|
|
395
|
+
try {
|
|
396
|
+
await navigator.clipboard.writeText(window.location.href);
|
|
397
|
+
toast.success(t.slide.toastCopyLinkSuccess);
|
|
398
|
+
setLinkCopied(true);
|
|
399
|
+
if (linkCopiedTimerRef.current) clearTimeout(linkCopiedTimerRef.current);
|
|
400
|
+
linkCopiedTimerRef.current = setTimeout(() => setLinkCopied(false), 1200);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
console.error('[open-slide] copy link failed', err);
|
|
403
|
+
toast.error(t.slide.toastCopyLinkFailed);
|
|
404
|
+
}
|
|
405
|
+
}}
|
|
406
|
+
>
|
|
407
|
+
<span className="relative grid size-4 place-items-center">
|
|
408
|
+
<Link2
|
|
409
|
+
className={cn(
|
|
410
|
+
'col-start-1 row-start-1 size-4 transition-opacity duration-200',
|
|
411
|
+
linkCopied ? 'opacity-0' : 'opacity-100',
|
|
412
|
+
)}
|
|
413
|
+
/>
|
|
414
|
+
<Check
|
|
415
|
+
className={cn(
|
|
416
|
+
'col-start-1 row-start-1 size-4 transition-opacity duration-200',
|
|
417
|
+
linkCopied ? 'opacity-100' : 'opacity-0',
|
|
418
|
+
)}
|
|
419
|
+
/>
|
|
420
|
+
</span>
|
|
421
|
+
</button>
|
|
422
|
+
)}
|
|
383
423
|
{view === 'slides' && allowHtmlDownload && (
|
|
384
424
|
<DropdownMenu>
|
|
385
425
|
<DropdownMenuTrigger
|
|
@@ -417,6 +457,10 @@ export function Slide() {
|
|
|
417
457
|
disabled={exporting}
|
|
418
458
|
onSelect={async () => {
|
|
419
459
|
if (!slide || exporting) return;
|
|
460
|
+
if (isSafari()) {
|
|
461
|
+
toast.error(t.slide.pdfExportSafariUnsupported, { duration: 5000 });
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
420
464
|
setExporting(true);
|
|
421
465
|
const toastId = `pdf-export-${slideId}`;
|
|
422
466
|
toast.custom(
|
|
@@ -460,18 +504,52 @@ export function Slide() {
|
|
|
460
504
|
{view === 'slides' && <InspectToggleButton />}
|
|
461
505
|
<span aria-hidden className="mx-0.5 hidden h-5 w-px bg-hairline md:block" />
|
|
462
506
|
{view === 'slides' && (
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
507
|
+
<div className="inline-flex items-stretch">
|
|
508
|
+
<Button
|
|
509
|
+
size="sm"
|
|
510
|
+
variant="brand"
|
|
511
|
+
onClick={() => setPlayMode('fullscreen')}
|
|
512
|
+
className="rounded-r-none px-2.5 md:px-3"
|
|
513
|
+
>
|
|
514
|
+
<Play className="size-3.5 fill-current" />
|
|
515
|
+
<span className="hidden md:inline">{t.slide.present}</span>
|
|
516
|
+
<kbd className="ml-1 hidden rounded-[3px] bg-brand-foreground/15 px-1 font-mono text-[9.5px] tracking-[0.04em] md:inline">
|
|
517
|
+
F
|
|
518
|
+
</kbd>
|
|
519
|
+
</Button>
|
|
520
|
+
<DropdownMenu>
|
|
521
|
+
<DropdownMenuTrigger
|
|
522
|
+
type="button"
|
|
523
|
+
aria-label={t.slide.presentMenuAria}
|
|
524
|
+
title={t.slide.presentMenuAria}
|
|
525
|
+
className={cn(
|
|
526
|
+
buttonVariants({ variant: 'brand', size: 'sm' }),
|
|
527
|
+
'rounded-l-none px-1.5 shadow-[inset_1px_0_0_oklch(0_0_0/0.12),inset_0_1px_0_oklch(1_0_0/0.18),0_1px_0_oklch(0_0_0/0.16)]',
|
|
528
|
+
)}
|
|
529
|
+
>
|
|
530
|
+
<ChevronDown className="size-3.5" />
|
|
531
|
+
</DropdownMenuTrigger>
|
|
532
|
+
<DropdownMenuContent align="end" className="min-w-[200px]">
|
|
533
|
+
<DropdownMenuItem onSelect={() => setPlayMode('window')}>
|
|
534
|
+
<Play />
|
|
535
|
+
{t.slide.presentInWindow}
|
|
536
|
+
</DropdownMenuItem>
|
|
537
|
+
<DropdownMenuItem onSelect={() => setPlayMode('fullscreen')}>
|
|
538
|
+
<Maximize />
|
|
539
|
+
{t.slide.presentFullscreen}
|
|
540
|
+
</DropdownMenuItem>
|
|
541
|
+
<DropdownMenuItem
|
|
542
|
+
onSelect={() => {
|
|
543
|
+
if (slideId) openPresenterWindow(slideId);
|
|
544
|
+
setPlayMode('window');
|
|
545
|
+
}}
|
|
546
|
+
>
|
|
547
|
+
<MonitorSpeaker />
|
|
548
|
+
{t.slide.presentPresenter}
|
|
549
|
+
</DropdownMenuItem>
|
|
550
|
+
</DropdownMenuContent>
|
|
551
|
+
</DropdownMenu>
|
|
552
|
+
</div>
|
|
475
553
|
)}
|
|
476
554
|
</div>
|
|
477
555
|
</header>
|
package/src/app/virtual.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ declare module 'virtual:open-slide/slides' {
|
|
|
2
2
|
import type { SlideModule } from './lib/sdk';
|
|
3
3
|
export const slideIds: string[];
|
|
4
4
|
export const slideThemes: Record<string, string>;
|
|
5
|
+
export const slideCreatedAt: Record<string, number>;
|
|
5
6
|
export function loadSlide(id: string): Promise<SlideModule>;
|
|
6
7
|
}
|
|
7
8
|
|
package/src/locale/en.ts
CHANGED
|
@@ -39,6 +39,7 @@ export const en: Locale = {
|
|
|
39
39
|
appTitle: 'open-slide',
|
|
40
40
|
draft: 'Draft',
|
|
41
41
|
themes: 'Themes',
|
|
42
|
+
assets: 'Assets',
|
|
42
43
|
folders: 'Folders',
|
|
43
44
|
newFolder: 'New folder',
|
|
44
45
|
folderName: 'Folder name',
|
|
@@ -48,6 +49,11 @@ export const en: Locale = {
|
|
|
48
49
|
folderActions: 'Folder actions',
|
|
49
50
|
searchPlaceholder: 'Search slides',
|
|
50
51
|
clearSearch: 'Clear search',
|
|
52
|
+
sortLabel: 'Sort',
|
|
53
|
+
sortByCreatedDesc: 'Newest',
|
|
54
|
+
sortByCreatedAsc: 'Oldest',
|
|
55
|
+
sortByTitleAsc: 'A–Z',
|
|
56
|
+
sortByTitleDesc: 'Z–A',
|
|
51
57
|
noMatches: 'No matches',
|
|
52
58
|
nothingMatchesPrefix: 'Nothing matches ',
|
|
53
59
|
nothingMatchesSuffix: ' in this folder.',
|
|
@@ -85,15 +91,24 @@ export const en: Locale = {
|
|
|
85
91
|
backToHome: 'Back to home',
|
|
86
92
|
agentConnected: 'Agent connected',
|
|
87
93
|
agentConnectedTooltip:
|
|
88
|
-
'The
|
|
94
|
+
'The current slide and inspector selection are synced to your agent in real time.',
|
|
89
95
|
agentDisconnected: 'Agent disconnected',
|
|
90
96
|
agentDisconnectedTooltip:
|
|
91
97
|
'Lost connection to the dev server, so your agent can no longer see the current slide or inspector selection. Restart the dev server to restore the connection.',
|
|
92
98
|
download: 'Download',
|
|
99
|
+
copyLink: 'Copy link',
|
|
100
|
+
toastCopyLinkSuccess: 'Link copied to clipboard',
|
|
101
|
+
toastCopyLinkFailed: 'Failed to copy link',
|
|
93
102
|
exportAsHtml: 'Export as HTML',
|
|
94
103
|
exportAsPdf: 'Export as PDF',
|
|
95
104
|
pdfExportFailed: 'PDF export failed',
|
|
105
|
+
pdfExportSafariUnsupported:
|
|
106
|
+
'Export as PDF is not supported on Safari. Please try a Chromium-based browser instead.',
|
|
96
107
|
present: 'Present',
|
|
108
|
+
presentMenuAria: 'Present options',
|
|
109
|
+
presentInWindow: 'Play',
|
|
110
|
+
presentFullscreen: 'Fullscreen',
|
|
111
|
+
presentPresenter: 'Presenter mode',
|
|
97
112
|
slidesTab: 'Slides',
|
|
98
113
|
assetsTab: 'Assets',
|
|
99
114
|
renameSlide: 'Rename slide',
|
|
@@ -137,6 +152,8 @@ export const en: Locale = {
|
|
|
137
152
|
whiteoutAria: 'White screen (W)',
|
|
138
153
|
laserAria: 'Laser pointer (L)',
|
|
139
154
|
presenterAria: 'Presenter view (P)',
|
|
155
|
+
enterFullscreenAria: 'Enter fullscreen',
|
|
156
|
+
exitFullscreenAria: 'Exit fullscreen',
|
|
140
157
|
helpAria: 'Keyboard shortcuts (?)',
|
|
141
158
|
exitAria: 'Exit (Esc)',
|
|
142
159
|
elapsedTime: 'Elapsed time',
|
|
@@ -163,8 +180,7 @@ export const en: Locale = {
|
|
|
163
180
|
inspect: 'Inspect',
|
|
164
181
|
deselect: 'Deselect',
|
|
165
182
|
agentWatching: 'Agent is watching',
|
|
166
|
-
agentWatchingTooltip:
|
|
167
|
-
'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.',
|
|
183
|
+
agentWatchingTooltip: 'The selected element is synced to your agent in real time.',
|
|
168
184
|
agentNotWatching: 'Agent not watching',
|
|
169
185
|
agentNotWatchingTooltip:
|
|
170
186
|
'Lost connection to the dev server, so your agent can no longer see the selected element. Restart the dev server to restore the connection.',
|
|
@@ -206,7 +222,7 @@ export const en: Locale = {
|
|
|
206
222
|
cropResetAria: 'Reset crop',
|
|
207
223
|
leaveComment: 'Leave a comment',
|
|
208
224
|
commentPlaceholder: 'Describe a change for the agent…',
|
|
209
|
-
commentShortcutHint: '⌘↵ to add',
|
|
225
|
+
commentShortcutHint: '⌘/ to focus · ⌘↵ to add',
|
|
210
226
|
addComment: 'Add comment',
|
|
211
227
|
unsavedChanges: {
|
|
212
228
|
one: '{count} unsaved change',
|
|
@@ -251,6 +267,8 @@ export const en: Locale = {
|
|
|
251
267
|
devOnlyMessage: 'Asset management is only available in dev mode.',
|
|
252
268
|
sectionAria: 'Slide assets',
|
|
253
269
|
eyebrow: 'Assets',
|
|
270
|
+
scopeSlide: 'This slide',
|
|
271
|
+
scopeGlobal: 'Global',
|
|
254
272
|
fileCount: { one: '{count} file', other: '{count} files' },
|
|
255
273
|
searchLogos: 'Search logos',
|
|
256
274
|
upload: 'Upload',
|
|
@@ -266,11 +284,16 @@ export const en: Locale = {
|
|
|
266
284
|
renameMenuItem: 'Rename',
|
|
267
285
|
deleteMenuItem: 'Delete',
|
|
268
286
|
conflictTitle: 'File already exists',
|
|
269
|
-
conflictDescription:
|
|
287
|
+
conflictDescription: '{name} is already in the assets folder.',
|
|
270
288
|
conflictReplace: 'Replace',
|
|
271
289
|
conflictRenameCopy: 'Rename copy',
|
|
272
290
|
deleteAssetTitle: 'Delete asset',
|
|
273
291
|
deleteAssetDescription: 'Delete {name}? Imports referencing this file in the slide will break.',
|
|
292
|
+
deleteAssetInUseDescription: '{name} is used in {count} place(s) across {slides} slide(s).',
|
|
293
|
+
deleteAssetInUseHint: 'Deleting will revert these usages back to image placeholders.',
|
|
294
|
+
deleteAndRevert: 'Delete & revert',
|
|
295
|
+
toastRevertFailed: "Couldn't revert usage in {slideId}",
|
|
296
|
+
toastDeletedWithRevert: 'Deleted {name} and reverted {count} usage(s)',
|
|
274
297
|
noPreview: 'No preview available',
|
|
275
298
|
importHintComment: 'import asset from ',
|
|
276
299
|
importHintSemi: ';',
|
package/src/locale/ja.ts
CHANGED
|
@@ -39,6 +39,7 @@ export const ja: Locale = {
|
|
|
39
39
|
appTitle: 'open-slide',
|
|
40
40
|
draft: '下書き',
|
|
41
41
|
themes: 'テーマ',
|
|
42
|
+
assets: 'アセット',
|
|
42
43
|
folders: 'フォルダ',
|
|
43
44
|
newFolder: '新規フォルダ',
|
|
44
45
|
folderName: 'フォルダ名',
|
|
@@ -48,6 +49,11 @@ export const ja: Locale = {
|
|
|
48
49
|
folderActions: 'フォルダ操作',
|
|
49
50
|
searchPlaceholder: 'スライドを検索',
|
|
50
51
|
clearSearch: '検索をクリア',
|
|
52
|
+
sortLabel: '並べ替え',
|
|
53
|
+
sortByCreatedDesc: '新しい順',
|
|
54
|
+
sortByCreatedAsc: '古い順',
|
|
55
|
+
sortByTitleAsc: 'A–Z',
|
|
56
|
+
sortByTitleDesc: 'Z–A',
|
|
51
57
|
noMatches: '一致なし',
|
|
52
58
|
nothingMatchesPrefix: 'このフォルダ内に ',
|
|
53
59
|
nothingMatchesSuffix: ' に一致する項目はありません。',
|
|
@@ -83,17 +89,26 @@ export const ja: Locale = {
|
|
|
83
89
|
slide: {
|
|
84
90
|
agentConnected: 'エージェント接続中',
|
|
85
91
|
agentConnectedTooltip:
|
|
86
|
-
'現在のスライドと Inspector
|
|
92
|
+
'現在のスライドと Inspector の選択はエージェントにリアルタイムで同期されています。',
|
|
87
93
|
agentDisconnected: 'エージェント切断',
|
|
88
94
|
agentDisconnectedTooltip:
|
|
89
95
|
'dev server との接続が切れたため、現在のスライドや Inspector の選択がエージェントに届かなくなっています。dev server を再起動して接続を復旧してください。',
|
|
90
96
|
home: 'ホーム',
|
|
91
97
|
backToHome: 'ホームへ戻る',
|
|
92
98
|
download: 'ダウンロード',
|
|
99
|
+
copyLink: 'リンクをコピー',
|
|
100
|
+
toastCopyLinkSuccess: 'リンクをクリップボードにコピーしました',
|
|
101
|
+
toastCopyLinkFailed: 'リンクのコピーに失敗しました',
|
|
93
102
|
exportAsHtml: 'HTML として書き出し',
|
|
94
103
|
exportAsPdf: 'PDF として書き出し',
|
|
95
104
|
pdfExportFailed: 'PDF の書き出しに失敗しました',
|
|
105
|
+
pdfExportSafariUnsupported:
|
|
106
|
+
'PDF の書き出しは現在 Safari では対応していません。Chromium ベースのブラウザでお試しください。',
|
|
96
107
|
present: '発表',
|
|
108
|
+
presentMenuAria: '発表オプション',
|
|
109
|
+
presentInWindow: '再生',
|
|
110
|
+
presentFullscreen: 'フルスクリーン再生',
|
|
111
|
+
presentPresenter: '発表者モード',
|
|
97
112
|
slidesTab: 'スライド',
|
|
98
113
|
assetsTab: 'アセット',
|
|
99
114
|
renameSlide: 'スライドの名前を変更',
|
|
@@ -137,6 +152,8 @@ export const ja: Locale = {
|
|
|
137
152
|
whiteoutAria: '白い画面 (W)',
|
|
138
153
|
laserAria: 'レーザーポインタ (L)',
|
|
139
154
|
presenterAria: '発表者ビュー (P)',
|
|
155
|
+
enterFullscreenAria: 'フルスクリーンへ',
|
|
156
|
+
exitFullscreenAria: 'フルスクリーン解除',
|
|
140
157
|
helpAria: 'キーボードショートカット (?)',
|
|
141
158
|
exitAria: '終了 (Esc)',
|
|
142
159
|
elapsedTime: '経過時間',
|
|
@@ -200,14 +217,13 @@ export const ja: Locale = {
|
|
|
200
217
|
cropApply: '適用',
|
|
201
218
|
cropResetAria: 'トリミングをリセット',
|
|
202
219
|
agentWatching: 'エージェント監視中',
|
|
203
|
-
agentWatchingTooltip:
|
|
204
|
-
'エージェントは選択中の要素を dev server 経由で把握しています。直接チャットで頼めます。ここにコメントを残すのは、複数の依頼をまとめて出したいときだけで OK。',
|
|
220
|
+
agentWatchingTooltip: '選択中の要素はエージェントにリアルタイムで同期されています。',
|
|
205
221
|
agentNotWatching: 'エージェント未接続',
|
|
206
222
|
agentNotWatchingTooltip:
|
|
207
223
|
'dev server との接続が切れたため、選択中の要素がエージェントに見えなくなっています。dev server を再起動して接続を復旧してください。',
|
|
208
224
|
leaveComment: 'コメントを残す',
|
|
209
225
|
commentPlaceholder: 'エージェントに依頼する変更を記述…',
|
|
210
|
-
commentShortcutHint: '⌘↵ で追加',
|
|
226
|
+
commentShortcutHint: '⌘/ でフォーカス · ⌘↵ で追加',
|
|
211
227
|
addComment: 'コメントを追加',
|
|
212
228
|
unsavedChanges: {
|
|
213
229
|
one: '未保存の変更 {count} 件',
|
|
@@ -253,6 +269,8 @@ export const ja: Locale = {
|
|
|
253
269
|
devOnlyMessage: 'アセット管理は開発モードでのみ利用できます。',
|
|
254
270
|
sectionAria: 'スライドのアセット',
|
|
255
271
|
eyebrow: 'アセット',
|
|
272
|
+
scopeSlide: 'このスライド',
|
|
273
|
+
scopeGlobal: 'グローバル',
|
|
256
274
|
fileCount: { one: 'ファイル {count} 件', other: 'ファイル {count} 件' },
|
|
257
275
|
searchLogos: 'ロゴを検索',
|
|
258
276
|
upload: 'アップロード',
|
|
@@ -268,12 +286,17 @@ export const ja: Locale = {
|
|
|
268
286
|
renameMenuItem: '名前を変更',
|
|
269
287
|
deleteMenuItem: '削除',
|
|
270
288
|
conflictTitle: 'ファイルがすでに存在します',
|
|
271
|
-
conflictDescription: '{name}
|
|
289
|
+
conflictDescription: '{name} はすでにアセットフォルダにあります。',
|
|
272
290
|
conflictReplace: '置き換え',
|
|
273
291
|
conflictRenameCopy: 'コピーをリネーム',
|
|
274
292
|
deleteAssetTitle: 'アセットを削除',
|
|
275
293
|
deleteAssetDescription:
|
|
276
294
|
'{name} を削除しますか?スライド内でこのファイルを参照しているインポートは壊れます。',
|
|
295
|
+
deleteAssetInUseDescription: '{name} は {slides} 枚のスライドで {count} 箇所使用されています。',
|
|
296
|
+
deleteAssetInUseHint: '削除すると、これらの使用箇所は画像プレースホルダーに戻ります。',
|
|
297
|
+
deleteAndRevert: '削除して戻す',
|
|
298
|
+
toastRevertFailed: '{slideId} の使用箇所を戻せませんでした',
|
|
299
|
+
toastDeletedWithRevert: '{name} を削除し、{count} 箇所をプレースホルダーに戻しました',
|
|
277
300
|
noPreview: 'プレビューはありません',
|
|
278
301
|
importHintComment: 'import asset from ',
|
|
279
302
|
importHintSemi: ';',
|
package/src/locale/types.ts
CHANGED
|
@@ -39,6 +39,7 @@ export type Locale = {
|
|
|
39
39
|
appTitle: string;
|
|
40
40
|
draft: string;
|
|
41
41
|
themes: string;
|
|
42
|
+
assets: string;
|
|
42
43
|
folders: string;
|
|
43
44
|
newFolder: string;
|
|
44
45
|
folderName: string;
|
|
@@ -48,6 +49,11 @@ export type Locale = {
|
|
|
48
49
|
folderActions: string;
|
|
49
50
|
searchPlaceholder: string;
|
|
50
51
|
clearSearch: string;
|
|
52
|
+
sortLabel: string;
|
|
53
|
+
sortByCreatedDesc: string;
|
|
54
|
+
sortByCreatedAsc: string;
|
|
55
|
+
sortByTitleAsc: string;
|
|
56
|
+
sortByTitleDesc: string;
|
|
51
57
|
noMatches: string;
|
|
52
58
|
nothingMatchesPrefix: string;
|
|
53
59
|
nothingMatchesSuffix: string;
|
|
@@ -91,10 +97,18 @@ export type Locale = {
|
|
|
91
97
|
agentDisconnected: string;
|
|
92
98
|
agentDisconnectedTooltip: string;
|
|
93
99
|
download: string;
|
|
100
|
+
copyLink: string;
|
|
101
|
+
toastCopyLinkSuccess: string;
|
|
102
|
+
toastCopyLinkFailed: string;
|
|
94
103
|
exportAsHtml: string;
|
|
95
104
|
exportAsPdf: string;
|
|
96
105
|
pdfExportFailed: string;
|
|
106
|
+
pdfExportSafariUnsupported: string;
|
|
97
107
|
present: string;
|
|
108
|
+
presentMenuAria: string;
|
|
109
|
+
presentInWindow: string;
|
|
110
|
+
presentFullscreen: string;
|
|
111
|
+
presentPresenter: string;
|
|
98
112
|
slidesTab: string;
|
|
99
113
|
assetsTab: string;
|
|
100
114
|
renameSlide: string;
|
|
@@ -139,6 +153,8 @@ export type Locale = {
|
|
|
139
153
|
whiteoutAria: string;
|
|
140
154
|
laserAria: string;
|
|
141
155
|
presenterAria: string;
|
|
156
|
+
enterFullscreenAria: string;
|
|
157
|
+
exitFullscreenAria: string;
|
|
142
158
|
helpAria: string;
|
|
143
159
|
exitAria: string;
|
|
144
160
|
elapsedTime: string;
|
|
@@ -251,6 +267,8 @@ export type Locale = {
|
|
|
251
267
|
devOnlyMessage: string;
|
|
252
268
|
sectionAria: string;
|
|
253
269
|
eyebrow: string;
|
|
270
|
+
scopeSlide: string;
|
|
271
|
+
scopeGlobal: string;
|
|
254
272
|
/** templates: "{count} file" / "{count} files" */
|
|
255
273
|
fileCount: Plural;
|
|
256
274
|
searchLogos: string;
|
|
@@ -269,13 +287,21 @@ export type Locale = {
|
|
|
269
287
|
renameMenuItem: string;
|
|
270
288
|
deleteMenuItem: string;
|
|
271
289
|
conflictTitle: string;
|
|
272
|
-
/** template: "{name} is already in
|
|
290
|
+
/** template: "{name} is already in the assets folder." */
|
|
273
291
|
conflictDescription: string;
|
|
274
292
|
conflictReplace: string;
|
|
275
293
|
conflictRenameCopy: string;
|
|
276
294
|
deleteAssetTitle: string;
|
|
277
295
|
/** template: "Delete {name}? Imports referencing this file in the slide will break." */
|
|
278
296
|
deleteAssetDescription: string;
|
|
297
|
+
/** template: "{name} is used in {count} place across {slides} slide." (singular/plural via {count}/{slides}) */
|
|
298
|
+
deleteAssetInUseDescription: string;
|
|
299
|
+
deleteAssetInUseHint: string;
|
|
300
|
+
deleteAndRevert: string;
|
|
301
|
+
/** template: "Couldn't revert usage in {slideId}." */
|
|
302
|
+
toastRevertFailed: string;
|
|
303
|
+
/** template: "Deleted {name} and reverted {count} usage." */
|
|
304
|
+
toastDeletedWithRevert: string;
|
|
279
305
|
noPreview: string;
|
|
280
306
|
importHintComment: string;
|
|
281
307
|
importHintSemi: string;
|
package/src/locale/zh-cn.ts
CHANGED
|
@@ -39,6 +39,7 @@ export const zhCN: Locale = {
|
|
|
39
39
|
appTitle: 'open-slide',
|
|
40
40
|
draft: '草稿',
|
|
41
41
|
themes: '主题',
|
|
42
|
+
assets: '素材',
|
|
42
43
|
folders: '文件夹',
|
|
43
44
|
newFolder: '新建文件夹',
|
|
44
45
|
folderName: '文件夹名称',
|
|
@@ -48,6 +49,11 @@ export const zhCN: Locale = {
|
|
|
48
49
|
folderActions: '文件夹操作',
|
|
49
50
|
searchPlaceholder: '搜索幻灯片',
|
|
50
51
|
clearSearch: '清除搜索',
|
|
52
|
+
sortLabel: '排序',
|
|
53
|
+
sortByCreatedDesc: '最新',
|
|
54
|
+
sortByCreatedAsc: '最旧',
|
|
55
|
+
sortByTitleAsc: 'A–Z',
|
|
56
|
+
sortByTitleDesc: 'Z–A',
|
|
51
57
|
noMatches: '没有匹配项',
|
|
52
58
|
nothingMatchesPrefix: '该文件夹中没有匹配 ',
|
|
53
59
|
nothingMatchesSuffix: ' 的内容。',
|
|
@@ -82,18 +88,26 @@ export const zhCN: Locale = {
|
|
|
82
88
|
|
|
83
89
|
slide: {
|
|
84
90
|
agentConnected: 'Agent 已连接',
|
|
85
|
-
agentConnectedTooltip:
|
|
86
|
-
'Dev server 正在把你目前在哪张 slide、Inspector 选了哪个元素发布给 agent。直接到聊天说"这张 slide"或"这个元素"就行。Production build 不会出现。',
|
|
91
|
+
agentConnectedTooltip: '目前的 slide 与 Inspector 选择会即时同步给 agent。',
|
|
87
92
|
agentDisconnected: 'Agent 已断开',
|
|
88
93
|
agentDisconnectedTooltip:
|
|
89
94
|
'已和 dev server 断开连接,agent 没办法再看到你目前的 slide 或 Inspector 选择。请重新启动 dev server 来恢复连接。',
|
|
90
95
|
home: '首页',
|
|
91
96
|
backToHome: '返回首页',
|
|
92
97
|
download: '下载',
|
|
98
|
+
copyLink: '复制链接',
|
|
99
|
+
toastCopyLinkSuccess: '已复制链接到剪贴板',
|
|
100
|
+
toastCopyLinkFailed: '复制链接失败',
|
|
93
101
|
exportAsHtml: '导出为 HTML',
|
|
94
102
|
exportAsPdf: '导出为 PDF',
|
|
95
103
|
pdfExportFailed: 'PDF 导出失败',
|
|
104
|
+
pdfExportSafariUnsupported:
|
|
105
|
+
'导出 PDF 目前不支持 Safari 设备,请尝试使用基于 Chromium 的浏览器替代。',
|
|
96
106
|
present: '演示',
|
|
107
|
+
presentMenuAria: '演示选项',
|
|
108
|
+
presentInWindow: '播放',
|
|
109
|
+
presentFullscreen: '全屏播放',
|
|
110
|
+
presentPresenter: '演讲者模式',
|
|
97
111
|
slidesTab: '幻灯片',
|
|
98
112
|
assetsTab: '素材',
|
|
99
113
|
renameSlide: '重命名幻灯片',
|
|
@@ -137,6 +151,8 @@ export const zhCN: Locale = {
|
|
|
137
151
|
whiteoutAria: '白屏 (W)',
|
|
138
152
|
laserAria: '激光笔 (L)',
|
|
139
153
|
presenterAria: '演讲者视图 (P)',
|
|
154
|
+
enterFullscreenAria: '进入全屏',
|
|
155
|
+
exitFullscreenAria: '退出全屏',
|
|
140
156
|
helpAria: '键盘快捷键 (?)',
|
|
141
157
|
exitAria: '退出 (Esc)',
|
|
142
158
|
elapsedTime: '已用时',
|
|
@@ -199,14 +215,13 @@ export const zhCN: Locale = {
|
|
|
199
215
|
cropApply: '应用',
|
|
200
216
|
cropResetAria: '重置裁剪',
|
|
201
217
|
agentWatching: 'Agent 正在关注',
|
|
202
|
-
agentWatchingTooltip:
|
|
203
|
-
'Agent 已经通过 dev server 看到你选的元素了,直接到聊天请它修改就行。想累积几个再一次问才需要在这里留 comments。',
|
|
218
|
+
agentWatchingTooltip: '选取的元素会即时同步给 agent。',
|
|
204
219
|
agentNotWatching: 'Agent 没在关注',
|
|
205
220
|
agentNotWatchingTooltip:
|
|
206
221
|
'已和 dev server 断开连接,agent 看不到你选的元素了。请重新启动 dev server 来恢复连接。',
|
|
207
222
|
leaveComment: '留个 comment',
|
|
208
223
|
commentPlaceholder: '描述你希望代理执行的更改…',
|
|
209
|
-
commentShortcutHint: '⌘↵ 添加',
|
|
224
|
+
commentShortcutHint: '⌘/ 聚焦 · ⌘↵ 添加',
|
|
210
225
|
addComment: '添加 comment',
|
|
211
226
|
unsavedChanges: {
|
|
212
227
|
one: '{count} 项未保存的更改',
|
|
@@ -251,6 +266,8 @@ export const zhCN: Locale = {
|
|
|
251
266
|
devOnlyMessage: '素材管理仅在开发模式下可用。',
|
|
252
267
|
sectionAria: '幻灯片素材',
|
|
253
268
|
eyebrow: '素材',
|
|
269
|
+
scopeSlide: '当前幻灯片',
|
|
270
|
+
scopeGlobal: '全局',
|
|
254
271
|
fileCount: { one: '{count} 个文件', other: '{count} 个文件' },
|
|
255
272
|
searchLogos: '搜索 Logo',
|
|
256
273
|
upload: '上传',
|
|
@@ -266,11 +283,16 @@ export const zhCN: Locale = {
|
|
|
266
283
|
renameMenuItem: '重命名',
|
|
267
284
|
deleteMenuItem: '删除',
|
|
268
285
|
conflictTitle: '文件已存在',
|
|
269
|
-
conflictDescription: '{name}
|
|
286
|
+
conflictDescription: '{name} 已在素材文件夹中。',
|
|
270
287
|
conflictReplace: '替换',
|
|
271
288
|
conflictRenameCopy: '重命名副本',
|
|
272
289
|
deleteAssetTitle: '删除素材',
|
|
273
290
|
deleteAssetDescription: '要删除 {name} 吗?幻灯片中引用此文件的导入将失效。',
|
|
291
|
+
deleteAssetInUseDescription: '{name} 在 {slides} 个幻灯片中被使用了 {count} 次。',
|
|
292
|
+
deleteAssetInUseHint: '删除后这些使用处会自动还原为图片占位符。',
|
|
293
|
+
deleteAndRevert: '删除并还原',
|
|
294
|
+
toastRevertFailed: '无法还原 {slideId} 中的使用',
|
|
295
|
+
toastDeletedWithRevert: '已删除 {name} 并还原 {count} 个使用处',
|
|
274
296
|
noPreview: '无预览',
|
|
275
297
|
importHintComment: 'import asset from ',
|
|
276
298
|
importHintSemi: ';',
|