@open-file-viewer/core 0.1.21 → 0.1.23

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/index.cjs CHANGED
@@ -655,7 +655,36 @@ var defaultMessages = {
655
655
  officeUnsupportedIntro: "\u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002{message}",
656
656
  officeUnsupportedSupportedFormats: "\u5F53\u524D\u7248\u672C\u4F18\u5148\u652F\u6301 docx\u3001rtf\u3001odt/fodt\u3001xlsx/xls/csv/ods\u3001pptx/ppsx\u3001odp/fodp \u7684\u57FA\u7840\u5185\u5BB9\u9884\u89C8\u3002",
657
657
  officeErrorWithMessage: "\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A{message}",
658
- officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002"
658
+ officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002",
659
+ officeConvertedTitle: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
660
+ officeConvertedPdfFailed: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
661
+ pdfEncryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
662
+ pdfEncryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
663
+ pdfPreviewFailedTitle: "PDF \u9884\u89C8\u5931\u8D25",
664
+ pdfCorruptedMessage: "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002",
665
+ pdfCannotLoadMessage: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u52A0\u8F7D\u8BE5 PDF\u3002",
666
+ pdfDownload: "\u4E0B\u8F7D PDF",
667
+ pdfPageLoading: "\u9875\u9762 {page} \u52A0\u8F7D\u4E2D...",
668
+ pdfPageEmpty: "\u8BE5\u9875\u6CA1\u6709\u68C0\u6D4B\u5230\u53EF\u663E\u793A\u7684 PDF \u517C\u5BB9\u5185\u5BB9\u3002\u82E5\u8FD9\u662F Illustrator/AI \u6587\u4EF6\uFF0C\u53EF\u80FD\u53EA\u5305\u542B\u79C1\u6709\u7F16\u8F91\u6570\u636E\uFF0C\u5EFA\u8BAE\u5BFC\u51FA\u4E3A PDF/SVG/PNG \u540E\u9884\u89C8\u3002",
669
+ pdfPageRenderFailed: "\u65E0\u6CD5\u6E32\u67D3\u8BE5\u9875\u9762\u3002\u8BE5\u9875\u53EF\u80FD\u5305\u542B\u6D4F\u89C8\u5668 PDF \u5F15\u64CE\u6682\u4E0D\u652F\u6301\u7684\u56FE\u5F62\u3001\u5B57\u4F53\u6216\u538B\u7F29\u7279\u6027\u3002",
670
+ pdfPreviousPage: "\u4E0A\u4E00\u9875",
671
+ pdfNextPage: "\u4E0B\u4E00\u9875",
672
+ pdfPageInput: "\u5F53\u524D\u9875\u7801",
673
+ pdfPagePosition: "/ {total}",
674
+ pdfPageLabel: "\u7B2C {page} \u9875",
675
+ pdfSummaryPages: "\u9875\u6570",
676
+ pdfSummaryPageSizes: "\u9875\u9762\u5C3A\u5BF8",
677
+ pdfSummaryFit: "\u9002\u914D",
678
+ pdfSummaryActualSize: "\u539F\u59CB\u5927\u5C0F",
679
+ pdfSummaryFitWidth: "\u9002\u5408\u5BBD\u5EA6",
680
+ pdfSummaryZoom: "\u7F29\u653E",
681
+ imagePreviewFailedTitle: "\u56FE\u7247\u9884\u89C8\u5931\u8D25",
682
+ imagePreviewFailedMessage: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u76F4\u63A5\u663E\u793A\u8BE5\u56FE\u7247\uFF0C\u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u7F16\u7801\u6682\u4E0D\u53D7\u652F\u6301\u3002",
683
+ imageDownload: "\u4E0B\u8F7D\u56FE\u7247",
684
+ imageZoomOut: "\u7F29\u5C0F",
685
+ imageZoomIn: "\u653E\u5927",
686
+ imageRotate: "\u65CB\u8F6C\u56FE\u7247",
687
+ imageReset: "\u91CD\u7F6E\u56FE\u7247\u89C6\u56FE"
659
688
  },
660
689
  "en-US": {
661
690
  loading: "Loading preview...",
@@ -704,7 +733,36 @@ var defaultMessages = {
704
733
  officeUnsupportedIntro: "is handled by the Office plugin. {message}",
705
734
  officeUnsupportedSupportedFormats: "This version prioritizes basic previews for docx, rtf, odt/fodt, xlsx/xls/csv/ods, pptx/ppsx, and odp/fodp files.",
706
735
  officeErrorWithMessage: "Parser returned: {message}",
707
- officeErrorWithoutMessage: "Parser did not return a specific error."
736
+ officeErrorWithoutMessage: "Parser did not return a specific error.",
737
+ officeConvertedTitle: "High-fidelity Office conversion preview",
738
+ officeConvertedPdfFailed: "The converted Office PDF could not be previewed",
739
+ pdfEncryptedTitle: "This PDF is encrypted and cannot be previewed online",
740
+ pdfEncryptedMessage: "Download and open it with the password, or upload a decrypted PDF file.",
741
+ pdfPreviewFailedTitle: "PDF preview failed",
742
+ pdfCorruptedMessage: "The PDF may be corrupted or use an invalid format.",
743
+ pdfCannotLoadMessage: "This browser could not load the PDF.",
744
+ pdfDownload: "Download PDF",
745
+ pdfPageLoading: "Loading page {page}...",
746
+ pdfPageEmpty: "No displayable PDF-compatible content was detected on this page. If this is an Illustrator/AI file, export it as PDF, SVG, or PNG before previewing.",
747
+ pdfPageRenderFailed: "This page could not be rendered. It may contain graphics, fonts, or compression features that the browser PDF engine does not support.",
748
+ pdfPreviousPage: "Previous page",
749
+ pdfNextPage: "Next page",
750
+ pdfPageInput: "Current page",
751
+ pdfPagePosition: "/ {total}",
752
+ pdfPageLabel: "Page {page}",
753
+ pdfSummaryPages: "Pages",
754
+ pdfSummaryPageSizes: "Page sizes",
755
+ pdfSummaryFit: "Fit",
756
+ pdfSummaryActualSize: "Actual size",
757
+ pdfSummaryFitWidth: "Fit width",
758
+ pdfSummaryZoom: "Zoom",
759
+ imagePreviewFailedTitle: "Image preview failed",
760
+ imagePreviewFailedMessage: "This browser cannot display the image. The file may be corrupted or use an unsupported encoding.",
761
+ imageDownload: "Download image",
762
+ imageZoomOut: "Zoom out",
763
+ imageZoomIn: "Zoom in",
764
+ imageRotate: "Rotate image",
765
+ imageReset: "Reset image view"
708
766
  }
709
767
  };
710
768
  function resolveMessages(options) {
@@ -809,12 +867,17 @@ function createViewer(options) {
809
867
  currentIndex = clampIndex(index, queue.length);
810
868
  await renderQueueItem(currentIndex);
811
869
  };
812
- const toolbar = createToolbar(options.toolbar, viewport, {
813
- getLength: () => queue.length,
814
- next: () => goTo(currentIndex + 1),
815
- previous: () => goTo(currentIndex - 1),
816
- command: (command) => currentInstance?.command?.(command)
817
- });
870
+ const toolbar = createToolbar(
871
+ options.toolbar,
872
+ viewport,
873
+ {
874
+ getLength: () => queue.length,
875
+ next: () => goTo(currentIndex + 1),
876
+ previous: () => goTo(currentIndex - 1),
877
+ command: (command) => currentInstance?.command?.(command)
878
+ },
879
+ options.locale || "en-US"
880
+ );
818
881
  if (toolbar) {
819
882
  host.append(toolbar.element);
820
883
  }
@@ -1051,7 +1114,7 @@ function removeMediaListener(media, listener) {
1051
1114
  }
1052
1115
  media.removeListener?.(listener);
1053
1116
  }
1054
- function createToolbar(toolbar, viewport, queue) {
1117
+ function createToolbar(toolbar, viewport, queue, locale) {
1055
1118
  if (!toolbar) {
1056
1119
  return void 0;
1057
1120
  }
@@ -1059,7 +1122,7 @@ function createToolbar(toolbar, viewport, queue) {
1059
1122
  const element = document.createElement("div");
1060
1123
  element.className = "ofv-toolbar";
1061
1124
  element.setAttribute("role", "toolbar");
1062
- element.setAttribute("aria-label", "File preview toolbar");
1125
+ element.setAttribute("aria-label", defaultToolbarText[locale].ariaLabel);
1063
1126
  let file;
1064
1127
  let currentIndex = 0;
1065
1128
  let currentLength = queue.getLength();
@@ -1120,8 +1183,8 @@ function createToolbar(toolbar, viewport, queue) {
1120
1183
  }
1121
1184
  if (id === "previous" && queue.getLength() > 1) {
1122
1185
  previousButton = addButton(
1123
- getToolbarLabel(options, "previous"),
1124
- getToolbarTitle(options, "previous"),
1186
+ getToolbarLabel(options, locale, "previous"),
1187
+ getToolbarTitle(options, locale, "previous"),
1125
1188
  () => void queue.previous(),
1126
1189
  void 0,
1127
1190
  options.icons?.previous
@@ -1130,8 +1193,8 @@ function createToolbar(toolbar, viewport, queue) {
1130
1193
  }
1131
1194
  if (id === "next" && queue.getLength() > 1) {
1132
1195
  nextButton = addButton(
1133
- getToolbarLabel(options, "next"),
1134
- getToolbarTitle(options, "next"),
1196
+ getToolbarLabel(options, locale, "next"),
1197
+ getToolbarTitle(options, locale, "next"),
1135
1198
  () => void queue.next(),
1136
1199
  void 0,
1137
1200
  options.icons?.next
@@ -1145,27 +1208,27 @@ function createToolbar(toolbar, viewport, queue) {
1145
1208
  return;
1146
1209
  }
1147
1210
  if (id === "zoom-out" && options.zoom) {
1148
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-out");
1211
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-out");
1149
1212
  return;
1150
1213
  }
1151
1214
  if (id === "zoom-in" && options.zoom) {
1152
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-in");
1215
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-in");
1153
1216
  return;
1154
1217
  }
1155
1218
  if (id === "zoom-reset" && options.zoom) {
1156
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-reset");
1219
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-reset");
1157
1220
  zoomResetButton = commandButtons[commandButtons.length - 1]?.button;
1158
1221
  updateZoomLabel();
1159
1222
  return;
1160
1223
  }
1161
1224
  if (id === "rotate-right" && options.rotate) {
1162
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "rotate-right");
1225
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "rotate-right");
1163
1226
  return;
1164
1227
  }
1165
1228
  if (id === "download" && options.download !== false) {
1166
1229
  addButton(
1167
- getToolbarLabel(options, id),
1168
- getToolbarTitle(options, id),
1230
+ getToolbarLabel(options, locale, id),
1231
+ getToolbarTitle(options, locale, id),
1169
1232
  () => getContext().download(),
1170
1233
  void 0,
1171
1234
  options.icons?.download
@@ -1174,8 +1237,8 @@ function createToolbar(toolbar, viewport, queue) {
1174
1237
  }
1175
1238
  if (id === "fullscreen" && options.fullscreen !== false) {
1176
1239
  fullscreenButton = addButton(
1177
- getToolbarLabel(options, id),
1178
- getToolbarTitle(options, id),
1240
+ getToolbarLabel(options, locale, id),
1241
+ getToolbarTitle(options, locale, id),
1179
1242
  () => getContext().fullscreen(),
1180
1243
  void 0,
1181
1244
  options.icons?.fullscreen
@@ -1185,8 +1248,8 @@ function createToolbar(toolbar, viewport, queue) {
1185
1248
  }
1186
1249
  if (id === "print" && options.print) {
1187
1250
  addButton(
1188
- getToolbarLabel(options, id),
1189
- getToolbarTitle(options, id),
1251
+ getToolbarLabel(options, locale, id),
1252
+ getToolbarTitle(options, locale, id),
1190
1253
  () => getContext().print(),
1191
1254
  void 0,
1192
1255
  options.icons?.print
@@ -1212,11 +1275,11 @@ function createToolbar(toolbar, viewport, queue) {
1212
1275
  const renderSearchControl = () => {
1213
1276
  const searchGroup = document.createElement("div");
1214
1277
  searchGroup.className = "ofv-toolbar-search";
1215
- searchGroup.title = getToolbarTitle(options, "search");
1278
+ searchGroup.title = getToolbarTitle(options, locale, "search");
1216
1279
  const nextSearchInput = document.createElement("input");
1217
1280
  nextSearchInput.type = "search";
1218
- nextSearchInput.placeholder = getToolbarLabel(options, "search");
1219
- nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, "search"));
1281
+ nextSearchInput.placeholder = getToolbarLabel(options, locale, "search");
1282
+ nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, locale, "search"));
1220
1283
  const nextSearchCount = document.createElement("span");
1221
1284
  nextSearchCount.className = "ofv-toolbar-search-count";
1222
1285
  searchInput = nextSearchInput;
@@ -1271,7 +1334,7 @@ function createToolbar(toolbar, viewport, queue) {
1271
1334
  }
1272
1335
  setToolbarButtonContent(
1273
1336
  zoomResetButton,
1274
- currentZoom === void 0 ? getToolbarLabel(options, "zoom-reset") : formatToolbarZoom(currentZoom),
1337
+ currentZoom === void 0 ? getToolbarLabel(options, locale, "zoom-reset") : formatToolbarZoom(currentZoom),
1275
1338
  options.icons?.["zoom-reset"]
1276
1339
  );
1277
1340
  }
@@ -1294,8 +1357,8 @@ function createToolbar(toolbar, viewport, queue) {
1294
1357
  const active = isFullscreenActive();
1295
1358
  const id = active ? "exit-fullscreen" : "fullscreen";
1296
1359
  const icon = active ? options.icons?.["exit-fullscreen"] ?? options.icons?.fullscreen : options.icons?.fullscreen;
1297
- setToolbarButtonContent(fullscreenButton, getToolbarLabel(options, id), icon);
1298
- const title = getToolbarTitle(options, id);
1360
+ setToolbarButtonContent(fullscreenButton, getToolbarLabel(options, locale, id), icon);
1361
+ const title = getToolbarTitle(options, locale, id);
1299
1362
  fullscreenButton.title = title;
1300
1363
  fullscreenButton.setAttribute("aria-label", title);
1301
1364
  fullscreenButton.setAttribute("aria-pressed", String(active));
@@ -1419,39 +1482,75 @@ function createToolbarContext({
1419
1482
  clearSearch: search.clear
1420
1483
  };
1421
1484
  }
1422
- var defaultToolbarLabels = {
1423
- previous: "Prev",
1424
- next: "Next",
1425
- queue: "",
1426
- "zoom-out": "-",
1427
- "zoom-in": "+",
1428
- "zoom-reset": "100%",
1429
- "rotate-right": "Rotate",
1430
- download: "Download",
1431
- fullscreen: "Fullscreen",
1432
- "exit-fullscreen": "Exit fullscreen",
1433
- print: "Print",
1434
- search: "Search"
1435
- };
1436
- var defaultToolbarTitles = {
1437
- previous: "Previous file",
1438
- next: "Next file",
1439
- queue: "Current file position",
1440
- "zoom-out": "Zoom out",
1441
- "zoom-in": "Zoom in",
1442
- "zoom-reset": "Reset zoom",
1443
- "rotate-right": "Rotate right",
1444
- download: "Download file",
1445
- fullscreen: "Open preview fullscreen",
1446
- "exit-fullscreen": "Exit fullscreen",
1447
- print: "Print preview",
1448
- search: "Search preview text"
1485
+ var defaultToolbarText = {
1486
+ "zh-CN": {
1487
+ ariaLabel: "\u6587\u4EF6\u9884\u89C8\u5DE5\u5177\u680F",
1488
+ labels: {
1489
+ previous: "\u4E0A\u4E00\u4E2A",
1490
+ next: "\u4E0B\u4E00\u4E2A",
1491
+ queue: "",
1492
+ "zoom-out": "-",
1493
+ "zoom-in": "+",
1494
+ "zoom-reset": "100%",
1495
+ "rotate-right": "\u65CB\u8F6C",
1496
+ download: "\u4E0B\u8F7D",
1497
+ fullscreen: "\u5168\u5C4F",
1498
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1499
+ print: "\u6253\u5370",
1500
+ search: "\u641C\u7D22"
1501
+ },
1502
+ titles: {
1503
+ previous: "\u4E0A\u4E00\u4E2A\u6587\u4EF6",
1504
+ next: "\u4E0B\u4E00\u4E2A\u6587\u4EF6",
1505
+ queue: "\u5F53\u524D\u6587\u4EF6\u4F4D\u7F6E",
1506
+ "zoom-out": "\u7F29\u5C0F",
1507
+ "zoom-in": "\u653E\u5927",
1508
+ "zoom-reset": "\u91CD\u7F6E\u7F29\u653E",
1509
+ "rotate-right": "\u5411\u53F3\u65CB\u8F6C",
1510
+ download: "\u4E0B\u8F7D\u6587\u4EF6",
1511
+ fullscreen: "\u5168\u5C4F\u67E5\u770B\u9884\u89C8",
1512
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1513
+ print: "\u6253\u5370\u9884\u89C8",
1514
+ search: "\u641C\u7D22\u9884\u89C8\u6587\u672C"
1515
+ }
1516
+ },
1517
+ "en-US": {
1518
+ ariaLabel: "File preview toolbar",
1519
+ labels: {
1520
+ previous: "Prev",
1521
+ next: "Next",
1522
+ queue: "",
1523
+ "zoom-out": "-",
1524
+ "zoom-in": "+",
1525
+ "zoom-reset": "100%",
1526
+ "rotate-right": "Rotate",
1527
+ download: "Download",
1528
+ fullscreen: "Fullscreen",
1529
+ "exit-fullscreen": "Exit fullscreen",
1530
+ print: "Print",
1531
+ search: "Search"
1532
+ },
1533
+ titles: {
1534
+ previous: "Previous file",
1535
+ next: "Next file",
1536
+ queue: "Current file position",
1537
+ "zoom-out": "Zoom out",
1538
+ "zoom-in": "Zoom in",
1539
+ "zoom-reset": "Reset zoom",
1540
+ "rotate-right": "Rotate right",
1541
+ download: "Download file",
1542
+ fullscreen: "Open preview fullscreen",
1543
+ "exit-fullscreen": "Exit fullscreen",
1544
+ print: "Print preview",
1545
+ search: "Search preview text"
1546
+ }
1547
+ }
1449
1548
  };
1450
- function getToolbarLabel(options, id) {
1451
- return options.labels?.[id] ?? defaultToolbarLabels[id];
1549
+ function getToolbarLabel(options, locale, id) {
1550
+ return options.labels?.[id] ?? defaultToolbarText[locale].labels[id];
1452
1551
  }
1453
- function getToolbarTitle(options, id) {
1454
- return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarTitles[id];
1552
+ function getToolbarTitle(options, locale, id) {
1553
+ return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarText[locale].titles[id];
1455
1554
  }
1456
1555
  function formatToolbarZoom(zoom) {
1457
1556
  return `${Math.round(zoom * 100)}%`;
@@ -1603,7 +1702,7 @@ function isSafeToolbarIconAttribute(name, value) {
1603
1702
  return !/^\s*(?:javascript|data:text\/html|vbscript):/i.test(value);
1604
1703
  }
1605
1704
  function isBuiltInToolbarAction(id) {
1606
- return id in defaultToolbarLabels;
1705
+ return id in defaultToolbarText["en-US"].labels;
1607
1706
  }
1608
1707
  function createSearchController(root) {
1609
1708
  const markerClass = "ofv-search-match";
@@ -2174,7 +2273,7 @@ function imagePlugin() {
2174
2273
  infoBar.hidden = false;
2175
2274
  infoBar.removeAttribute("aria-hidden");
2176
2275
  infoBar.style.removeProperty("display");
2177
- stage.replaceChildren(createImageFallback(ctx.file.name, url));
2276
+ stage.replaceChildren(createImageFallback(ctx.file.name, url, ctx.options.messages));
2178
2277
  ctx.toolbar?.refreshCommandSupport();
2179
2278
  };
2180
2279
  const setScale = (nextScale) => {
@@ -2199,13 +2298,13 @@ function imagePlugin() {
2199
2298
  return () => button.removeEventListener("click", action);
2200
2299
  };
2201
2300
  const disposers = showInlineControls ? [
2202
- addButton("-", "Zoom out", () => setScale(scale - 0.25)),
2203
- addButton("+", "Zoom in", () => setScale(scale + 0.25)),
2204
- addButton("Rotate", "Rotate image", () => {
2301
+ addButton("-", ctx.options.messages.imageZoomOut, () => setScale(scale - 0.25)),
2302
+ addButton("+", ctx.options.messages.imageZoomIn, () => setScale(scale + 0.25)),
2303
+ addButton(ctx.options.messages.imageRotate, ctx.options.messages.imageRotate, () => {
2205
2304
  rotation += 90;
2206
2305
  updateTransform();
2207
2306
  }),
2208
- addButton("Reset", "Reset image view", reset)
2307
+ addButton(ctx.options.messages.imageReset, ctx.options.messages.imageReset, reset)
2209
2308
  ] : [];
2210
2309
  if (showInlineControls) {
2211
2310
  controls.append(zoomLabel);
@@ -2420,17 +2519,17 @@ function getTiffDimensions(ifd) {
2420
2519
  height: Number(ifd.height || ifd.t257 || 0)
2421
2520
  };
2422
2521
  }
2423
- function createImageFallback(fileName, url) {
2522
+ function createImageFallback(fileName, url, messages) {
2424
2523
  const fallback = document.createElement("div");
2425
2524
  fallback.className = "ofv-fallback";
2426
2525
  const title = document.createElement("strong");
2427
- title.textContent = "\u56FE\u7247\u9884\u89C8\u5931\u8D25";
2526
+ title.textContent = messages.imagePreviewFailedTitle;
2428
2527
  const meta = document.createElement("span");
2429
- meta.textContent = "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u76F4\u63A5\u663E\u793A\u8BE5\u56FE\u7247\uFF0C\u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u7F16\u7801\u6682\u4E0D\u53D7\u652F\u6301\u3002";
2528
+ meta.textContent = messages.imagePreviewFailedMessage;
2430
2529
  const download = document.createElement("a");
2431
2530
  download.href = url;
2432
2531
  download.download = fileName;
2433
- download.textContent = "\u4E0B\u8F7D\u56FE\u7247";
2532
+ download.textContent = messages.imageDownload;
2434
2533
  fallback.append(title, meta, download);
2435
2534
  return fallback;
2436
2535
  }
@@ -4791,17 +4890,20 @@ function pdfPlugin(options = {}) {
4791
4890
  fit: ctx.options.fit,
4792
4891
  zoom: ctx.options.zoom,
4793
4892
  toolbar: ctx.toolbar,
4794
- encryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4795
- encryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4796
- encryptedAction: "\u4E0B\u8F7D PDF",
4893
+ messages: ctx.options.messages,
4797
4894
  revokeUrlOnDestroy: true
4798
4895
  });
4799
4896
  }
4800
4897
  };
4801
4898
  }
4802
4899
  async function renderPdfDocumentPreview(options) {
4900
+ const useLegacyCompatibility = shouldUseLegacyPdfCompatibility(options.compatibilityMode);
4901
+ if (useLegacyCompatibility) {
4902
+ installPromiseWithResolversPolyfill();
4903
+ }
4803
4904
  const pdf = options.pdfjs || await import("pdfjs-dist");
4804
- configurePdfWorker(pdf, options.workerSrc);
4905
+ const messages = { ...defaultMessages["en-US"], ...options.messages };
4906
+ configurePdfWorker(pdf, options.workerSrc, useLegacyCompatibility);
4805
4907
  const viewer = document.createElement("div");
4806
4908
  viewer.className = "ofv-pdf-viewer";
4807
4909
  if (options.title) {
@@ -4830,10 +4932,10 @@ async function renderPdfDocumentPreview(options) {
4830
4932
  url: options.fileUrl
4831
4933
  };
4832
4934
  const fallback = isEncryptedError(error) ? createEncryptedFallback(fileLike, options.fileUrl, {
4833
- title: options.encryptedTitle || "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4834
- message: options.encryptedMessage || "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4835
- action: options.encryptedAction || "\u4E0B\u8F7D PDF"
4836
- }) : createPdfFallback(options.fileName, options.fileUrl, normalizePdfError(error), options.fallbackTitle);
4935
+ title: options.encryptedTitle || messages.pdfEncryptedTitle,
4936
+ message: options.encryptedMessage || messages.pdfEncryptedMessage,
4937
+ action: options.encryptedAction || messages.pdfDownload
4938
+ }) : createPdfFallback(options.fileName, options.fileUrl, normalizePdfError(error, messages), messages, options.fallbackTitle);
4837
4939
  if (!fallback.classList.contains("ofv-pdf-web-fallback")) {
4838
4940
  options.viewport.classList.add("ofv-center");
4839
4941
  }
@@ -4900,8 +5002,43 @@ async function renderPdfDocumentPreview(options) {
4900
5002
  let currentSize = options.size;
4901
5003
  let zoomFactor = getInitialZoom({ options: { zoom: options.zoom ?? 1 } }, 0.25, 4);
4902
5004
  let rotation = 0;
5005
+ let currentPage = 1;
5006
+ const goToPage = (page, scroll = true) => {
5007
+ currentPage = Math.min(pdfDocument.numPages, Math.max(1, Math.round(page) || 1));
5008
+ pageNavigator.setCurrent(currentPage);
5009
+ const wrapper = pageStates[currentPage - 1]?.wrapper;
5010
+ if (!scroll || !wrapper) {
5011
+ return;
5012
+ }
5013
+ const top = Math.max(0, wrapper.offsetTop - 16);
5014
+ if (typeof scroller.scrollTo === "function") {
5015
+ scroller.scrollTo({ top, behavior: "smooth" });
5016
+ } else {
5017
+ scroller.scrollTop = top;
5018
+ }
5019
+ };
5020
+ const pageNavigator = createPdfPageNavigator(pdfDocument.numPages, messages, (page) => goToPage(page));
5021
+ viewer.insertBefore(pageNavigator.element, summary);
5022
+ const handleScrollerScroll = () => {
5023
+ const scrollerRect = scroller.getBoundingClientRect();
5024
+ const targetY = scrollerRect.top + Math.min(Math.max(scroller.clientHeight * 0.25, 40), 180);
5025
+ let nearestPage = currentPage;
5026
+ let nearestDistance = Number.POSITIVE_INFINITY;
5027
+ pageStates.forEach((state, index) => {
5028
+ const distance = Math.abs(state.wrapper.getBoundingClientRect().top - targetY);
5029
+ if (distance < nearestDistance) {
5030
+ nearestDistance = distance;
5031
+ nearestPage = index + 1;
5032
+ }
5033
+ });
5034
+ if (nearestPage !== currentPage) {
5035
+ currentPage = nearestPage;
5036
+ pageNavigator.setCurrent(currentPage);
5037
+ }
5038
+ };
5039
+ scroller.addEventListener("scroll", handleScrollerScroll, { passive: true });
4903
5040
  const updateSummary = () => {
4904
- renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor);
5041
+ renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor, messages);
4905
5042
  options.toolbar?.setZoom(zoomFactor);
4906
5043
  };
4907
5044
  const clearPage = (pageIdx) => {
@@ -4917,7 +5054,9 @@ async function renderPdfDocumentPreview(options) {
4917
5054
  state.canvas = null;
4918
5055
  state.rendered = false;
4919
5056
  state.wrapper.replaceChildren();
4920
- state.wrapper.append(createPageStatus("ofv-pdf-skeleton", `\u9875\u9762 ${pageIdx + 1} \u52A0\u8F7D\u4E2D...`));
5057
+ state.wrapper.append(
5058
+ createPageStatus("ofv-pdf-skeleton", formatPreviewMessage(messages.pdfPageLoading, { page: pageIdx + 1 }))
5059
+ );
4921
5060
  };
4922
5061
  const renderPage = async (pageIdx, size) => {
4923
5062
  const state = pageStates[pageIdx];
@@ -4985,7 +5124,7 @@ async function renderPdfDocumentPreview(options) {
4985
5124
  state.wrapper.appendChild(
4986
5125
  createPageStatus(
4987
5126
  "ofv-pdf-empty",
4988
- "\u8BE5\u9875\u6CA1\u6709\u68C0\u6D4B\u5230\u53EF\u663E\u793A\u7684 PDF \u517C\u5BB9\u5185\u5BB9\u3002\u82E5\u8FD9\u662F Illustrator/AI \u6587\u4EF6\uFF0C\u53EF\u80FD\u53EA\u5305\u542B\u79C1\u6709\u7F16\u8F91\u6570\u636E\uFF0C\u5EFA\u8BAE\u5BFC\u51FA\u4E3A PDF/SVG/PNG \u540E\u9884\u89C8\u3002"
5127
+ messages.pdfPageEmpty
4989
5128
  )
4990
5129
  );
4991
5130
  }
@@ -4993,7 +5132,7 @@ async function renderPdfDocumentPreview(options) {
4993
5132
  console.error(`Failed to render PDF page ${pageIdx + 1}:`, err);
4994
5133
  state.rendered = false;
4995
5134
  state.wrapper.replaceChildren(
4996
- createPageStatus("ofv-pdf-error", "\u65E0\u6CD5\u6E32\u67D3\u8BE5\u9875\u9762\u3002\u8BE5\u9875\u53EF\u80FD\u5305\u542B\u6D4F\u89C8\u5668 PDF \u5F15\u64CE\u6682\u4E0D\u652F\u6301\u7684\u56FE\u5F62\u3001\u5B57\u4F53\u6216\u538B\u7F29\u7279\u6027\u3002")
5135
+ createPageStatus("ofv-pdf-error", messages.pdfPageRenderFailed)
4997
5136
  );
4998
5137
  }
4999
5138
  };
@@ -5034,9 +5173,10 @@ async function renderPdfDocumentPreview(options) {
5034
5173
  const wrapper = document.createElement("div");
5035
5174
  wrapper.className = "ofv-pdf-page-wrapper";
5036
5175
  wrapper.setAttribute("data-page-index", String(i));
5176
+ wrapper.setAttribute("aria-label", formatPreviewMessage(messages.pdfPageLabel, { page: i + 1 }));
5037
5177
  wrapper.style.width = `${w}px`;
5038
5178
  wrapper.style.height = `${h}px`;
5039
- wrapper.append(createPageStatus("ofv-pdf-skeleton", `\u9875\u9762 ${i + 1} \u52A0\u8F7D\u4E2D...`));
5179
+ wrapper.append(createPageStatus("ofv-pdf-skeleton", formatPreviewMessage(messages.pdfPageLoading, { page: i + 1 })));
5040
5180
  scroller.appendChild(wrapper);
5041
5181
  pageStates.push({
5042
5182
  wrapper,
@@ -5058,6 +5198,7 @@ async function renderPdfDocumentPreview(options) {
5058
5198
  }
5059
5199
  }, 0);
5060
5200
  }
5201
+ goToPage(currentPage, false);
5061
5202
  };
5062
5203
  renderLayout(options.size);
5063
5204
  let resizeTimer;
@@ -5098,6 +5239,8 @@ async function renderPdfDocumentPreview(options) {
5098
5239
  },
5099
5240
  destroy() {
5100
5241
  options.toolbar?.setZoom(void 0);
5242
+ pageNavigator.destroy();
5243
+ scroller.removeEventListener("scroll", handleScrollerScroll);
5101
5244
  window.clearTimeout(resizeTimer);
5102
5245
  observer?.disconnect();
5103
5246
  pageStates.forEach((state) => {
@@ -5143,15 +5286,15 @@ function getPdfAvailableWidth(width) {
5143
5286
  const gutter = width < 160 ? 16 : 32;
5144
5287
  return Math.max(1, width - gutter);
5145
5288
  }
5146
- function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor) {
5289
+ function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor, messages) {
5147
5290
  summary.replaceChildren();
5148
- appendPdfSummary(summary, "\u9875\u6570", String(pages));
5291
+ appendPdfSummary(summary, messages.pdfSummaryPages, String(pages));
5149
5292
  const pageSizes = formatPdfPageSizes(pagesMeta);
5150
5293
  if (pageSizes) {
5151
- appendPdfSummary(summary, "\u9875\u9762\u5C3A\u5BF8", pageSizes);
5294
+ appendPdfSummary(summary, messages.pdfSummaryPageSizes, pageSizes);
5152
5295
  }
5153
- appendPdfSummary(summary, "\u9002\u914D", fit === "actual" ? "\u539F\u59CB\u5927\u5C0F" : "\u9002\u5408\u5BBD\u5EA6");
5154
- appendPdfSummary(summary, "\u7F29\u653E", `${Math.round(zoomFactor * 100)}%`);
5296
+ appendPdfSummary(summary, messages.pdfSummaryFit, fit === "actual" ? messages.pdfSummaryActualSize : messages.pdfSummaryFitWidth);
5297
+ appendPdfSummary(summary, messages.pdfSummaryZoom, `${Math.round(zoomFactor * 100)}%`);
5155
5298
  }
5156
5299
  function appendPdfSummary(parent, label, value) {
5157
5300
  const item = document.createElement("span");
@@ -5241,7 +5384,7 @@ function createPageStatus(className, text) {
5241
5384
  status.textContent = text;
5242
5385
  return status;
5243
5386
  }
5244
- function createPdfFallback(fileName, url, message, titleText = "PDF \u9884\u89C8\u5931\u8D25") {
5387
+ function createPdfFallback(fileName, url, message, messages, titleText = messages.pdfPreviewFailedTitle) {
5245
5388
  if (isEmbeddableRemoteUrl(url)) {
5246
5389
  return createPdfWebFallback(fileName, url);
5247
5390
  }
@@ -5254,7 +5397,7 @@ function createPdfFallback(fileName, url, message, titleText = "PDF \u9884\u89C8
5254
5397
  const download = document.createElement("a");
5255
5398
  download.href = url;
5256
5399
  download.download = fileName;
5257
- download.textContent = "\u4E0B\u8F7D PDF";
5400
+ download.textContent = messages.pdfDownload;
5258
5401
  fallback.append(title, meta, download);
5259
5402
  return fallback;
5260
5403
  }
@@ -5273,23 +5416,109 @@ function createPdfWebFallback(fileName, url) {
5273
5416
  function isEmbeddableRemoteUrl(url) {
5274
5417
  return /^https?:\/\//i.test(url);
5275
5418
  }
5276
- function normalizePdfError(error) {
5419
+ function normalizePdfError(error, messages) {
5277
5420
  const message = error instanceof Error ? error.message : String(error || "");
5278
5421
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
5279
5422
  const lower = `${name} ${message}`.toLowerCase();
5280
5423
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
5281
- return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
5424
+ return messages.pdfCorruptedMessage;
5282
5425
  }
5283
- return "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u52A0\u8F7D\u8BE5 PDF\u3002";
5426
+ return messages.pdfCannotLoadMessage;
5427
+ }
5428
+ function createPdfPageNavigator(total, messages, onChange) {
5429
+ const element = document.createElement("div");
5430
+ element.className = "ofv-pdf-page-navigator";
5431
+ const previous = document.createElement("button");
5432
+ previous.type = "button";
5433
+ previous.textContent = "\u2039";
5434
+ previous.title = messages.pdfPreviousPage;
5435
+ previous.setAttribute("aria-label", messages.pdfPreviousPage);
5436
+ const input = document.createElement("input");
5437
+ input.type = "number";
5438
+ input.min = "1";
5439
+ input.max = String(total);
5440
+ input.value = "1";
5441
+ input.inputMode = "numeric";
5442
+ input.setAttribute("aria-label", messages.pdfPageInput);
5443
+ const position = document.createElement("span");
5444
+ position.textContent = formatPreviewMessage(messages.pdfPagePosition, { total });
5445
+ const next = document.createElement("button");
5446
+ next.type = "button";
5447
+ next.textContent = "\u203A";
5448
+ next.title = messages.pdfNextPage;
5449
+ next.setAttribute("aria-label", messages.pdfNextPage);
5450
+ const commit = () => onChange(Number(input.value));
5451
+ const handleKeydown = (event) => {
5452
+ if (event.key === "Enter") {
5453
+ commit();
5454
+ }
5455
+ };
5456
+ const handlePrevious = () => onChange(Number(input.value) - 1);
5457
+ const handleNext = () => onChange(Number(input.value) + 1);
5458
+ input.addEventListener("change", commit);
5459
+ input.addEventListener("keydown", handleKeydown);
5460
+ previous.addEventListener("click", handlePrevious);
5461
+ next.addEventListener("click", handleNext);
5462
+ element.append(previous, input, position, next);
5463
+ return {
5464
+ element,
5465
+ setCurrent(page) {
5466
+ input.value = String(page);
5467
+ previous.disabled = page <= 1;
5468
+ next.disabled = page >= total;
5469
+ },
5470
+ destroy() {
5471
+ input.removeEventListener("change", commit);
5472
+ input.removeEventListener("keydown", handleKeydown);
5473
+ previous.removeEventListener("click", handlePrevious);
5474
+ next.removeEventListener("click", handleNext);
5475
+ }
5476
+ };
5284
5477
  }
5285
- function configurePdfWorker(pdf, workerSrc) {
5478
+ function configurePdfWorker(pdf, workerSrc, legacy = false) {
5286
5479
  if (workerSrc) {
5287
5480
  pdf.GlobalWorkerOptions.workerSrc = workerSrc;
5288
5481
  return;
5289
5482
  }
5290
5483
  if (!pdf.GlobalWorkerOptions.workerSrc && typeof window !== "undefined") {
5291
- pdf.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/build/pdf.worker.mjs`;
5484
+ const build = legacy ? "legacy/build" : "build";
5485
+ pdf.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/${build}/pdf.worker.mjs`;
5486
+ }
5487
+ }
5488
+ function shouldUseLegacyPdfCompatibility(mode = "auto") {
5489
+ if (mode === "legacy") {
5490
+ return true;
5491
+ }
5492
+ if (mode === "modern") {
5493
+ return false;
5494
+ }
5495
+ const promise = Promise;
5496
+ if (typeof promise.withResolvers !== "function") {
5497
+ return true;
5498
+ }
5499
+ if (typeof navigator === "undefined") {
5500
+ return false;
5292
5501
  }
5502
+ return /(?:QIHU|360SE|360EE)/i.test(navigator.userAgent);
5503
+ }
5504
+ function installPromiseWithResolversPolyfill() {
5505
+ const promise = Promise;
5506
+ if (typeof promise.withResolvers === "function") {
5507
+ return;
5508
+ }
5509
+ Object.defineProperty(promise, "withResolvers", {
5510
+ configurable: true,
5511
+ writable: true,
5512
+ value: () => {
5513
+ let resolve;
5514
+ let reject;
5515
+ const pending = new Promise((resolvePromise, rejectPromise) => {
5516
+ resolve = resolvePromise;
5517
+ reject = rejectPromise;
5518
+ });
5519
+ return { promise: pending, resolve, reject };
5520
+ }
5521
+ });
5293
5522
  }
5294
5523
 
5295
5524
  // src/plugins/epub.ts
@@ -7684,8 +7913,9 @@ async function renderConvertedOfficePreview(panel, ctx, options, conversionConte
7684
7913
  fit: ctx.options.fit,
7685
7914
  zoom: ctx.options.zoom,
7686
7915
  toolbar: ctx.toolbar,
7687
- title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
7688
- fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
7916
+ messages: ctx.options.messages,
7917
+ title: ctx.options.messages.officeConvertedTitle,
7918
+ fallbackTitle: ctx.options.messages.officeConvertedPdfFailed,
7689
7919
  revokeUrlOnDestroy: converted.revokeUrlOnDestroy
7690
7920
  });
7691
7921
  }
@@ -9467,6 +9697,9 @@ function renderChartCard(chart) {
9467
9697
  function renderChartSvg(chart) {
9468
9698
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9469
9699
  svg.setAttribute("viewBox", "0 0 640 380");
9700
+ svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
9701
+ svg.setAttribute("text-rendering", "geometricPrecision");
9702
+ svg.setAttribute("shape-rendering", "geometricPrecision");
9470
9703
  svg.setAttribute("role", "img");
9471
9704
  svg.setAttribute("aria-label", chart.title);
9472
9705
  svg.classList.add("ofv-chart-svg");
@@ -9512,16 +9745,7 @@ function renderChartSvg(chart) {
9512
9745
  const clusterWidth = groupWidth * 0.58;
9513
9746
  const barWidth = Math.max(5, Math.min(28, clusterWidth / Math.max(1, chart.series.length)));
9514
9747
  const zeroY = plot.y + plot.height - (0 - axisMin) / (axisMax - axisMin || 1) * plot.height;
9515
- categories.slice(0, categoryCount).forEach((category, index) => {
9516
- const x = plot.x + groupWidth * (index + 0.5);
9517
- const label = appendSvg(svg, "text", {
9518
- x: Number(x.toFixed(1)),
9519
- y: plot.y + plot.height + 22,
9520
- class: "ofv-chart-label",
9521
- "text-anchor": "middle"
9522
- });
9523
- label.textContent = truncateChartLabel(category);
9524
- });
9748
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + groupWidth * (index + 0.5));
9525
9749
  chart.series.forEach((series, seriesIndex) => {
9526
9750
  const color = series.color || colors[seriesIndex % colors.length];
9527
9751
  series.values.forEach((value, index) => {
@@ -9539,6 +9763,8 @@ function renderChartSvg(chart) {
9539
9763
  });
9540
9764
  });
9541
9765
  } else {
9766
+ const categoryStep = categories.length > 1 ? plot.width / (categories.length - 1) : plot.width;
9767
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + index * categoryStep);
9542
9768
  chart.series.forEach((series, seriesIndex) => {
9543
9769
  const color = series.color || colors[seriesIndex % colors.length];
9544
9770
  const step = series.values.length > 1 ? plot.width / (series.values.length - 1) : plot.width;
@@ -9563,6 +9789,22 @@ function renderChartSvg(chart) {
9563
9789
  appendChartLegend(svg, chart, colors, 348);
9564
9790
  return svg;
9565
9791
  }
9792
+ function appendChartCategoryLabels(svg, categories, plot, getX) {
9793
+ const interval = Math.max(1, Math.ceil(categories.length / 14));
9794
+ categories.forEach((category, index) => {
9795
+ if (index % interval !== 0 && index !== categories.length - 1) {
9796
+ return;
9797
+ }
9798
+ const label = appendSvg(svg, "text", {
9799
+ x: Number(getX(index).toFixed(1)),
9800
+ y: plot.y + plot.height + 22,
9801
+ class: "ofv-chart-label ofv-chart-category-label",
9802
+ "data-axis": "category",
9803
+ "text-anchor": "middle"
9804
+ });
9805
+ label.textContent = truncateChartLabel(category);
9806
+ });
9807
+ }
9566
9808
  function appendChartLegend(svg, chart, colors, y) {
9567
9809
  const itemWidth = 86;
9568
9810
  const startX = 320 - chart.series.length * itemWidth / 2;