@open-slide/core 1.7.0 → 1.9.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 (43) hide show
  1. package/dist/{build-tLrkKUHr.js → build-ZM7IfDO-.js} +1 -1
  2. package/dist/cli/bin.js +3 -3
  3. package/dist/{config-PwUHqZ_X.js → config-BAZeaz2P.js} +289 -246
  4. package/dist/{config-CfMThYN9.d.ts → config-D_5nlXFU.d.ts} +6 -1
  5. package/dist/{dev-DpCIRbhT.js → dev-BQkNTG_t.js} +1 -1
  6. package/dist/format-CYOb2cAQ.js +1573 -0
  7. package/dist/index.d.ts +4 -4
  8. package/dist/index.js +38 -4
  9. package/dist/locale/index.d.ts +1 -1
  10. package/dist/locale/index.js +1 -1144
  11. package/dist/{preview-BSGlM6Se.js → preview-D8hUtbRA.js} +1 -1
  12. package/dist/{types-B-KrjgX8.d.ts → types-AalTbxMj.d.ts} +17 -3
  13. package/dist/vite/index.d.ts +2 -2
  14. package/dist/vite/index.js +1 -1
  15. package/package.json +2 -1
  16. package/skills/create-theme/SKILL.md +1 -1
  17. package/src/app/components/inspector/comment-widget.tsx +16 -2
  18. package/src/app/components/language-toggle.tsx +39 -0
  19. package/src/app/components/player.tsx +12 -17
  20. package/src/app/components/pptx-progress-toast.tsx +32 -0
  21. package/src/app/components/sidebar/folder-item.tsx +7 -2
  22. package/src/app/components/sidebar/sidebar-footer.tsx +51 -0
  23. package/src/app/components/sidebar/sidebar.tsx +95 -17
  24. package/src/app/lib/design-presets.ts +1 -1
  25. package/src/app/lib/export-pptx.ts +284 -0
  26. package/src/app/lib/folders.ts +28 -0
  27. package/src/app/lib/inspector/fiber.test.ts +154 -0
  28. package/src/app/lib/inspector/fiber.ts +12 -1
  29. package/src/app/lib/locale-store.ts +67 -0
  30. package/src/app/lib/use-click-page-navigation.ts +52 -0
  31. package/src/app/lib/use-is-mobile.ts +21 -0
  32. package/src/app/lib/use-locale.ts +4 -16
  33. package/src/app/routes/home-shell.tsx +8 -0
  34. package/src/app/routes/home.tsx +1 -1
  35. package/src/app/routes/slide.tsx +145 -53
  36. package/src/app/virtual.d.ts +1 -0
  37. package/src/locale/en.ts +18 -3
  38. package/src/locale/ja.ts +19 -3
  39. package/src/locale/types.ts +18 -3
  40. package/src/locale/zh-cn.ts +17 -3
  41. package/src/locale/zh-tw.ts +17 -3
  42. package/dist/en-BDnM5zKJ.js +0 -378
  43. package/src/app/components/click-nav-zones.tsx +0 -36
@@ -1,20 +1,8 @@
1
- import config from 'virtual:open-slide/config';
2
- import { en } from '../../locale/en';
3
- import type { Locale, Plural } from '../../locale/types';
4
-
5
- const resolved: Locale = (config.locale as Locale | undefined) ?? en;
1
+ import type { Locale } from '../../locale/types';
2
+ import { useLocaleValue } from './locale-store';
6
3
 
7
4
  export function useLocale(): Locale {
8
- return resolved;
9
- }
10
-
11
- export function format(template: string, vars: Record<string, string | number>): string {
12
- return template.replace(/\{(\w+)\}/g, (m, key) => {
13
- const v = vars[key];
14
- return v === undefined ? m : String(v);
15
- });
5
+ return useLocaleValue();
16
6
  }
17
7
 
18
- export function plural(count: number, forms: Plural): string {
19
- return count === 1 ? forms.one : forms.other;
20
- }
8
+ export { format, plural } from '../../locale/format';
@@ -39,6 +39,7 @@ export function HomeShell() {
39
39
  create,
40
40
  update,
41
41
  remove,
42
+ reorder,
42
43
  assign,
43
44
  renameSlide,
44
45
  duplicateSlide,
@@ -147,6 +148,13 @@ export function HomeShell() {
147
148
  }}
148
149
  onDropToFolder={(folderId, slideId) => moveSlideWithToast(slideId, folderId)}
149
150
  onDropToDraft={(slideId) => moveSlideWithToast(slideId, null)}
151
+ onReorder={async (ids) => {
152
+ try {
153
+ await reorder(ids);
154
+ } catch {
155
+ toast.error(t.home.toastFolderReorderFailed);
156
+ }
157
+ }}
150
158
  />
151
159
  </div>
152
160
 
@@ -242,7 +242,7 @@ function SortControl({ value, onChange }: { value: SortKey; onChange: (next: Sor
242
242
  <button
243
243
  type="button"
244
244
  aria-label={`${t.home.sortLabel}: ${labels[value]}`}
245
- className="flex h-8 items-center gap-1.5 rounded-[6px] border border-border bg-background pl-2 pr-1.5 text-[12.5px] font-medium text-foreground outline-none hover:bg-muted focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30"
245
+ className="flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-[6px] border border-border bg-background pl-2 pr-1.5 text-[12.5px] font-medium text-foreground outline-none hover:bg-muted focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30"
246
246
  >
247
247
  <FieldIcon k={value} className="size-3.5 text-muted-foreground" />
248
248
  <span>{labels[value]}</span>
@@ -5,13 +5,14 @@ import {
5
5
  ChevronLeft,
6
6
  Download,
7
7
  FileCode2,
8
+ FileImage,
8
9
  FileText,
9
10
  Link2,
10
11
  Loader2,
11
12
  Maximize,
12
13
  MonitorSpeaker,
13
- Pencil,
14
14
  Play,
15
+ Presentation,
15
16
  } from 'lucide-react';
16
17
  import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
17
18
  import { Link, useParams, useSearchParams } from 'react-router-dom';
@@ -34,24 +35,28 @@ import {
34
35
  DropdownMenu,
35
36
  DropdownMenuContent,
36
37
  DropdownMenuItem,
38
+ DropdownMenuSeparator,
37
39
  DropdownMenuTrigger,
38
40
  } from '@/components/ui/dropdown-menu';
39
41
  import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
40
42
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
41
43
  import { useFolders } from '@/lib/folders';
42
44
  import { useAgentSocketConnected } from '@/lib/use-agent-socket';
45
+ import { useClickPageNavigation } from '@/lib/use-click-page-navigation';
46
+ import { useIsMobile } from '@/lib/use-is-mobile';
43
47
  import { format, useLocale } from '@/lib/use-locale';
44
48
  import { useWheelPageNavigation } from '@/lib/use-wheel-page-navigation';
45
49
  import { cn } from '@/lib/utils';
46
- import { ClickNavZones } from '../components/click-nav-zones';
47
50
  import { NotesDrawer } from '../components/notes-drawer';
48
51
  import { PdfProgressToast } from '../components/pdf-progress-toast';
49
52
  import { openPresenterWindow, Player } from '../components/player';
53
+ import { PptxProgressToast } from '../components/pptx-progress-toast';
50
54
  import { SlideCanvas } from '../components/slide-canvas';
51
55
  import { SlideTransitionLayer } from '../components/slide-transition-layer';
52
56
  import { type ThumbnailActions, ThumbnailRail } from '../components/thumbnail-rail';
53
57
  import { exportSlideAsHtml } from '../lib/export-html';
54
58
  import { exportSlideAsPdf, isSafari } from '../lib/export-pdf';
59
+ import { exportSlideAsImagePptx } from '../lib/export-pptx';
55
60
  import { remapNotesSessionCacheAfterReorder } from '../lib/inspector/use-notes';
56
61
  import type { SlideModule } from '../lib/sdk';
57
62
  import { usePrefersReducedMotion } from '../lib/use-prefers-reduced-motion';
@@ -348,7 +353,7 @@ export function Slide() {
348
353
  <SelectionReporter />
349
354
  <div className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">
350
355
  {/* Editorial toolbar — three zones, hairline separators, mono-folio center */}
351
- <header className="flex h-12 shrink-0 items-center gap-2 border-b border-hairline bg-sidebar/85 px-2 backdrop-blur-md md:px-3">
356
+ <header className="relative flex h-12 shrink-0 items-center gap-2 border-b border-hairline bg-sidebar/85 px-2 backdrop-blur-md md:px-3">
352
357
  <div className="flex shrink-0 items-center gap-1.5 md:gap-2">
353
358
  {showSlideBrowser && (
354
359
  <Button asChild variant="ghost" size="icon-sm" title={t.slide.home}>
@@ -382,14 +387,14 @@ export function Slide() {
382
387
  {import.meta.env.DEV && <AgentConnectedBadge />}
383
388
  </div>
384
389
 
385
- {/* Centered title the rail and mobile pill carry the page count. */}
386
- <div className="flex min-w-0 flex-1 justify-center px-2">
387
- <div className="min-w-0 max-w-[34rem]">
390
+ {/* Title centered to the viewport, not the leftover space between the side groups. */}
391
+ <div className="pointer-events-none absolute inset-x-0 flex justify-center px-2">
392
+ <div className="pointer-events-auto min-w-0 max-w-[34rem]">
388
393
  <InlineTitleEditor title={title} onSubmit={(next) => renameSlide(slideId, next)} />
389
394
  </div>
390
395
  </div>
391
396
 
392
- <div className="flex shrink-0 items-center gap-1">
397
+ <div className="ml-auto flex shrink-0 items-center gap-1">
393
398
  {view === 'slides' && (
394
399
  <button
395
400
  type="button"
@@ -500,6 +505,69 @@ export function Slide() {
500
505
  <FileText />
501
506
  {t.slide.exportAsPdf}
502
507
  </DropdownMenuItem>
508
+ <DropdownMenuSeparator />
509
+ <DropdownMenuItem
510
+ disabled={exporting}
511
+ onSelect={async () => {
512
+ if (!slide || exporting) return;
513
+ setExporting(true);
514
+ const toastId = `pptx-export-${slideId}`;
515
+ toast.custom(
516
+ () => (
517
+ <PptxProgressToast
518
+ progress={{
519
+ phase: 'processing',
520
+ current: 0,
521
+ total: pages.length,
522
+ percent: 0,
523
+ }}
524
+ />
525
+ ),
526
+ { id: toastId, duration: Infinity },
527
+ );
528
+ try {
529
+ await exportSlideAsImagePptx(slide, slideId, (p) => {
530
+ toast.custom(() => <PptxProgressToast progress={p} />, {
531
+ id: toastId,
532
+ duration: Infinity,
533
+ });
534
+ });
535
+ } catch (err) {
536
+ console.error('[open-slide] image pptx export failed', err);
537
+ toast.error(t.slide.imagePptxExportFailed, {
538
+ id: toastId,
539
+ duration: 4000,
540
+ });
541
+ } finally {
542
+ setExporting(false);
543
+ toast.dismiss(toastId);
544
+ }
545
+ }}
546
+ >
547
+ <FileImage />
548
+ {t.slide.exportAsImagePptx}
549
+ </DropdownMenuItem>
550
+ <TooltipProvider delayDuration={200}>
551
+ <Tooltip>
552
+ <TooltipTrigger asChild>
553
+ <div
554
+ aria-disabled
555
+ className="relative flex cursor-help items-center justify-between gap-2 rounded-[5px] px-2 py-1.5 text-[12.5px] opacity-45 select-none [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:opacity-80"
556
+ >
557
+ <span className="flex items-center gap-2">
558
+ <Presentation />
559
+ {t.slide.exportAsPptx}
560
+ </span>
561
+ <span className="rounded-[3px] bg-muted px-1.5 py-0.5 font-mono text-[9.5px] tracking-[0.04em] text-muted-foreground">
562
+ {t.slide.comingSoon}
563
+ </span>
564
+ </div>
565
+ </TooltipTrigger>
566
+ <TooltipContent side="left" className="max-w-[240px] leading-relaxed">
567
+ {t.slide.pptxComingSoonTooltip}
568
+ </TooltipContent>
569
+ </Tooltip>
570
+ </TooltipProvider>
503
571
  </DropdownMenuContent>
504
572
  </DropdownMenu>
505
573
  )}
@@ -581,7 +649,7 @@ export function Slide() {
581
649
  data-slide-id={slideId}
582
650
  className="paper relative min-h-0 min-w-0 flex-1 bg-canvas p-2 md:p-10"
583
651
  >
584
- <SlideWheelNavigation
652
+ <SlideViewportNavigation
585
653
  targetRef={slideViewportRef}
586
654
  onPrev={() => goTo(index - 1)}
587
655
  onNext={() => goTo(index + 1)}
@@ -597,12 +665,6 @@ export function Slide() {
597
665
  disabled={prefersReducedMotion}
598
666
  />
599
667
  </SlideCanvas>
600
- <ClickNavZones
601
- onPrev={() => goTo(index - 1)}
602
- onNext={() => goTo(index + 1)}
603
- canPrev={index > 0}
604
- canNext={index < pageCount - 1}
605
- />
606
668
  <InspectOverlay />
607
669
  <SaveBar />
608
670
  {import.meta.env.DEV && <CommentWidget />}
@@ -814,7 +876,7 @@ function SelectionReporter() {
814
876
  return null;
815
877
  }
816
878
 
817
- function SlideWheelNavigation({
879
+ function SlideViewportNavigation({
818
880
  targetRef,
819
881
  onPrev,
820
882
  onNext,
@@ -828,6 +890,7 @@ function SlideWheelNavigation({
828
890
  canNext: boolean;
829
891
  }) {
830
892
  const { active } = useInspector();
893
+ const isMobile = useIsMobile();
831
894
 
832
895
  useWheelPageNavigation({
833
896
  ref: targetRef,
@@ -838,6 +901,19 @@ function SlideWheelNavigation({
838
901
  onNext,
839
902
  });
840
903
 
904
+ // Tap-to-navigate is a touch affordance — desktop has visible prev/next
905
+ // chrome, so it stays edge-only on small screens (matches the old md:hidden
906
+ // zones). Interactive slide content keeps its tap via the hook's passthrough.
907
+ useClickPageNavigation({
908
+ ref: targetRef,
909
+ enabled: isMobile && !active,
910
+ edgeRatio: 0.18,
911
+ canPrev,
912
+ canNext,
913
+ onPrev,
914
+ onNext,
915
+ });
916
+
841
917
  return null;
842
918
  }
843
919
 
@@ -890,49 +966,65 @@ function InlineTitleEditor({
890
966
 
891
967
  if (editing) {
892
968
  return (
893
- <div className="flex flex-1 items-center justify-center">
894
- <input
895
- ref={inputRef}
896
- value={value}
897
- disabled={saving}
898
- onChange={(e) => setValue(e.target.value)}
899
- onBlur={() => {
900
- if (!saving) commit();
901
- }}
902
- onKeyDown={(e) => {
903
- if (e.key === 'Enter') {
904
- e.preventDefault();
905
- commit();
906
- } else if (e.key === 'Escape') {
907
- e.preventDefault();
908
- cancel();
909
- }
910
- }}
911
- maxLength={80}
912
- className="min-w-0 max-w-[min(34rem,90%)] rounded-[5px] border border-foreground/30 bg-card px-2 py-0.5 text-center font-heading text-[13px] font-medium tracking-tight outline-none focus-visible:ring-2 focus-visible:ring-ring/30"
913
- />
969
+ <div className="flex min-w-0 flex-1 items-center justify-center">
970
+ <div className="inline-grid max-w-full items-center">
971
+ <span
972
+ aria-hidden
973
+ className="invisible col-start-1 row-start-1 overflow-hidden whitespace-pre border border-transparent px-2 py-0.5 font-heading text-[13.5px] font-semibold tracking-[-0.01em]"
974
+ >
975
+ {value || ' '}
976
+ </span>
977
+ <input
978
+ ref={inputRef}
979
+ size={1}
980
+ value={value}
981
+ disabled={saving}
982
+ onChange={(e) => setValue(e.target.value)}
983
+ onBlur={() => {
984
+ if (!saving) commit();
985
+ }}
986
+ onKeyDown={(e) => {
987
+ if (e.key === 'Enter') {
988
+ e.preventDefault();
989
+ commit();
990
+ } else if (e.key === 'Escape') {
991
+ e.preventDefault();
992
+ cancel();
993
+ }
994
+ }}
995
+ maxLength={80}
996
+ className="col-start-1 row-start-1 w-full min-w-0 rounded-[5px] border border-foreground/30 bg-card px-2 py-0.5 text-center font-heading text-[13.5px] font-semibold tracking-[-0.01em] outline-none"
997
+ />
998
+ </div>
999
+ </div>
1000
+ );
1001
+ }
1002
+
1003
+ if (!import.meta.env.DEV) {
1004
+ return (
1005
+ <div className="flex min-w-0 items-baseline justify-center">
1006
+ <h1 className="truncate font-heading text-[13.5px] font-semibold tracking-[-0.01em]">
1007
+ {title}
1008
+ </h1>
914
1009
  </div>
915
1010
  );
916
1011
  }
917
1012
 
918
1013
  return (
919
- <div className="group/title flex min-w-0 items-baseline justify-center gap-1.5">
920
- <h1 className="truncate font-heading text-[13.5px] font-semibold tracking-[-0.01em]">
921
- {title}
922
- </h1>
923
- {import.meta.env.DEV && (
924
- <button
925
- type="button"
926
- onClick={() => setEditing(true)}
927
- aria-label={t.slide.renameSlide}
928
- className={cn(
929
- 'flex size-5 shrink-0 items-center justify-center rounded-[4px] text-muted-foreground transition-opacity hover:bg-muted hover:text-foreground',
930
- 'opacity-0 group-hover/title:opacity-100 focus-visible:opacity-100',
931
- )}
932
- >
933
- <Pencil className="size-3" />
934
- </button>
935
- )}
1014
+ <div className="flex min-w-0 items-center justify-center">
1015
+ <button
1016
+ type="button"
1017
+ onClick={() => setEditing(true)}
1018
+ aria-label={t.slide.renameSlide}
1019
+ className={cn(
1020
+ 'min-w-0 max-w-full cursor-text rounded-[5px] border border-transparent px-2 py-0.5 transition-colors',
1021
+ 'hover:border-foreground/30 hover:bg-card focus-visible:border-foreground/30 focus-visible:bg-card focus-visible:outline-none',
1022
+ )}
1023
+ >
1024
+ <h1 className="truncate font-heading text-[13.5px] font-semibold tracking-[-0.01em]">
1025
+ {title}
1026
+ </h1>
1027
+ </button>
936
1028
  </div>
937
1029
  );
938
1030
  }
@@ -13,6 +13,7 @@ declare module 'virtual:open-slide/config' {
13
13
  slidesDir?: string;
14
14
  port?: number;
15
15
  locale?: Locale;
16
+ version: string;
16
17
  build: {
17
18
  showSlideBrowser: boolean;
18
19
  showSlideUi: boolean;
package/src/locale/en.ts CHANGED
@@ -44,6 +44,7 @@ export const en: Locale = {
44
44
  folders: 'Folders',
45
45
  newFolder: 'New folder',
46
46
  folderName: 'Folder name',
47
+ updateAvailable: 'open-slide {version} is available — update the package to get the latest.',
47
48
  changeIcon: 'Change icon',
48
49
  iconEmojiTab: 'Emoji',
49
50
  iconColorTab: 'Color',
@@ -86,6 +87,7 @@ export const en: Locale = {
86
87
  toastSlideMoveFailed: 'Failed to move slide',
87
88
  toastFolderDeleted: 'Deleted folder “{name}”',
88
89
  toastFolderDeleteFailed: 'Failed to delete folder',
90
+ toastFolderReorderFailed: 'Failed to reorder folders',
89
91
  pickIcon: 'Pick icon',
90
92
  },
91
93
 
@@ -104,7 +106,13 @@ export const en: Locale = {
104
106
  toastCopyLinkFailed: 'Failed to copy link',
105
107
  exportAsHtml: 'Export as HTML',
106
108
  exportAsPdf: 'Export as PDF',
109
+ exportAsImagePptx: 'Export as image PPTX',
110
+ exportAsPptx: 'Export as PPTX',
111
+ comingSoon: 'Coming soon',
112
+ pptxComingSoonTooltip:
113
+ 'Editable PPTX export isn’t ready yet. For now, use “Export as image PPTX” instead.',
107
114
  pdfExportFailed: 'PDF export failed',
115
+ imagePptxExportFailed: 'PPTX export failed',
108
116
  pdfExportSafariUnsupported:
109
117
  'Export as PDF is not supported on Safari. Please try a Chromium-based browser instead.',
110
118
  present: 'Present',
@@ -344,6 +352,13 @@ export const en: Locale = {
344
352
  done: 'Done',
345
353
  },
346
354
 
355
+ pptxToast: {
356
+ title: 'Exporting PPTX',
357
+ processing: 'Rendering page {current} of {total}',
358
+ generating: 'Building presentation…',
359
+ done: 'Done',
360
+ },
361
+
347
362
  themeToggle: {
348
363
  toggleAria: 'Toggle theme',
349
364
  title: 'Theme',
@@ -352,9 +367,9 @@ export const en: Locale = {
352
367
  system: 'System',
353
368
  },
354
369
 
355
- clickNav: {
356
- prevAria: 'Previous page',
357
- nextAria: 'Next page',
370
+ languageToggle: {
371
+ toggleAria: 'Change language',
372
+ title: 'Language',
358
373
  },
359
374
 
360
375
  imagePlaceholder: {
package/src/locale/ja.ts CHANGED
@@ -44,6 +44,8 @@ export const ja: Locale = {
44
44
  folders: 'フォルダ',
45
45
  newFolder: '新規フォルダ',
46
46
  folderName: 'フォルダ名',
47
+ updateAvailable:
48
+ 'open-slide {version} が利用可能です — パッケージを更新して最新版を入手してください。',
47
49
  changeIcon: 'アイコンを変更',
48
50
  iconEmojiTab: '絵文字',
49
51
  iconColorTab: 'カラー',
@@ -86,6 +88,7 @@ export const ja: Locale = {
86
88
  toastSlideMoveFailed: 'スライドの移動に失敗しました',
87
89
  toastFolderDeleted: 'フォルダ「{name}」を削除しました',
88
90
  toastFolderDeleteFailed: 'フォルダの削除に失敗しました',
91
+ toastFolderReorderFailed: 'フォルダの並び替えに失敗しました',
89
92
  pickIcon: 'アイコンを選択',
90
93
  },
91
94
 
@@ -104,7 +107,13 @@ export const ja: Locale = {
104
107
  toastCopyLinkFailed: 'リンクのコピーに失敗しました',
105
108
  exportAsHtml: 'HTML として書き出し',
106
109
  exportAsPdf: 'PDF として書き出し',
110
+ exportAsImagePptx: '画像 PPTX として書き出し',
111
+ exportAsPptx: 'PPTX として書き出し',
112
+ comingSoon: '近日公開',
113
+ pptxComingSoonTooltip:
114
+ '編集可能な PPTX の書き出しはまだ対応していません。それまでは「画像 PPTX として書き出し」をご利用ください。',
107
115
  pdfExportFailed: 'PDF の書き出しに失敗しました',
116
+ imagePptxExportFailed: 'PPTX の書き出しに失敗しました',
108
117
  pdfExportSafariUnsupported:
109
118
  'PDF の書き出しは現在 Safari では対応していません。Chromium ベースのブラウザでお試しください。',
110
119
  present: '発表',
@@ -348,6 +357,13 @@ export const ja: Locale = {
348
357
  done: '完了',
349
358
  },
350
359
 
360
+ pptxToast: {
361
+ title: 'PPTX を書き出し中',
362
+ processing: 'ページ {current} / {total} を描画中',
363
+ generating: 'プレゼンテーションを構築中…',
364
+ done: '完了',
365
+ },
366
+
351
367
  themeToggle: {
352
368
  toggleAria: 'テーマを切り替え',
353
369
  title: 'テーマ',
@@ -356,9 +372,9 @@ export const ja: Locale = {
356
372
  system: 'システム',
357
373
  },
358
374
 
359
- clickNav: {
360
- prevAria: '前のページ',
361
- nextAria: '次のページ',
375
+ languageToggle: {
376
+ toggleAria: '言語を切り替え',
377
+ title: '言語',
362
378
  },
363
379
 
364
380
  imagePlaceholder: {
@@ -44,6 +44,7 @@ export type Locale = {
44
44
  folders: string;
45
45
  newFolder: string;
46
46
  folderName: string;
47
+ updateAvailable: string;
47
48
  changeIcon: string;
48
49
  iconEmojiTab: string;
49
50
  iconColorTab: string;
@@ -90,6 +91,7 @@ export type Locale = {
90
91
  /** template: "Deleted folder “{name}”" */
91
92
  toastFolderDeleted: string;
92
93
  toastFolderDeleteFailed: string;
94
+ toastFolderReorderFailed: string;
93
95
  pickIcon: string;
94
96
  };
95
97
 
@@ -106,7 +108,12 @@ export type Locale = {
106
108
  toastCopyLinkFailed: string;
107
109
  exportAsHtml: string;
108
110
  exportAsPdf: string;
111
+ exportAsImagePptx: string;
112
+ exportAsPptx: string;
113
+ comingSoon: string;
114
+ pptxComingSoonTooltip: string;
109
115
  pdfExportFailed: string;
116
+ imagePptxExportFailed: string;
110
117
  pdfExportSafariUnsupported: string;
111
118
  present: string;
112
119
  presentMenuAria: string;
@@ -367,6 +374,14 @@ export type Locale = {
367
374
  done: string;
368
375
  };
369
376
 
377
+ pptxToast: {
378
+ title: string;
379
+ /** template: "Rendering page {current} of {total}" */
380
+ processing: string;
381
+ generating: string;
382
+ done: string;
383
+ };
384
+
370
385
  themeToggle: {
371
386
  toggleAria: string;
372
387
  title: string;
@@ -375,9 +390,9 @@ export type Locale = {
375
390
  system: string;
376
391
  };
377
392
 
378
- clickNav: {
379
- prevAria: string;
380
- nextAria: string;
393
+ languageToggle: {
394
+ toggleAria: string;
395
+ title: string;
381
396
  };
382
397
 
383
398
  imagePlaceholder: {
@@ -44,6 +44,7 @@ export const zhCN: Locale = {
44
44
  folders: '文件夹',
45
45
  newFolder: '新建文件夹',
46
46
  folderName: '文件夹名称',
47
+ updateAvailable: 'open-slide {version} 已发布,请更新软件包以获取最新版本。',
47
48
  changeIcon: '更换图标',
48
49
  iconEmojiTab: 'Emoji',
49
50
  iconColorTab: '颜色',
@@ -86,6 +87,7 @@ export const zhCN: Locale = {
86
87
  toastSlideMoveFailed: '移动幻灯片失败',
87
88
  toastFolderDeleted: '已删除文件夹"{name}"',
88
89
  toastFolderDeleteFailed: '删除文件夹失败',
90
+ toastFolderReorderFailed: '文件夹排序失败',
89
91
  pickIcon: '选择图标',
90
92
  },
91
93
 
@@ -103,7 +105,12 @@ export const zhCN: Locale = {
103
105
  toastCopyLinkFailed: '复制链接失败',
104
106
  exportAsHtml: '导出为 HTML',
105
107
  exportAsPdf: '导出为 PDF',
108
+ exportAsImagePptx: '导出图片 PPTX',
109
+ exportAsPptx: '导出 PPTX',
110
+ comingSoon: '即将推出',
111
+ pptxComingSoonTooltip: '可编辑的 PPTX 导出尚未支持,在此之前可以先使用“导出图片 PPTX”。',
106
112
  pdfExportFailed: 'PDF 导出失败',
113
+ imagePptxExportFailed: 'PPTX 导出失败',
107
114
  pdfExportSafariUnsupported:
108
115
  '导出 PDF 目前不支持 Safari 设备,请尝试使用基于 Chromium 的浏览器替代。',
109
116
  present: '演示',
@@ -343,6 +350,13 @@ export const zhCN: Locale = {
343
350
  done: '完成',
344
351
  },
345
352
 
353
+ pptxToast: {
354
+ title: '导出 PPTX',
355
+ processing: '正在渲染第 {current} / {total} 页',
356
+ generating: '正在组合演示文稿…',
357
+ done: '完成',
358
+ },
359
+
346
360
  themeToggle: {
347
361
  toggleAria: '切换主题',
348
362
  title: '主题',
@@ -351,9 +365,9 @@ export const zhCN: Locale = {
351
365
  system: '系统',
352
366
  },
353
367
 
354
- clickNav: {
355
- prevAria: '上一页',
356
- nextAria: '下一页',
368
+ languageToggle: {
369
+ toggleAria: '切换语言',
370
+ title: '语言',
357
371
  },
358
372
 
359
373
  imagePlaceholder: {
@@ -44,6 +44,7 @@ export const zhTW: Locale = {
44
44
  folders: '資料夾',
45
45
  newFolder: '新增資料夾',
46
46
  folderName: '資料夾名稱',
47
+ updateAvailable: 'open-slide {version} 已發布,請更新套件以取得最新版本。',
47
48
  changeIcon: '變更圖示',
48
49
  iconEmojiTab: 'Emoji',
49
50
  iconColorTab: '顏色',
@@ -86,6 +87,7 @@ export const zhTW: Locale = {
86
87
  toastSlideMoveFailed: '移動投影片失敗',
87
88
  toastFolderDeleted: '已刪除資料夾「{name}」',
88
89
  toastFolderDeleteFailed: '刪除資料夾失敗',
90
+ toastFolderReorderFailed: '資料夾排序失敗',
89
91
  pickIcon: '選擇圖示',
90
92
  },
91
93
 
@@ -103,7 +105,12 @@ export const zhTW: Locale = {
103
105
  toastCopyLinkFailed: '複製連結失敗',
104
106
  exportAsHtml: '匯出為 HTML',
105
107
  exportAsPdf: '匯出為 PDF',
108
+ exportAsImagePptx: '匯出圖片 PPTX',
109
+ exportAsPptx: '匯出 PPTX',
110
+ comingSoon: '即將推出',
111
+ pptxComingSoonTooltip: '可編輯的 PPTX 匯出尚未支援,在此之前可以先使用「匯出圖片 PPTX」。',
106
112
  pdfExportFailed: 'PDF 匯出失敗',
113
+ imagePptxExportFailed: 'PPTX 匯出失敗',
107
114
  pdfExportSafariUnsupported:
108
115
  '匯出 PDF 目前不支援 Safari 裝置,請嘗試用 Chromium 基底瀏覽器替代。',
109
116
  present: '簡報',
@@ -343,6 +350,13 @@ export const zhTW: Locale = {
343
350
  done: '完成',
344
351
  },
345
352
 
353
+ pptxToast: {
354
+ title: '匯出 PPTX',
355
+ processing: '正在算繪第 {current} / {total} 頁',
356
+ generating: '正在組合簡報…',
357
+ done: '完成',
358
+ },
359
+
346
360
  themeToggle: {
347
361
  toggleAria: '切換主題',
348
362
  title: '主題',
@@ -351,9 +365,9 @@ export const zhTW: Locale = {
351
365
  system: '系統',
352
366
  },
353
367
 
354
- clickNav: {
355
- prevAria: '上一頁',
356
- nextAria: '下一頁',
368
+ languageToggle: {
369
+ toggleAria: '切換語言',
370
+ title: '語言',
357
371
  },
358
372
 
359
373
  imagePlaceholder: {