@open-file-viewer/core 0.1.20 → 0.1.21

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
@@ -622,7 +622,40 @@ var defaultMessages = {
622
622
  size: "\u5927\u5C0F",
623
623
  source: "\u6765\u6E90",
624
624
  remoteUrl: "\u8FDC\u7A0B URL",
625
- localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6"
625
+ localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6",
626
+ textPlainLanguage: "\u7EAF\u6587\u672C",
627
+ textLineCount: "{count} \u884C",
628
+ textWrap: "\u6362\u884C",
629
+ textCopy: "\u590D\u5236",
630
+ textCopied: "\u5DF2\u590D\u5236",
631
+ textCopyFailed: "\u590D\u5236\u5931\u8D25",
632
+ textDownload: "\u4E0B\u8F7D",
633
+ textDownloadReady: "\u4E0B\u8F7D\u5DF2\u51C6\u5907",
634
+ textLargeFileNotice: "\u6587\u4EF6\u8F83\u5927\uFF0C\u5F53\u524D\u5C55\u793A\u524D {size}\uFF0C\u590D\u5236\u548C\u4E0B\u8F7D\u4ECD\u4F1A\u4F7F\u7528\u5B8C\u6574\u5185\u5BB9\u3002",
635
+ textHighlightSkipped: "\u5185\u5BB9\u8F83\u5927\uFF0C\u5DF2\u8DF3\u8FC7\u8BED\u6CD5\u9AD8\u4EAE\u4EE5\u4FDD\u6301\u6EDA\u52A8\u6D41\u7545\u3002",
636
+ textPreviewFailedTitle: "\u6587\u672C\u9884\u89C8\u5931\u8D25",
637
+ textPreviewFailedMessage: "\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u6587\u672C\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u8FDC\u7A0B\u6587\u4EF6\u4E0D\u53EF\u8BBF\u95EE\u6216\u54CD\u5E94\u72B6\u6001\u5F02\u5E38\u3002",
638
+ textOpenOriginal: "\u6253\u5F00\u539F\u6587\u4EF6",
639
+ officeLegacyConversionTitle: "Office \u8F6C\u6362\u63D0\u793A",
640
+ officeLegacyBinaryNotice: "\u5C5E\u4E8E\u65E7\u7248 Microsoft Office \u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u9AD8\u4FDD\u771F\u89E3\u6790\uFF1B\u5F53\u524D\u4EC5\u5C55\u793A\u53EF\u4FE1\u6587\u672C\u7247\u6BB5\u548C\u7ED3\u6784\u6307\u7EB9\uFF0C\u5B8C\u6574\u6392\u7248\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML\u3002",
641
+ officeLegacyMetaFormatType: "\u683C\u5F0F\u7C7B\u578B",
642
+ officeLegacyMetaFileStructure: "\u6587\u4EF6\u7ED3\u6784",
643
+ officeLegacyOleDetected: "\u68C0\u6D4B\u5230 OLE Compound File \u7B7E\u540D",
644
+ officeLegacyOleMissing: "\u672A\u68C0\u6D4B\u5230\u6807\u51C6 OLE \u7B7E\u540D\uFF0C\u6309\u539F\u59CB\u4E8C\u8FDB\u5236\u5C1D\u8BD5\u63D0\u53D6",
645
+ officeLegacyMetaTextFragments: "\u6587\u672C\u7247\u6BB5",
646
+ officeLegacyTextFragmentCount: "{count} \u6BB5",
647
+ officeLegacyMetaParseStatus: "\u89E3\u6790\u72B6\u6001",
648
+ officeLegacyReadableFragments: "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5",
649
+ officeLegacyNoText: "\u672A\u63D0\u53D6\u5230\u7A33\u5B9A\u53EF\u8BFB\u6587\u672C\u3002\u8BE5\u6587\u4EF6\u53EF\u80FD\u7ECF\u8FC7\u538B\u7F29\u3001\u52A0\u5BC6\uFF0C\u6216\u6587\u672C\u7F16\u7801\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u7AEF\u53EF\u9760\u8BC6\u522B\uFF1B\u8BF7\u4F7F\u7528\u670D\u52A1\u7AEF LibreOffice/OnlyOffice \u8F6C\u6362\u540E\u9884\u89C8\u3002",
650
+ officeLegacyWordParseFailed: "Word \u4E8C\u8FDB\u5236\u89E3\u6790\u5931\u8D25\uFF1A{message}",
651
+ officeSheetParseFailed: "\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A{message}",
652
+ officeUnsupportedTitle: "Office \u57FA\u7840\u9884\u89C8",
653
+ officeUnsupportedLegacyMessage: "\u8BE5\u683C\u5F0F\u5C5E\u4E8E\u8001\u4E8C\u8FDB\u5236\u6216\u4E13\u6709\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u53EF\u9760\u89E3\u6790\uFF1B\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML \u540E\u9884\u89C8\u3002",
654
+ officeUnsupportedGenericMessage: "\u8BE5\u683C\u5F0F\u901A\u5E38\u9700\u8981\u670D\u52A1\u7AEF\u8F6C\u6362\u6216\u4E13\u7528\u89E3\u6790\u5668\u624D\u80FD\u9AD8\u4FDD\u771F\u9884\u89C8\u3002",
655
+ officeUnsupportedIntro: "\u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002{message}",
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
+ officeErrorWithMessage: "\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A{message}",
658
+ officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002"
626
659
  },
627
660
  "en-US": {
628
661
  loading: "Loading preview...",
@@ -638,7 +671,40 @@ var defaultMessages = {
638
671
  size: "Size",
639
672
  source: "Source",
640
673
  remoteUrl: "Remote URL",
641
- localFile: "Local or in-memory file"
674
+ localFile: "Local or in-memory file",
675
+ textPlainLanguage: "plain text",
676
+ textLineCount: "{count} lines",
677
+ textWrap: "Wrap",
678
+ textCopy: "Copy",
679
+ textCopied: "Copied",
680
+ textCopyFailed: "Copy failed",
681
+ textDownload: "Download",
682
+ textDownloadReady: "Download ready",
683
+ textLargeFileNotice: "Large file, showing the first {size}. Copy and download still use the full content.",
684
+ textHighlightSkipped: "Large content, syntax highlighting was skipped to keep scrolling smooth.",
685
+ textPreviewFailedTitle: "Text preview failed",
686
+ textPreviewFailedMessage: "Unable to read this text content. The remote file may be unreachable or returned an invalid response.",
687
+ textOpenOriginal: "Open original file",
688
+ officeLegacyConversionTitle: "Office conversion guidance",
689
+ officeLegacyBinaryNotice: "belongs to a legacy Microsoft Office binary format that cannot be rendered with high fidelity in the browser. The preview shows trusted text fragments and structural fingerprints; use a LibreOffice/OnlyOffice server conversion to PDF/HTML for complete layout fidelity.",
690
+ officeLegacyMetaFormatType: "Format type",
691
+ officeLegacyMetaFileStructure: "File structure",
692
+ officeLegacyOleDetected: "OLE Compound File signature detected",
693
+ officeLegacyOleMissing: "No standard OLE signature detected; extracting from raw binary data",
694
+ officeLegacyMetaTextFragments: "Text fragments",
695
+ officeLegacyTextFragmentCount: "{count} fragments",
696
+ officeLegacyMetaParseStatus: "Parse status",
697
+ officeLegacyReadableFragments: "Readable text fragments",
698
+ officeLegacyNoText: "No stable readable text was extracted. The file may be compressed, encrypted, or use text encoding that cannot be reliably recognized in the browser; use LibreOffice/OnlyOffice server conversion before previewing.",
699
+ officeLegacyWordParseFailed: "Word binary parse failed: {message}",
700
+ officeSheetParseFailed: "Spreadsheet parse failed: {message}",
701
+ officeUnsupportedTitle: "Office basic preview",
702
+ officeUnsupportedLegacyMessage: "This is a legacy binary or proprietary format that cannot be reliably parsed in the browser; convert it to PDF/HTML through LibreOffice/OnlyOffice on the server before previewing.",
703
+ officeUnsupportedGenericMessage: "This format usually needs server-side conversion or a dedicated parser for high-fidelity preview.",
704
+ officeUnsupportedIntro: "is handled by the Office plugin. {message}",
705
+ officeUnsupportedSupportedFormats: "This version prioritizes basic previews for docx, rtf, odt/fodt, xlsx/xls/csv/ods, pptx/ppsx, and odp/fodp files.",
706
+ officeErrorWithMessage: "Parser returned: {message}",
707
+ officeErrorWithoutMessage: "Parser did not return a specific error."
642
708
  }
643
709
  };
644
710
  function resolveMessages(options) {
@@ -647,6 +713,9 @@ function resolveMessages(options) {
647
713
  ...options.messages
648
714
  };
649
715
  }
716
+ function formatPreviewMessage(template, values) {
717
+ return template.replace(/\{(\w+)\}/g, (match, key) => String(values[key] ?? match));
718
+ }
650
719
 
651
720
  // src/plugins/fallback.ts
652
721
  function fallbackPlugin() {
@@ -717,9 +786,10 @@ function formatFallbackBytes(bytes) {
717
786
  function createViewer(options) {
718
787
  const container = resolveContainer(options.container);
719
788
  applyBoxSize(container, options.width, options.height);
789
+ const customClassNames = parseClassNameTokens(options.className);
720
790
  container.classList.add("ofv-root");
721
- if (options.className) {
722
- container.classList.add(options.className);
791
+ if (customClassNames.length > 0) {
792
+ container.classList.add(...customClassNames);
723
793
  }
724
794
  const theme = applyTheme(container, options.theme || "light");
725
795
  const host = document.createElement("div");
@@ -863,12 +933,15 @@ function createViewer(options) {
863
933
  theme.destroy();
864
934
  container.replaceChildren();
865
935
  container.classList.remove("ofv-root");
866
- if (options.className) {
867
- container.classList.remove(options.className);
936
+ if (customClassNames.length > 0) {
937
+ container.classList.remove(...customClassNames);
868
938
  }
869
939
  }
870
940
  };
871
941
  }
942
+ function parseClassNameTokens(className) {
943
+ return className?.trim().split(/\s+/).filter(Boolean) ?? [];
944
+ }
872
945
  function destroyPreviewInstance(instance) {
873
946
  if (!instance) {
874
947
  return;
@@ -994,6 +1067,7 @@ function createToolbar(toolbar, viewport, queue) {
994
1067
  let previousButton;
995
1068
  let nextButton;
996
1069
  let zoomResetButton;
1070
+ let fullscreenButton;
997
1071
  let currentZoom;
998
1072
  const commandButtons = [];
999
1073
  const customButtons = [];
@@ -1099,13 +1173,14 @@ function createToolbar(toolbar, viewport, queue) {
1099
1173
  return;
1100
1174
  }
1101
1175
  if (id === "fullscreen" && options.fullscreen !== false) {
1102
- addButton(
1176
+ fullscreenButton = addButton(
1103
1177
  getToolbarLabel(options, id),
1104
1178
  getToolbarTitle(options, id),
1105
1179
  () => getContext().fullscreen(),
1106
1180
  void 0,
1107
1181
  options.icons?.fullscreen
1108
1182
  );
1183
+ updateFullscreenButton();
1109
1184
  return;
1110
1185
  }
1111
1186
  if (id === "print" && options.print) {
@@ -1207,6 +1282,32 @@ function createToolbar(toolbar, viewport, queue) {
1207
1282
  updateCustomButtons();
1208
1283
  refreshCustomRender();
1209
1284
  }
1285
+ function isFullscreenActive() {
1286
+ return Boolean(
1287
+ typeof document !== "undefined" && element.parentElement && document.fullscreenElement === element.parentElement
1288
+ );
1289
+ }
1290
+ function updateFullscreenButton() {
1291
+ if (!fullscreenButton) {
1292
+ return;
1293
+ }
1294
+ const active = isFullscreenActive();
1295
+ const id = active ? "exit-fullscreen" : "fullscreen";
1296
+ 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);
1299
+ fullscreenButton.title = title;
1300
+ fullscreenButton.setAttribute("aria-label", title);
1301
+ fullscreenButton.setAttribute("aria-pressed", String(active));
1302
+ }
1303
+ const handleFullscreenChange = () => {
1304
+ updateFullscreenButton();
1305
+ refreshCustomRender();
1306
+ };
1307
+ if (typeof document !== "undefined") {
1308
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
1309
+ disposers.push(() => document.removeEventListener("fullscreenchange", handleFullscreenChange));
1310
+ }
1210
1311
  const refreshCustomRender = () => {
1211
1312
  if (!options.render) {
1212
1313
  return;
@@ -1280,6 +1381,9 @@ function createToolbarContext({
1280
1381
  viewport,
1281
1382
  canPrevious: index > 0,
1282
1383
  canNext: index < length - 1,
1384
+ isFullscreen: Boolean(
1385
+ typeof document !== "undefined" && element.parentElement && document.fullscreenElement === element.parentElement
1386
+ ),
1283
1387
  zoom,
1284
1388
  zoomLabel: zoom === void 0 ? void 0 : formatToolbarZoom(zoom),
1285
1389
  async previous() {
@@ -1298,7 +1402,15 @@ function createToolbarContext({
1298
1402
  }
1299
1403
  },
1300
1404
  fullscreen() {
1301
- void element.parentElement?.requestFullscreen?.();
1405
+ const target = element.parentElement;
1406
+ if (!target) {
1407
+ return;
1408
+ }
1409
+ if (typeof document !== "undefined" && document.fullscreenElement === target) {
1410
+ void document.exitFullscreen?.();
1411
+ } else {
1412
+ void target.requestFullscreen?.();
1413
+ }
1302
1414
  },
1303
1415
  print() {
1304
1416
  printPreview(viewport);
@@ -1317,6 +1429,7 @@ var defaultToolbarLabels = {
1317
1429
  "rotate-right": "Rotate",
1318
1430
  download: "Download",
1319
1431
  fullscreen: "Fullscreen",
1432
+ "exit-fullscreen": "Exit fullscreen",
1320
1433
  print: "Print",
1321
1434
  search: "Search"
1322
1435
  };
@@ -1330,6 +1443,7 @@ var defaultToolbarTitles = {
1330
1443
  "rotate-right": "Rotate right",
1331
1444
  download: "Download file",
1332
1445
  fullscreen: "Open preview fullscreen",
1446
+ "exit-fullscreen": "Exit fullscreen",
1333
1447
  print: "Print preview",
1334
1448
  search: "Search preview text"
1335
1449
  };
@@ -1568,6 +1682,9 @@ function collectSearchableTextNodes(root) {
1568
1682
  if (["SCRIPT", "STYLE", "TEXTAREA", "INPUT", "BUTTON"].includes(parent.tagName)) {
1569
1683
  return NodeFilter.FILTER_REJECT;
1570
1684
  }
1685
+ if (hasHiddenSearchAncestor(parent, root)) {
1686
+ return NodeFilter.FILTER_REJECT;
1687
+ }
1571
1688
  return NodeFilter.FILTER_ACCEPT;
1572
1689
  }
1573
1690
  });
@@ -1578,6 +1695,19 @@ function collectSearchableTextNodes(root) {
1578
1695
  }
1579
1696
  return nodes;
1580
1697
  }
1698
+ function hasHiddenSearchAncestor(element, root) {
1699
+ let current = element;
1700
+ while (current) {
1701
+ if (current.hidden || current.getAttribute("aria-hidden") === "true" || current.style.display === "none" || current.style.visibility === "hidden") {
1702
+ return true;
1703
+ }
1704
+ if (current === root) {
1705
+ break;
1706
+ }
1707
+ current = current.parentElement;
1708
+ }
1709
+ return false;
1710
+ }
1581
1711
  function printPreview(viewport) {
1582
1712
  const frame = document.createElement("iframe");
1583
1713
  frame.className = "ofv-print-frame";
@@ -4075,7 +4205,7 @@ function textPlugin() {
4075
4205
  const isMarkdown = lang === "markdown";
4076
4206
  const text = await readText(ctx.file.source).catch((error) => void 0);
4077
4207
  if (text === void 0) {
4078
- const fallback = createTextFallback(ctx.file.name, ctx.file.url);
4208
+ const fallback = createTextFallback(ctx.file.name, ctx.options.messages, ctx.file.url);
4079
4209
  ctx.viewport.classList.add("ofv-center");
4080
4210
  ctx.viewport.append(fallback);
4081
4211
  return {
@@ -4247,10 +4377,11 @@ function textPlugin() {
4247
4377
  title.className = "ofv-code-title";
4248
4378
  const fileName = document.createElement("strong");
4249
4379
  fileName.textContent = ctx.file.name;
4380
+ const messages = ctx.options.messages;
4250
4381
  const meta = document.createElement("span");
4251
4382
  meta.textContent = [
4252
- lang === "none" ? "plain text" : lang,
4253
- `${totalLines.toLocaleString()} lines`,
4383
+ lang === "none" ? messages.textPlainLanguage : lang,
4384
+ formatPreviewMessage(messages.textLineCount, { count: totalLines.toLocaleString() }),
4254
4385
  formatBytes(ctx.file.size ?? (ctx.file.source instanceof Blob ? ctx.file.source.size : text.length))
4255
4386
  ].join(" \xB7 ");
4256
4387
  title.append(fileName, meta);
@@ -4262,7 +4393,7 @@ function textPlugin() {
4262
4393
  const wrapButton = document.createElement("button");
4263
4394
  wrapButton.type = "button";
4264
4395
  wrapButton.className = "ofv-code-action";
4265
- wrapButton.textContent = "Wrap";
4396
+ wrapButton.textContent = messages.textWrap;
4266
4397
  wrapButton.setAttribute("aria-pressed", String(defaultWrapped));
4267
4398
  wrapButton.addEventListener("click", () => {
4268
4399
  const wrapped = wrapper.classList.toggle("is-wrapped");
@@ -4271,14 +4402,14 @@ function textPlugin() {
4271
4402
  const copyButton = document.createElement("button");
4272
4403
  copyButton.type = "button";
4273
4404
  copyButton.className = "ofv-code-action";
4274
- copyButton.textContent = "Copy";
4405
+ copyButton.textContent = messages.textCopy;
4275
4406
  copyButton.addEventListener("click", async () => {
4276
4407
  copyButton.disabled = true;
4277
4408
  try {
4278
4409
  await copyToClipboard(text);
4279
- status.textContent = "Copied";
4410
+ status.textContent = messages.textCopied;
4280
4411
  } catch {
4281
- status.textContent = "Copy failed";
4412
+ status.textContent = messages.textCopyFailed;
4282
4413
  } finally {
4283
4414
  copyButton.disabled = false;
4284
4415
  }
@@ -4286,10 +4417,10 @@ function textPlugin() {
4286
4417
  const downloadButton = document.createElement("button");
4287
4418
  downloadButton.type = "button";
4288
4419
  downloadButton.className = "ofv-code-action";
4289
- downloadButton.textContent = "Download";
4420
+ downloadButton.textContent = messages.textDownload;
4290
4421
  downloadButton.addEventListener("click", () => {
4291
4422
  downloadText(ctx.file.name, text);
4292
- status.textContent = "Download ready";
4423
+ status.textContent = messages.textDownloadReady;
4293
4424
  });
4294
4425
  actions.append(wrapButton, copyButton, downloadButton, status);
4295
4426
  header.append(title, actions);
@@ -4314,13 +4445,13 @@ function textPlugin() {
4314
4445
  if (truncated) {
4315
4446
  const notice = document.createElement("div");
4316
4447
  notice.className = "ofv-code-notice";
4317
- notice.textContent = `\u6587\u4EF6\u8F83\u5927\uFF0C\u5F53\u524D\u5C55\u793A\u524D ${formatBytes(codeText.length)}\uFF0C\u590D\u5236\u548C\u4E0B\u8F7D\u4ECD\u4F1A\u4F7F\u7528\u5B8C\u6574\u5185\u5BB9\u3002`;
4448
+ notice.textContent = formatPreviewMessage(messages.textLargeFileNotice, { size: formatBytes(codeText.length) });
4318
4449
  wrapper.append(notice);
4319
4450
  }
4320
4451
  if (!shouldHighlight) {
4321
4452
  const notice = document.createElement("div");
4322
4453
  notice.className = "ofv-code-notice";
4323
- notice.textContent = "\u5185\u5BB9\u8F83\u5927\uFF0C\u5DF2\u8DF3\u8FC7\u8BED\u6CD5\u9AD8\u4EAE\u4EE5\u4FDD\u6301\u6EDA\u52A8\u6D41\u7545\u3002";
4454
+ notice.textContent = messages.textHighlightSkipped;
4324
4455
  wrapper.append(notice);
4325
4456
  }
4326
4457
  wrapper.appendChild(body);
@@ -4387,19 +4518,19 @@ function normalizeFileName2(name) {
4387
4518
  const baseName = name.split(/[\\/]/).pop() || name;
4388
4519
  return baseName.toLowerCase();
4389
4520
  }
4390
- function createTextFallback(fileName, url) {
4521
+ function createTextFallback(fileName, messages, url) {
4391
4522
  const fallback = document.createElement("div");
4392
4523
  fallback.className = "ofv-fallback";
4393
4524
  const title = document.createElement("strong");
4394
- title.textContent = "\u6587\u672C\u9884\u89C8\u5931\u8D25";
4525
+ title.textContent = messages.textPreviewFailedTitle;
4395
4526
  const meta = document.createElement("span");
4396
- meta.textContent = "\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u6587\u672C\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u8FDC\u7A0B\u6587\u4EF6\u4E0D\u53EF\u8BBF\u95EE\u6216\u54CD\u5E94\u72B6\u6001\u5F02\u5E38\u3002";
4527
+ meta.textContent = messages.textPreviewFailedMessage;
4397
4528
  fallback.append(title, meta);
4398
4529
  if (url) {
4399
4530
  const download = document.createElement("a");
4400
4531
  download.href = url;
4401
4532
  download.download = fileName;
4402
- download.textContent = "\u6253\u5F00\u539F\u6587\u4EF6";
4533
+ download.textContent = messages.textOpenOriginal;
4403
4534
  fallback.append(download);
4404
4535
  }
4405
4536
  return fallback;
@@ -7461,7 +7592,7 @@ function officePlugin(options = {}) {
7461
7592
  } else if (packageFormat === "docx" && !fileIsDocx(extension)) {
7462
7593
  disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
7463
7594
  } else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
7464
- await renderSheet(panel, arrayBuffer, "xlsx");
7595
+ await renderSheet(panel, arrayBuffer, "xlsx", ctx.options.messages);
7465
7596
  } else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
7466
7597
  await renderPptx(panel, arrayBuffer);
7467
7598
  } else if (fileIsDocx(extension)) {
@@ -7474,9 +7605,9 @@ function officePlugin(options = {}) {
7474
7605
  renderOpenDocumentXml(panel, "FODT \u6587\u6863", await readTextFromBuffer(arrayBuffer));
7475
7606
  } else if (extension === "fods") {
7476
7607
  renderFlatOds(panel, await readTextFromBuffer(arrayBuffer));
7477
- } else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit)) {
7608
+ } else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit, ctx.options.messages)) {
7478
7609
  } else if (sheetExtensions.has(extension)) {
7479
- await renderSheet(panel, arrayBuffer, extension);
7610
+ await renderSheet(panel, arrayBuffer, extension, ctx.options.messages);
7480
7611
  } else if (["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
7481
7612
  await renderPptx(panel, arrayBuffer);
7482
7613
  } else if (extension === "odp") {
@@ -7484,11 +7615,11 @@ function officePlugin(options = {}) {
7484
7615
  } else if (extension === "fodp") {
7485
7616
  renderOpenDocumentPresentationXml(panel, await readTextFromBuffer(arrayBuffer));
7486
7617
  } else if (extension === "doc" || extension === "dot") {
7487
- renderLegacyWordBinary(panel, extension, arrayBuffer);
7618
+ renderLegacyWordBinary(panel, extension, arrayBuffer, ctx.options.messages);
7488
7619
  } else if (isLegacyOfficeBinary(extension)) {
7489
- renderLegacyOfficeBinary(panel, extension, arrayBuffer);
7620
+ renderLegacyOfficeBinary(panel, extension, arrayBuffer, ctx.options.messages);
7490
7621
  } else {
7491
- renderUnsupportedOffice(panel, extension || ctx.file.extension || "office");
7622
+ renderUnsupportedOffice(panel, extension || ctx.file.extension || "office", ctx.options.messages);
7492
7623
  }
7493
7624
  const controller = createOfficeZoomController(panel, ctx);
7494
7625
  ctx.toolbar?.refreshCommandSupport();
@@ -8833,7 +8964,7 @@ function renderPlainDocument(panel, title, text) {
8833
8964
  section.append(pre);
8834
8965
  panel.append(section);
8835
8966
  }
8836
- async function renderSheet(panel, arrayBuffer, extension) {
8967
+ async function renderSheet(panel, arrayBuffer, extension, messages) {
8837
8968
  const xlsx = await import("xlsx");
8838
8969
  let workbook;
8839
8970
  try {
@@ -8846,10 +8977,16 @@ async function renderSheet(panel, arrayBuffer, extension) {
8846
8977
  }) : xlsx.read(arrayBuffer, { type: "array", cellDates: true, cellNF: true, cellStyles: true });
8847
8978
  } catch (error) {
8848
8979
  if (isLegacyOfficeBinary(extension)) {
8849
- renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
8980
+ renderLegacyOfficeBinary(
8981
+ panel,
8982
+ extension,
8983
+ arrayBuffer,
8984
+ messages,
8985
+ formatPreviewMessage(messages.officeSheetParseFailed, { message: normalizeOfficeError(error, messages) })
8986
+ );
8850
8987
  return;
8851
8988
  }
8852
- renderSheetFallback(panel, extension, normalizeOfficeError(error));
8989
+ renderSheetFallback(panel, extension, normalizeOfficeError(error, messages));
8853
8990
  return;
8854
8991
  }
8855
8992
  const chartPreviews = await readWorkbookCharts(arrayBuffer).catch(() => []);
@@ -10110,10 +10247,15 @@ function pptxRenderTimeoutMs() {
10110
10247
  function normalizePptxLayout(container) {
10111
10248
  const slideCanvases = findPptxSlideCanvases(container);
10112
10249
  for (const slide of slideCanvases) {
10113
- slide.style.backgroundColor = "#FFFFFF";
10250
+ if (!hasInlineBackground(slide)) {
10251
+ slide.style.backgroundColor = "#FFFFFF";
10252
+ }
10114
10253
  }
10115
10254
  normalizePptxMirroredText(container);
10116
10255
  }
10256
+ function hasInlineBackground(element) {
10257
+ return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
10258
+ }
10117
10259
  function normalizePptxMirroredText(container) {
10118
10260
  const mirroredContainers = Array.from(container.querySelectorAll("div")).filter((element) => {
10119
10261
  const text = element.textContent?.trim();
@@ -10205,7 +10347,7 @@ function renderOpenDocumentPresentationXml(panel, xml) {
10205
10347
  renderPresentationInsight(panel, inspectOpenDocumentPresentation("FODP \u6F14\u793A\u6587\u7A3F", xml, 0));
10206
10348
  renderOpenDocumentPresentation(panel, "FODP \u6F14\u793A\u6587\u7A3F", xml, []);
10207
10349
  }
10208
- async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
10350
+ async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit, messages) {
10209
10351
  let zip;
10210
10352
  try {
10211
10353
  zip = await import_jszip3.default.loadAsync(arrayBuffer);
@@ -10220,7 +10362,7 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
10220
10362
  return true;
10221
10363
  }
10222
10364
  if (hasEntry("xl/workbook.xml")) {
10223
- await renderSheet(panel, arrayBuffer, extension);
10365
+ await renderSheet(panel, arrayBuffer, extension, messages);
10224
10366
  return true;
10225
10367
  }
10226
10368
  if (hasEntry("ppt/presentation.xml")) {
@@ -10651,41 +10793,50 @@ function legacyOfficeFormatLabel(extension) {
10651
10793
  }
10652
10794
  return "PowerPoint Binary File Format";
10653
10795
  }
10654
- function renderLegacyWordBinary(panel, extension, arrayBuffer) {
10796
+ function renderLegacyWordBinary(panel, extension, arrayBuffer, messages) {
10655
10797
  try {
10656
10798
  renderLegacyWordDocument(panel, parseLegacyWordDocument(arrayBuffer));
10657
10799
  } catch (error) {
10658
- renderLegacyOfficeBinary(panel, extension, arrayBuffer, `Word \u4E8C\u8FDB\u5236\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
10800
+ renderLegacyOfficeBinary(
10801
+ panel,
10802
+ extension,
10803
+ arrayBuffer,
10804
+ messages,
10805
+ formatPreviewMessage(messages.officeLegacyWordParseFailed, { message: normalizeOfficeError(error, messages) })
10806
+ );
10659
10807
  }
10660
10808
  }
10661
- function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
10809
+ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, messages, parseError) {
10662
10810
  const fragments = extractLegacyOfficeText(arrayBuffer);
10663
10811
  panel.replaceChildren();
10664
- const section = createSection("Office \u8F6C\u6362\u63D0\u793A");
10812
+ const section = createSection(messages.officeLegacyConversionTitle);
10665
10813
  section.classList.add("ofv-office-conversion");
10666
10814
  const format = document.createElement("p");
10667
10815
  const strong = document.createElement("strong");
10668
10816
  strong.textContent = `.${extension}`;
10669
- format.append(
10670
- strong,
10671
- document.createTextNode(
10672
- " \u5C5E\u4E8E\u65E7\u7248 Microsoft Office \u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u9AD8\u4FDD\u771F\u89E3\u6790\uFF1B\u5F53\u524D\u4EC5\u5C55\u793A\u53EF\u4FE1\u6587\u672C\u7247\u6BB5\u548C\u7ED3\u6784\u6307\u7EB9\uFF0C\u5B8C\u6574\u6392\u7248\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML\u3002"
10673
- )
10674
- );
10817
+ format.append(strong, document.createTextNode(" "), document.createTextNode(messages.officeLegacyBinaryNotice));
10675
10818
  const meta = document.createElement("dl");
10676
10819
  meta.className = "ofv-office-binary-meta";
10677
- appendOfficeBinaryMeta(meta, "\u683C\u5F0F\u7C7B\u578B", legacyOfficeFormatLabel(extension));
10678
- appendOfficeBinaryMeta(meta, "\u6587\u4EF6\u7ED3\u6784", hasOleSignature(arrayBuffer) ? "\u68C0\u6D4B\u5230 OLE Compound File \u7B7E\u540D" : "\u672A\u68C0\u6D4B\u5230\u6807\u51C6 OLE \u7B7E\u540D\uFF0C\u6309\u539F\u59CB\u4E8C\u8FDB\u5236\u5C1D\u8BD5\u63D0\u53D6");
10679
- appendOfficeBinaryMeta(meta, "\u6587\u672C\u7247\u6BB5", `${fragments.length} \u6BB5`);
10820
+ appendOfficeBinaryMeta(meta, messages.officeLegacyMetaFormatType, legacyOfficeFormatLabel(extension));
10821
+ appendOfficeBinaryMeta(
10822
+ meta,
10823
+ messages.officeLegacyMetaFileStructure,
10824
+ hasOleSignature(arrayBuffer) ? messages.officeLegacyOleDetected : messages.officeLegacyOleMissing
10825
+ );
10826
+ appendOfficeBinaryMeta(
10827
+ meta,
10828
+ messages.officeLegacyMetaTextFragments,
10829
+ formatPreviewMessage(messages.officeLegacyTextFragmentCount, { count: fragments.length.toLocaleString() })
10830
+ );
10680
10831
  if (parseError) {
10681
- appendOfficeBinaryMeta(meta, "\u89E3\u6790\u72B6\u6001", parseError);
10832
+ appendOfficeBinaryMeta(meta, messages.officeLegacyMetaParseStatus, parseError);
10682
10833
  }
10683
10834
  section.append(format, meta);
10684
10835
  if (fragments.length > 0) {
10685
10836
  const article = document.createElement("article");
10686
10837
  article.className = "ofv-document ofv-office-binary-fragments";
10687
10838
  const heading = document.createElement("h4");
10688
- heading.textContent = "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5";
10839
+ heading.textContent = messages.officeLegacyReadableFragments;
10689
10840
  article.append(heading);
10690
10841
  for (const fragment of fragments.slice(0, 80)) {
10691
10842
  const paragraph = document.createElement("p");
@@ -10696,7 +10847,7 @@ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
10696
10847
  } else {
10697
10848
  const empty = document.createElement("p");
10698
10849
  empty.className = "ofv-office-binary-empty";
10699
- empty.textContent = "\u672A\u63D0\u53D6\u5230\u7A33\u5B9A\u53EF\u8BFB\u6587\u672C\u3002\u8BE5\u6587\u4EF6\u53EF\u80FD\u7ECF\u8FC7\u538B\u7F29\u3001\u52A0\u5BC6\uFF0C\u6216\u6587\u672C\u7F16\u7801\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u7AEF\u53EF\u9760\u8BC6\u522B\uFF1B\u8BF7\u4F7F\u7528\u670D\u52A1\u7AEF LibreOffice/OnlyOffice \u8F6C\u6362\u540E\u9884\u89C8\u3002";
10850
+ empty.textContent = messages.officeLegacyNoText;
10700
10851
  section.append(empty);
10701
10852
  }
10702
10853
  panel.append(section);
@@ -10708,23 +10859,27 @@ function appendOfficeBinaryMeta(list, label, value) {
10708
10859
  detail.textContent = value;
10709
10860
  list.append(term, detail);
10710
10861
  }
10711
- function renderUnsupportedOffice(panel, extension) {
10862
+ function renderUnsupportedOffice(panel, extension, messages) {
10712
10863
  const legacyBinary = /* @__PURE__ */ new Set(["doc", "dot", "wps", "ppt", "pps", "key", "dps"]);
10713
- const message = legacyBinary.has(extension) ? "\u8BE5\u683C\u5F0F\u5C5E\u4E8E\u8001\u4E8C\u8FDB\u5236\u6216\u4E13\u6709\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u53EF\u9760\u89E3\u6790\uFF1B\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML \u540E\u9884\u89C8\u3002" : "\u8BE5\u683C\u5F0F\u901A\u5E38\u9700\u8981\u670D\u52A1\u7AEF\u8F6C\u6362\u6216\u4E13\u7528\u89E3\u6790\u5668\u624D\u80FD\u9AD8\u4FDD\u771F\u9884\u89C8\u3002";
10864
+ const message = legacyBinary.has(extension) ? messages.officeUnsupportedLegacyMessage : messages.officeUnsupportedGenericMessage;
10714
10865
  panel.replaceChildren();
10715
- const section = createSection("Office \u57FA\u7840\u9884\u89C8");
10866
+ const section = createSection(messages.officeUnsupportedTitle);
10716
10867
  const format = document.createElement("p");
10717
10868
  const strong = document.createElement("strong");
10718
10869
  strong.textContent = `.${extension}`;
10719
- format.append(strong, document.createTextNode(` \u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002${message}`));
10870
+ format.append(
10871
+ strong,
10872
+ document.createTextNode(" "),
10873
+ document.createTextNode(formatPreviewMessage(messages.officeUnsupportedIntro, { message }))
10874
+ );
10720
10875
  const support = document.createElement("p");
10721
- support.textContent = "\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";
10876
+ support.textContent = messages.officeUnsupportedSupportedFormats;
10722
10877
  section.append(format, support);
10723
10878
  panel.append(section);
10724
10879
  }
10725
- function normalizeOfficeError(error) {
10880
+ function normalizeOfficeError(error, messages) {
10726
10881
  const message = error instanceof Error ? error.message : String(error || "");
10727
- return message ? `\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A${message}` : "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002";
10882
+ return message ? formatPreviewMessage(messages.officeErrorWithMessage, { message }) : messages.officeErrorWithoutMessage;
10728
10883
  }
10729
10884
  function extractLegacyOfficeText(arrayBuffer) {
10730
10885
  const bytes = new Uint8Array(arrayBuffer);
@@ -11966,11 +12121,13 @@ function archivePlugin() {
11966
12121
  sidebarToggle.addEventListener("click", () => {
11967
12122
  setSidebarCollapsed(!layout.classList.contains("is-sidebar-collapsed"));
11968
12123
  });
11969
- const showDefaultSummary = () => {
12124
+ const showDefaultSummary = (isSupplemental = true) => {
11970
12125
  mainPanel.replaceChildren();
11971
12126
  const summary = document.createElement("div");
11972
12127
  summary.className = "ofv-archive-info";
11973
- hideSupplementalInfo4(summary);
12128
+ if (isSupplemental) {
12129
+ hideSupplementalInfo4(summary);
12130
+ }
11974
12131
  const heading = document.createElement("h3");
11975
12132
  heading.textContent = ctx.file.name;
11976
12133
  const info = document.createElement("div");
@@ -11980,12 +12137,16 @@ function archivePlugin() {
11980
12137
  appendArchiveInfo(info, "\u683C\u5F0F\u7C7B\u578B", `.${ext.toUpperCase()} \u538B\u7F29\u6587\u4EF6`);
11981
12138
  appendArchiveInfo(info, "\u5305\u542B\u6587\u4EF6\u6570", `${fileCount} \u4E2A`);
11982
12139
  appendArchiveInfo(info, "\u5305\u542B\u76EE\u5F55\u6570", `${dirCount} \u4E2A`);
11983
- appendArchiveInfo(info, "\u64CD\u4F5C\u63D0\u793A", "\u8BF7\u70B9\u51FB\u5DE6\u4FA7\u680F\u4E2D\u7684\u6587\u4EF6\u8FDB\u884C\u8054\u52A8\u9884\u89C8\u3002");
12140
+ appendArchiveInfo(
12141
+ info,
12142
+ "\u64CD\u4F5C\u63D0\u793A",
12143
+ fileCount === 0 ? "\u538B\u7F29\u5305\u5185\u6CA1\u6709\u53EF\u9884\u89C8\u7684\u6587\u4EF6\u3002" : "\u8BF7\u70B9\u51FB\u5DE6\u4FA7\u680F\u4E2D\u7684\u6587\u4EF6\u8FDB\u884C\u8054\u52A8\u9884\u89C8\u3002"
12144
+ );
11984
12145
  summary.append(heading, info, createArchiveSummary(archiveEntries));
11985
12146
  mainPanel.append(summary);
11986
12147
  };
11987
- showDefaultSummary();
11988
12148
  const visibleEntries = archiveEntries.filter((e) => !e.dir).slice(0, 500);
12149
+ showDefaultSummary(visibleEntries.length > 0);
11989
12150
  let destroyed = false;
11990
12151
  let renderToken = 0;
11991
12152
  const openArchiveEntry = async (entry, item) => {