@files-preview-app/preview-file 1.3.2 → 1.3.4

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.js CHANGED
@@ -3559,26 +3559,6 @@ var ExcelPlugin = class {
3559
3559
  instance.zoomIn?.();
3560
3560
  }
3561
3561
  },
3562
- {
3563
- id: "fit-page",
3564
- icon: "fit-page",
3565
- label: "Fit to View",
3566
- type: "button",
3567
- group: "zoom",
3568
- execute: () => {
3569
- instance.fitToPage?.();
3570
- }
3571
- },
3572
- {
3573
- id: "rotate-cw",
3574
- icon: "rotate-cw",
3575
- label: "Rotate",
3576
- type: "button",
3577
- group: "view",
3578
- execute: () => {
3579
- instance.rotateCW?.();
3580
- }
3581
- },
3582
3562
  {
3583
3563
  id: "download",
3584
3564
  icon: "download",
@@ -3639,12 +3619,11 @@ var ExcelPlugin = class {
3639
3619
  container.appendChild(tabsArea);
3640
3620
  ctx.container.appendChild(container);
3641
3621
  let scale = 1;
3642
- let rotation = 0;
3643
3622
  let currentSheetIndex = 1;
3644
3623
  let sheetNames = [];
3645
3624
  let wb = null;
3646
3625
  const applyTransform = () => {
3647
- contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3626
+ contentArea.style.transform = `scale(${scale})`;
3648
3627
  contentArea.style.transformOrigin = "top left";
3649
3628
  };
3650
3629
  const calculateFitScale = () => {
@@ -3652,7 +3631,7 @@ var ExcelPlugin = class {
3652
3631
  if (!table) return 1;
3653
3632
  const availW = Math.max(280, container.clientWidth - 48);
3654
3633
  const tW = table.offsetWidth || 800;
3655
- return Math.max(0.65, Math.min(1, availW / tW));
3634
+ return Math.max(0.4, Math.min(1, availW / tW));
3656
3635
  };
3657
3636
  try {
3658
3637
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
@@ -3707,7 +3686,16 @@ var ExcelPlugin = class {
3707
3686
  }
3708
3687
  });
3709
3688
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3689
+ requestAnimationFrame(() => {
3690
+ scale = calculateFitScale();
3691
+ applyTransform();
3692
+ });
3710
3693
  };
3694
+ const ro = new ResizeObserver(() => {
3695
+ scale = calculateFitScale();
3696
+ applyTransform();
3697
+ });
3698
+ ro.observe(container);
3711
3699
  if (sheetNames.length > 0) {
3712
3700
  sheetNames.forEach((name, idx) => {
3713
3701
  const btn = document.createElement("button");
@@ -3734,6 +3722,7 @@ var ExcelPlugin = class {
3734
3722
  `;
3735
3723
  }
3736
3724
  const cleanup = () => {
3725
+ ro.disconnect();
3737
3726
  container.remove();
3738
3727
  ctx.container.innerHTML = "";
3739
3728
  };
@@ -3755,16 +3744,11 @@ var ExcelPlugin = class {
3755
3744
  },
3756
3745
  fitToPage: () => {
3757
3746
  scale = calculateFitScale();
3758
- rotation = 0;
3759
3747
  applyTransform();
3760
3748
  },
3761
3749
  rotateCW: () => {
3762
- rotation = (rotation + 90) % 360;
3763
- applyTransform();
3764
3750
  },
3765
3751
  rotateCCW: () => {
3766
- rotation = (rotation - 90 + 360) % 360;
3767
- applyTransform();
3768
3752
  },
3769
3753
  goToPage: (page) => {
3770
3754
  if (page > 0 && page <= sheetNames.length) {
@@ -3862,38 +3846,15 @@ var CsvPlugin = class {
3862
3846
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3863
3847
  }
3864
3848
  getToolbarActions(instance) {
3865
- const totalPages = instance.getPageCount?.() ?? 1;
3866
3849
  const actions = [];
3867
3850
  actions.push({
3868
3851
  id: "thumbnails",
3869
3852
  icon: "thumbnails",
3870
- label: "Page Thumbnails",
3853
+ label: "Sheet Thumbnails",
3871
3854
  type: "button",
3872
3855
  group: "navigation",
3873
3856
  execute: () => instance.toggleThumbnails?.()
3874
3857
  });
3875
- if (totalPages > 1) {
3876
- actions.push({
3877
- id: "page-nav",
3878
- icon: "",
3879
- label: "Page Navigation",
3880
- type: "page-nav",
3881
- group: "navigation",
3882
- value: instance.getCurrentPage?.() ?? 1,
3883
- max: totalPages,
3884
- execute: (action, page) => {
3885
- const cur = instance.getCurrentPage?.() ?? 1;
3886
- const max = instance.getPageCount?.() ?? 1;
3887
- if (action === "prev") {
3888
- if (cur > 1) instance.goToPage?.(cur - 1);
3889
- } else if (action === "next") {
3890
- if (cur < max) instance.goToPage?.(cur + 1);
3891
- } else if (typeof page === "number") {
3892
- instance.goToPage?.(page);
3893
- }
3894
- }
3895
- });
3896
- }
3897
3858
  actions.push(
3898
3859
  {
3899
3860
  id: "zoom-out",
@@ -3915,16 +3876,6 @@ var CsvPlugin = class {
3915
3876
  instance.zoomIn?.();
3916
3877
  }
3917
3878
  },
3918
- {
3919
- id: "fit-page",
3920
- icon: "fit-page",
3921
- label: "Fit to View",
3922
- type: "button",
3923
- group: "zoom",
3924
- execute: () => {
3925
- instance.fitToPage?.();
3926
- }
3927
- },
3928
3879
  {
3929
3880
  id: "download",
3930
3881
  icon: "download",
@@ -3967,131 +3918,135 @@ var CsvPlugin = class {
3967
3918
  container.style.overflow = "auto";
3968
3919
  container.style.padding = "16px";
3969
3920
  container.style.boxSizing = "border-box";
3970
- container.style.transformOrigin = "top left";
3921
+ const tableWrapper = document.createElement("div");
3922
+ tableWrapper.style.transformOrigin = "top left";
3971
3923
  const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3972
3924
  const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3973
3925
  const headerLine = allLines[0] || "";
3974
3926
  const headerCells = headerLine.split(delimiter);
3975
3927
  const dataLines = allLines.slice(1);
3976
- const ROWS_PER_PAGE = 100;
3977
- const totalPages = Math.max(1, Math.ceil(dataLines.length / ROWS_PER_PAGE));
3978
- let currentPage = 1;
3979
3928
  const table = document.createElement("table");
3980
3929
  table.style.borderCollapse = "collapse";
3981
- table.style.width = "100%";
3930
+ table.style.minWidth = "100%";
3982
3931
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3983
3932
  table.style.fontSize = "13px";
3984
- const renderPage = (pageNum) => {
3985
- currentPage = Math.max(1, Math.min(totalPages, pageNum));
3986
- table.innerHTML = "";
3987
- const headerTr = document.createElement("tr");
3988
- headerCells.forEach((cell) => {
3989
- const th = document.createElement("th");
3990
- th.textContent = cell.trim();
3991
- th.style.border = "1px solid #d0d7de";
3992
- th.style.padding = "8px 12px";
3993
- th.style.backgroundColor = "#f6f8fa";
3994
- th.style.fontWeight = "600";
3995
- th.style.whiteSpace = "nowrap";
3996
- headerTr.appendChild(th);
3997
- });
3998
- table.appendChild(headerTr);
3999
- const start = (currentPage - 1) * ROWS_PER_PAGE;
4000
- const end = Math.min(dataLines.length, start + ROWS_PER_PAGE);
4001
- const pageRows = dataLines.slice(start, end);
4002
- pageRows.forEach((row, idx) => {
4003
- const tr = document.createElement("tr");
4004
- if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
4005
- const cells = row.split(delimiter);
4006
- cells.forEach((cell) => {
4007
- const td = document.createElement("td");
4008
- td.textContent = cell.trim();
4009
- td.style.border = "1px solid #d0d7de";
4010
- td.style.padding = "6px 12px";
4011
- td.style.whiteSpace = "nowrap";
4012
- tr.appendChild(td);
4013
- });
4014
- table.appendChild(tr);
3933
+ table.style.background = "#ffffff";
3934
+ table.style.border = "1px solid #d0d7de";
3935
+ const headerTr = document.createElement("tr");
3936
+ headerCells.forEach((cell) => {
3937
+ const th = document.createElement("th");
3938
+ th.textContent = cell.trim();
3939
+ th.style.border = "1px solid #d0d7de";
3940
+ th.style.padding = "8px 12px";
3941
+ th.style.backgroundColor = "#f6f8fa";
3942
+ th.style.fontWeight = "600";
3943
+ th.style.whiteSpace = "nowrap";
3944
+ headerTr.appendChild(th);
3945
+ });
3946
+ table.appendChild(headerTr);
3947
+ dataLines.forEach((row, idx) => {
3948
+ const tr = document.createElement("tr");
3949
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3950
+ const cells = row.split(delimiter);
3951
+ cells.forEach((cell) => {
3952
+ const td = document.createElement("td");
3953
+ td.textContent = cell.trim();
3954
+ td.style.border = "1px solid #d0d7de";
3955
+ td.style.padding = "6px 12px";
3956
+ td.style.whiteSpace = "nowrap";
3957
+ tr.appendChild(td);
4015
3958
  });
4016
- container.scrollTop = 0;
4017
- ctx.emit("page-change", { page: currentPage, total: totalPages });
4018
- };
4019
- renderPage(1);
4020
- container.appendChild(table);
3959
+ table.appendChild(tr);
3960
+ });
3961
+ tableWrapper.appendChild(table);
3962
+ container.appendChild(tableWrapper);
4021
3963
  ctx.container.appendChild(container);
4022
3964
  let scale = 1;
3965
+ const calculateFitScale = () => {
3966
+ const availW = Math.max(280, container.clientWidth - 48);
3967
+ const tW = table.offsetWidth || 800;
3968
+ return Math.max(0.4, Math.min(1, availW / tW));
3969
+ };
4023
3970
  const applyScale = () => {
4024
- container.style.transform = `scale(${scale})`;
4025
- container.style.transformOrigin = "top left";
3971
+ tableWrapper.style.transform = `scale(${scale})`;
4026
3972
  };
3973
+ requestAnimationFrame(() => {
3974
+ scale = calculateFitScale();
3975
+ applyScale();
3976
+ });
3977
+ const ro = new ResizeObserver(() => {
3978
+ scale = calculateFitScale();
3979
+ applyScale();
3980
+ });
3981
+ ro.observe(container);
4027
3982
  const cleanup = () => {
3983
+ ro.disconnect();
4028
3984
  container.remove();
4029
3985
  ctx.container.innerHTML = "";
4030
3986
  };
4031
3987
  ctx.signal.addEventListener("abort", cleanup);
4032
3988
  return {
4033
3989
  destroy: cleanup,
4034
- getPageCount: () => totalPages,
4035
- getCurrentPage: () => currentPage,
4036
- goToPage: (page) => renderPage(page),
3990
+ getPageCount: () => 1,
3991
+ getCurrentPage: () => 1,
3992
+ goToPage: () => {
3993
+ },
4037
3994
  getThumbnails: () => {
4038
- const thumbnails = [];
4039
- for (let i = 0; i < totalPages; i++) {
4040
- const pageIdx = i;
4041
- const startRow = pageIdx * ROWS_PER_PAGE + 1;
4042
- const endRow = Math.min(dataLines.length, (pageIdx + 1) * ROWS_PER_PAGE);
4043
- const label = totalPages === 1 ? "Table" : `Page ${pageIdx + 1} (${startRow}-${endRow})`;
4044
- thumbnails.push({
4045
- index: pageIdx,
4046
- label,
4047
- render: async (canvas) => {
4048
- canvas.width = 140;
4049
- canvas.height = 100;
4050
- const c = canvas.getContext("2d");
4051
- if (!c) return;
4052
- c.fillStyle = "#ffffff";
4053
- c.fillRect(0, 0, canvas.width, canvas.height);
4054
- c.fillStyle = "#2563eb";
4055
- c.fillRect(0, 0, canvas.width, 16);
4056
- c.fillStyle = "#ffffff";
4057
- c.font = "bold 8px sans-serif";
4058
- c.fillText(`\u{1F4C8} ${label.slice(0, 18)}`, 6, 11);
4059
- const startY = 17;
4060
- const rowH = 13;
4061
- const colW = 28;
4062
- const cols = Math.min(4, headerCells.length || 4);
4063
- c.fillStyle = "#f1f5f9";
4064
- c.fillRect(0, startY, canvas.width, rowH);
4065
- c.strokeStyle = "#cbd5e1";
4066
- c.lineWidth = 0.8;
4067
- c.strokeRect(0, startY, canvas.width, rowH);
4068
- c.fillStyle = "#64748b";
4069
- c.font = "bold 7px sans-serif";
4070
- for (let ci = 0; ci < cols; ci++) {
4071
- const hName = headerCells[ci] || `Col ${ci + 1}`;
4072
- c.fillText(hName.slice(0, 5), 8 + ci * colW, startY + 9);
4073
- }
4074
- const rowsToPreview = dataLines.slice(pageIdx * ROWS_PER_PAGE, pageIdx * ROWS_PER_PAGE + 5);
4075
- for (let ri = 0; ri < rowsToPreview.length; ri++) {
4076
- const y = startY + (ri + 1) * rowH;
4077
- if (y > canvas.height - 2) break;
4078
- c.strokeStyle = "#e2e8f0";
4079
- c.strokeRect(0, y, canvas.width, rowH);
4080
- const cVals = rowsToPreview[ri].split(delimiter);
4081
- c.fillStyle = "#334155";
4082
- c.font = "6px sans-serif";
4083
- for (let ci = 0; ci < cols; ci++) {
4084
- const val = cVals[ci] || "";
4085
- c.fillText(val.trim().slice(0, 6), 8 + ci * colW, y + 9);
3995
+ const sheetName = ctx.metadata.name?.replace(/\.[^/.]+$/, "") || "Sheet1";
3996
+ return [{
3997
+ index: 0,
3998
+ label: sheetName,
3999
+ render: async (canvas) => {
4000
+ canvas.width = 140;
4001
+ canvas.height = 100;
4002
+ const c = canvas.getContext("2d");
4003
+ if (!c) return;
4004
+ c.fillStyle = "#ffffff";
4005
+ c.fillRect(0, 0, canvas.width, canvas.height);
4006
+ c.fillStyle = "#107c41";
4007
+ c.fillRect(0, 0, canvas.width, 16);
4008
+ c.fillStyle = "#ffffff";
4009
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
4010
+ c.fillText(`\u{1F4CA} ${sheetName.slice(0, 16)}`, 6, 11);
4011
+ const startY = 17;
4012
+ const rowH = 13;
4013
+ const colW = 30;
4014
+ const cols = headerCells.slice(0, 4).map((h) => h.trim() || "Col");
4015
+ c.fillStyle = "#f1f5f9";
4016
+ c.fillRect(0, startY, canvas.width, rowH);
4017
+ c.strokeStyle = "#cbd5e1";
4018
+ c.lineWidth = 0.8;
4019
+ c.strokeRect(0, startY, canvas.width, rowH);
4020
+ c.fillStyle = "#64748b";
4021
+ c.font = "bold 7px sans-serif";
4022
+ for (let ci = 0; ci < cols.length; ci++) {
4023
+ c.fillText(cols[ci].slice(0, 5), 14 + ci * colW, startY + 9);
4024
+ }
4025
+ for (let ri = 0; ri < Math.min(5, dataLines.length); ri++) {
4026
+ const y = startY + (ri + 1) * rowH;
4027
+ if (y > canvas.height - 2) break;
4028
+ c.fillStyle = "#f8fafc";
4029
+ c.fillRect(0, y, 12, rowH);
4030
+ c.fillStyle = "#94a3b8";
4031
+ c.font = "6px sans-serif";
4032
+ c.fillText(String(ri + 1), 3, y + 9);
4033
+ c.strokeStyle = "#e2e8f0";
4034
+ c.strokeRect(0, y, canvas.width, rowH);
4035
+ const rowCells = dataLines[ri].split(delimiter);
4036
+ c.fillStyle = "#334155";
4037
+ c.font = "6px sans-serif";
4038
+ for (let ci = 0; ci < cols.length; ci++) {
4039
+ const val = (rowCells[ci] || "").trim();
4040
+ if (val) {
4041
+ c.fillText(val.slice(0, 6), 14 + ci * colW, y + 9);
4086
4042
  }
4087
4043
  }
4088
- c.strokeStyle = "#cbd5e1";
4089
- c.lineWidth = 1;
4090
- c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4091
4044
  }
4092
- });
4093
- }
4094
- return thumbnails;
4045
+ c.strokeStyle = "#cbd5e1";
4046
+ c.lineWidth = 1;
4047
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4048
+ }
4049
+ }];
4095
4050
  },
4096
4051
  zoomIn: () => {
4097
4052
  scale += 0.1;
@@ -4107,11 +4062,13 @@ var CsvPlugin = class {
4107
4062
  applyScale();
4108
4063
  },
4109
4064
  fitToPage: () => {
4110
- const availW = Math.max(280, (ctx.container.clientWidth || window.innerWidth) - 48);
4111
- const tW = table.offsetWidth || 600;
4112
- scale = Math.max(0.35, Math.min(1, availW / tW));
4065
+ scale = calculateFitScale();
4113
4066
  applyScale();
4114
4067
  },
4068
+ rotateCW: () => {
4069
+ },
4070
+ rotateCCW: () => {
4071
+ },
4115
4072
  download: () => {
4116
4073
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
4117
4074
  const url = URL.createObjectURL(blob);
@@ -4223,26 +4180,6 @@ var CodePlugin = class {
4223
4180
  instance.zoomIn?.();
4224
4181
  }
4225
4182
  },
4226
- {
4227
- id: "fit-page",
4228
- icon: "fit-page",
4229
- label: "Fit to View",
4230
- type: "button",
4231
- group: "zoom",
4232
- execute: () => {
4233
- instance.fitToPage?.();
4234
- }
4235
- },
4236
- {
4237
- id: "rotate-cw",
4238
- icon: "rotate-cw",
4239
- label: "Rotate",
4240
- type: "button",
4241
- group: "view",
4242
- execute: () => {
4243
- instance.rotateCW?.();
4244
- }
4245
- },
4246
4183
  {
4247
4184
  id: "copy",
4248
4185
  icon: "copy",
@@ -5386,6 +5323,166 @@ var ThreeDPlugin = class {
5386
5323
  function threeDPlugin() {
5387
5324
  return new ThreeDPlugin();
5388
5325
  }
5326
+ function cleanRtfText(raw) {
5327
+ return raw.replace(/\\bin\d+\s/g, "").replace(/{\\\*[^}]+}/g, "").replace(/\\u(-?\d+)\??/g, (_, code) => {
5328
+ let num = parseInt(code, 10);
5329
+ if (num < 0) num += 65536;
5330
+ return String.fromCharCode(num);
5331
+ }).replace(/\\'([0-9a-fA-F]{2})/g, (_, hex) => {
5332
+ return String.fromCharCode(parseInt(hex, 16));
5333
+ }).replace(/\\tab\b/g, " ").replace(/\\emdash\b/g, "\u2014").replace(/\\endash\b/g, "\u2013").replace(/\\bullet\b/g, "\u2022").replace(/\\lquote\b/g, "\u2018").replace(/\\rquote\b/g, "\u2019").replace(/\\ldblquote\b/g, "\u201C").replace(/\\rdblquote\b/g, "\u201D").replace(/\\\w+(?:-?\d+)?\s?/g, " ").replace(/[{}\r\n]/g, " ").replace(/\s+/g, " ").trim();
5334
+ }
5335
+ function parseRtfTables(rtfText) {
5336
+ const rowSegments = rtfText.split(/\\row\b/);
5337
+ if (rowSegments.length <= 1) return [];
5338
+ const rawTables = [];
5339
+ let currentTable = null;
5340
+ let charOffset = 0;
5341
+ for (let i = 0; i < rowSegments.length - 1; i++) {
5342
+ const segment = rowSegments[i];
5343
+ const segmentStart = charOffset;
5344
+ const segmentEnd = charOffset + segment.length + 4;
5345
+ const cellParts = segment.split(/\\cell\b/);
5346
+ if (cellParts.length > 1) {
5347
+ const cellxMatches = [...segment.matchAll(/\\cellx(\d+)/g)].map((m) => parseInt(m[1], 10));
5348
+ let cellWidths = [];
5349
+ if (cellxMatches.length > 0) {
5350
+ let seq = [cellxMatches[0]];
5351
+ for (let k = 1; k < cellxMatches.length; k++) {
5352
+ if (cellxMatches[k] > cellxMatches[k - 1]) seq.push(cellxMatches[k]);
5353
+ else break;
5354
+ }
5355
+ let prev = 0;
5356
+ cellWidths = seq.map((x) => {
5357
+ const w = x - prev;
5358
+ prev = x;
5359
+ return w;
5360
+ });
5361
+ }
5362
+ const cells = [];
5363
+ for (let c = 0; c < cellParts.length - 1; c++) {
5364
+ let rawCell = cellParts[c];
5365
+ if (c === 0) {
5366
+ const lastTrowd = rawCell.lastIndexOf("\\trowd");
5367
+ if (lastTrowd !== -1) rawCell = rawCell.slice(lastTrowd);
5368
+ }
5369
+ cells.push(cleanRtfText(rawCell));
5370
+ }
5371
+ const isNewTable = !currentTable || segmentStart - currentTable.lastEnd > 2500;
5372
+ if (isNewTable) {
5373
+ const prevTextSlice = rtfText.slice(Math.max(0, segmentStart - 4e3), segmentStart);
5374
+ const cleanPrev = cleanRtfText(prevTextSlice);
5375
+ const anchorBefore = cleanPrev.slice(-80).trim();
5376
+ currentTable = {
5377
+ id: `RTF_TABLE_${rawTables.length}`,
5378
+ startPos: segmentStart,
5379
+ lastEnd: segmentEnd,
5380
+ rows: [],
5381
+ columnWidths: cellWidths,
5382
+ anchorBefore,
5383
+ anchorAfter: ""
5384
+ };
5385
+ rawTables.push(currentTable);
5386
+ }
5387
+ if (currentTable) {
5388
+ currentTable.rows.push({
5389
+ cells,
5390
+ widths: cellWidths.length === cells.length ? cellWidths : currentTable.columnWidths
5391
+ });
5392
+ currentTable.lastEnd = segmentEnd;
5393
+ }
5394
+ }
5395
+ charOffset += segment.length + 4;
5396
+ }
5397
+ rawTables.forEach((tbl) => {
5398
+ const afterSlice = rtfText.slice(tbl.lastEnd, Math.min(rtfText.length, tbl.lastEnd + 2e3));
5399
+ const cleanAfter = cleanRtfText(afterSlice);
5400
+ tbl.anchorAfter = cleanAfter.slice(0, 80).trim();
5401
+ });
5402
+ return rawTables.map((tbl) => {
5403
+ const totalW = (tbl.columnWidths || []).reduce((a, b) => a + b, 0);
5404
+ const colPercents = totalW > 0 ? tbl.columnWidths.map((w) => Math.round(w / totalW * 100)) : [];
5405
+ return {
5406
+ id: tbl.id,
5407
+ rows: tbl.rows.map((r) => r.cells),
5408
+ colPercents,
5409
+ anchorBefore: tbl.anchorBefore,
5410
+ anchorAfter: tbl.anchorAfter
5411
+ };
5412
+ });
5413
+ }
5414
+ function renderRtfTableElement(table) {
5415
+ const tableEl = document.createElement("table");
5416
+ tableEl.className = "fp-rtf-table";
5417
+ tableEl.style.width = "100%";
5418
+ tableEl.style.borderCollapse = "collapse";
5419
+ tableEl.style.margin = "16px 0 20px";
5420
+ tableEl.style.fontSize = "10pt";
5421
+ tableEl.style.lineHeight = "1.5";
5422
+ tableEl.style.border = "1px solid #cbd5e1";
5423
+ tableEl.style.borderRadius = "4px";
5424
+ tableEl.style.boxSizing = "border-box";
5425
+ tableEl.style.backgroundColor = "#ffffff";
5426
+ const thead = document.createElement("thead");
5427
+ const tbody = document.createElement("tbody");
5428
+ table.rows.forEach((rowCells, rIdx) => {
5429
+ const tr = document.createElement("tr");
5430
+ const isHeader = rIdx === 0;
5431
+ if (isHeader) {
5432
+ tr.style.backgroundColor = "#f8fafc";
5433
+ tr.style.borderBottom = "2px solid #cbd5e1";
5434
+ } else {
5435
+ tr.style.borderBottom = "1px solid #e2e8f0";
5436
+ if (rIdx % 2 === 0) {
5437
+ tr.style.backgroundColor = "#fcfdfe";
5438
+ }
5439
+ }
5440
+ rowCells.forEach((cellText, cIdx) => {
5441
+ const cellEl = document.createElement(isHeader ? "th" : "td");
5442
+ cellEl.style.padding = "8px 12px";
5443
+ cellEl.style.border = "1px solid #cbd5e1";
5444
+ cellEl.style.verticalAlign = "top";
5445
+ cellEl.style.color = isHeader ? "#0f172a" : "#1e293b";
5446
+ cellEl.style.fontWeight = isHeader ? "600" : "normal";
5447
+ if (table.colPercents && table.colPercents[cIdx]) {
5448
+ cellEl.style.width = `${table.colPercents[cIdx]}%`;
5449
+ }
5450
+ if (/^\d+$/.test(cellText.trim()) || cellText.trim() === "#") {
5451
+ cellEl.style.textAlign = "center";
5452
+ } else {
5453
+ cellEl.style.textAlign = "left";
5454
+ }
5455
+ cellEl.textContent = cellText;
5456
+ tr.appendChild(cellEl);
5457
+ });
5458
+ if (isHeader) {
5459
+ thead.appendChild(tr);
5460
+ } else {
5461
+ tbody.appendChild(tr);
5462
+ }
5463
+ });
5464
+ tableEl.appendChild(thead);
5465
+ tableEl.appendChild(tbody);
5466
+ return tableEl;
5467
+ }
5468
+ function extractMediaFingerprint(el) {
5469
+ const img = el.tagName.toLowerCase() === "img" ? el : el.querySelector("img");
5470
+ if (img && img.src) {
5471
+ const s = img.src;
5472
+ return s.length > 300 ? `img_${s.slice(0, 80)}_${s.length}_${s.slice(-80)}` : `img_${s}`;
5473
+ }
5474
+ const innerImg = el.querySelector("image");
5475
+ if (innerImg) {
5476
+ const h = innerImg.getAttribute("xlink:href") || innerImg.getAttribute("href") || "";
5477
+ if (h) return h.length > 300 ? `svgimg_${h.slice(0, 80)}_${h.length}_${h.slice(-80)}` : `svgimg_${h}`;
5478
+ }
5479
+ const svg = el.tagName.toLowerCase() === "svg" ? el : el.querySelector("svg");
5480
+ if (svg) {
5481
+ const vb = svg.getAttribute("viewBox") || "";
5482
+ return `svg_${vb}_${svg.innerHTML.length}`;
5483
+ }
5484
+ return null;
5485
+ }
5389
5486
  var RtfPlugin = class {
5390
5487
  id = "rtf";
5391
5488
  name = "Rich Text Format (RTF)";
@@ -5421,6 +5518,14 @@ var RtfPlugin = class {
5421
5518
  }
5422
5519
  });
5423
5520
  actions.push(
5521
+ {
5522
+ id: "thumbnails",
5523
+ icon: "thumbnails",
5524
+ label: "Thumbnails",
5525
+ type: "button",
5526
+ group: "view",
5527
+ execute: () => instance.toggleThumbnails?.()
5528
+ },
5424
5529
  {
5425
5530
  id: "zoom-out",
5426
5531
  icon: "zoom-out",
@@ -5548,9 +5653,29 @@ var RtfPlugin = class {
5548
5653
  if (typeof RTFJS.loggingEnabled === "function") {
5549
5654
  RTFJS.loggingEnabled(false);
5550
5655
  }
5551
- const doc = new RTFJS.Document(ctx.buffer, {});
5656
+ const rawText = new TextDecoder("latin1").decode(ctx.buffer);
5657
+ const tables = parseRtfTables(rawText);
5658
+ const seenMediaSignatures = /* @__PURE__ */ new Set();
5659
+ const doc = new RTFJS.Document(ctx.buffer, {
5660
+ onPicture: (isLegacy, createPic) => {
5661
+ if (isLegacy === true) {
5662
+ return null;
5663
+ }
5664
+ const pic = createPic();
5665
+ if (!pic) return null;
5666
+ const fp = extractMediaFingerprint(pic);
5667
+ if (fp) {
5668
+ if (seenMediaSignatures.has(fp)) {
5669
+ return null;
5670
+ }
5671
+ seenMediaSignatures.add(fp);
5672
+ }
5673
+ return pic;
5674
+ }
5675
+ });
5552
5676
  const htmlElements = await doc.render();
5553
5677
  const contentNodes = [];
5678
+ const seenInContentBlocks = /* @__PURE__ */ new Set();
5554
5679
  const extractBlocks = (nodes) => {
5555
5680
  for (const item of nodes) {
5556
5681
  if (!item || !(item instanceof HTMLElement)) continue;
@@ -5562,6 +5687,32 @@ var RtfPlugin = class {
5562
5687
  if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5563
5688
  extractBlocks(Array.from(item.children));
5564
5689
  } else {
5690
+ const fp = extractMediaFingerprint(item);
5691
+ if (fp) {
5692
+ if (seenInContentBlocks.has(fp)) {
5693
+ continue;
5694
+ }
5695
+ seenInContentBlocks.add(fp);
5696
+ }
5697
+ const hasMedia = item.querySelector("img, svg, canvas, table") !== null || ["img", "svg", "table"].includes(tag);
5698
+ const textContent = (item.textContent || "").trim();
5699
+ if (!hasMedia && textContent.length === 0) {
5700
+ continue;
5701
+ }
5702
+ const svgs = item.querySelectorAll("svg, img");
5703
+ svgs.forEach((s) => {
5704
+ const el = s;
5705
+ el.style.maxWidth = "100%";
5706
+ el.style.maxHeight = "360px";
5707
+ el.style.display = "block";
5708
+ el.style.margin = "12px auto";
5709
+ if (s.tagName.toLowerCase() === "svg") {
5710
+ s.removeAttribute("width");
5711
+ s.removeAttribute("height");
5712
+ el.style.width = "100%";
5713
+ el.style.maxHeight = "360px";
5714
+ }
5715
+ });
5565
5716
  if (tag === "p" || tag === "div") {
5566
5717
  item.style.display = "block";
5567
5718
  item.style.marginBottom = "12px";
@@ -5572,52 +5723,120 @@ var RtfPlugin = class {
5572
5723
  }
5573
5724
  };
5574
5725
  extractBlocks(htmlElements);
5575
- wrapper.innerHTML = "";
5576
- contentNodes.forEach((node) => wrapper.appendChild(node));
5577
- const childHeights = contentNodes.map((c) => {
5578
- const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
5579
- const offH = c.offsetHeight || 0;
5580
- const textLen = c.textContent?.trim().length || 0;
5581
- const estH = Math.max(28, Math.ceil(textLen / 75) * 24 + 16);
5582
- return Math.max(rectH, offH, estH);
5726
+ tables.forEach((table) => {
5727
+ const tableEl = renderRtfTableElement(table);
5728
+ let inserted = false;
5729
+ if (table.anchorBefore && table.anchorBefore.length > 10) {
5730
+ const keyword = table.anchorBefore.slice(-35).trim();
5731
+ for (let i = 0; i < contentNodes.length; i++) {
5732
+ if (contentNodes[i].textContent?.includes(keyword)) {
5733
+ contentNodes.splice(i + 1, 0, tableEl);
5734
+ inserted = true;
5735
+ break;
5736
+ }
5737
+ }
5738
+ }
5739
+ if (!inserted && table.anchorAfter && table.anchorAfter.length > 10) {
5740
+ const keyword = table.anchorAfter.slice(0, 35).trim();
5741
+ for (let i = 0; i < contentNodes.length; i++) {
5742
+ if (contentNodes[i].textContent?.includes(keyword)) {
5743
+ contentNodes.splice(i, 0, tableEl);
5744
+ inserted = true;
5745
+ break;
5746
+ }
5747
+ }
5748
+ }
5749
+ if (!inserted) {
5750
+ if (contentNodes.length > 2) {
5751
+ contentNodes.splice(2, 0, tableEl);
5752
+ } else {
5753
+ contentNodes.push(tableEl);
5754
+ }
5755
+ }
5583
5756
  });
5584
- wrapper.innerHTML = "";
5585
- const createRtfCard = () => {
5757
+ const createRtfCard = (pageNum) => {
5586
5758
  const card = document.createElement("div");
5587
5759
  card.className = "fp-rtf-page-card";
5760
+ card.setAttribute("data-page-number", String(pageNum));
5588
5761
  card.style.backgroundColor = "#ffffff";
5589
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5762
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04)";
5590
5763
  card.style.borderRadius = "4px";
5591
- card.style.padding = "72px 56px";
5764
+ card.style.padding = "64px 56px";
5592
5765
  card.style.width = "816px";
5593
5766
  card.style.minHeight = "1056px";
5767
+ card.style.maxHeight = "1056px";
5768
+ card.style.overflow = "hidden";
5594
5769
  card.style.boxSizing = "border-box";
5595
5770
  card.style.marginBottom = "24px";
5596
5771
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5597
5772
  card.style.lineHeight = "1.6";
5598
5773
  card.style.color = "#1e293b";
5774
+ card.style.position = "relative";
5599
5775
  return card;
5600
5776
  };
5601
- let curCard = createRtfCard();
5777
+ wrapper.innerHTML = "";
5778
+ const measureCard = createRtfCard(0);
5779
+ measureCard.style.maxHeight = "none";
5780
+ measureCard.style.height = "auto";
5781
+ wrapper.appendChild(measureCard);
5782
+ contentNodes.forEach((node) => measureCard.appendChild(node));
5783
+ const childHeights = contentNodes.map((c) => {
5784
+ const rect = c.getBoundingClientRect ? c.getBoundingClientRect() : null;
5785
+ const rectH = rect ? rect.height : 0;
5786
+ const offH = c.offsetHeight || 0;
5787
+ let realH = Math.max(rectH, offH);
5788
+ try {
5789
+ const cs = window.getComputedStyle(c);
5790
+ realH += (parseFloat(cs.marginTop) || 0) + (parseFloat(cs.marginBottom) || 0);
5791
+ } catch {
5792
+ }
5793
+ const hasMedia = c.querySelector("img, svg") !== null || ["img", "svg"].includes(c.tagName.toLowerCase());
5794
+ const hasTable = c.querySelector("table") !== null || c.tagName.toLowerCase() === "table";
5795
+ if (hasMedia) {
5796
+ realH = Math.max(realH, 300);
5797
+ } else if (hasTable) {
5798
+ realH = Math.max(realH, 240);
5799
+ }
5800
+ if (realH > 0) return realH;
5801
+ const textLen = c.textContent?.trim().length || 0;
5802
+ return Math.max(28, Math.ceil(textLen / 75) * 26 + 16);
5803
+ });
5804
+ wrapper.innerHTML = "";
5805
+ let curCard = createRtfCard(1);
5602
5806
  wrapper.appendChild(curCard);
5603
5807
  pageElements = [curCard];
5604
5808
  let curH = 0;
5605
- const maxH = 912;
5809
+ const maxH = 860;
5606
5810
  for (let i = 0; i < contentNodes.length; i++) {
5607
5811
  const child = contentNodes[i];
5608
5812
  const chH = childHeights[i];
5609
- curCard.appendChild(child);
5610
- curH += chH;
5611
- if (curH >= maxH && i < contentNodes.length - 1) {
5612
- curCard = createRtfCard();
5813
+ const isChildEmpty = (child.textContent || "").trim().length === 0 && !child.querySelector("table, img, svg, canvas");
5814
+ if (curH === 0 && isChildEmpty) {
5815
+ continue;
5816
+ }
5817
+ const isTable = child.querySelector("table") !== null || child.tagName.toLowerCase() === "table";
5818
+ const shouldBreakBeforeTable = isTable && curH > 400 && curCard.childNodes.length > 0;
5819
+ if ((curH + chH > maxH || shouldBreakBeforeTable) && curCard.childNodes.length > 0) {
5820
+ curCard = createRtfCard(pageElements.length + 1);
5613
5821
  wrapper.appendChild(curCard);
5614
5822
  pageElements.push(curCard);
5615
5823
  curH = 0;
5616
5824
  }
5825
+ curCard.appendChild(child);
5826
+ curH += chH;
5827
+ }
5828
+ pageElements = pageElements.filter((card) => {
5829
+ const hasText = (card.textContent || "").trim().length > 0;
5830
+ const hasMedia = card.querySelector("table, img, svg, canvas") !== null;
5831
+ return hasText || hasMedia;
5832
+ });
5833
+ if (pageElements.length === 0) {
5834
+ pageElements = [curCard];
5617
5835
  }
5618
5836
  } catch (err) {
5619
5837
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
5620
5838
  const text = new TextDecoder("latin1").decode(ctx.buffer);
5839
+ const tables = parseRtfTables(text);
5621
5840
  const rawPages = text.split(/\\page\b/).map((segment) => {
5622
5841
  return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
5623
5842
  }).filter((p) => p.length > 0);
@@ -5628,7 +5847,7 @@ var RtfPlugin = class {
5628
5847
  pageCard.style.backgroundColor = "#ffffff";
5629
5848
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
5630
5849
  pageCard.style.borderRadius = "4px";
5631
- pageCard.style.padding = "72px 56px";
5850
+ pageCard.style.padding = "64px 56px";
5632
5851
  pageCard.style.width = "816px";
5633
5852
  pageCard.style.minHeight = "1056px";
5634
5853
  pageCard.style.boxSizing = "border-box";
@@ -5639,6 +5858,9 @@ var RtfPlugin = class {
5639
5858
  pageCard.style.whiteSpace = "pre-wrap";
5640
5859
  pageCard.style.display = i === 0 ? "block" : "none";
5641
5860
  pageCard.textContent = pages[i];
5861
+ if (i === 1 && tables.length > 0) {
5862
+ tables.forEach((tbl) => pageCard.appendChild(renderRtfTableElement(tbl)));
5863
+ }
5642
5864
  wrapper.appendChild(pageCard);
5643
5865
  pageElements.push(pageCard);
5644
5866
  }
@@ -5669,6 +5891,32 @@ var RtfPlugin = class {
5669
5891
  getPageCount: () => totalPages,
5670
5892
  getCurrentPage: () => currentPage,
5671
5893
  goToPage: (page) => showPage(page),
5894
+ getThumbnails: async () => {
5895
+ const items = [];
5896
+ for (let idx = 0; idx < totalPages; idx++) {
5897
+ const pageIdx = idx;
5898
+ items.push({
5899
+ index: pageIdx,
5900
+ label: `Page ${pageIdx + 1}`,
5901
+ render: async (canvas) => {
5902
+ const ctx2d = canvas.getContext("2d");
5903
+ if (!ctx2d) return;
5904
+ canvas.width = 140;
5905
+ canvas.height = 180;
5906
+ ctx2d.fillStyle = "#ffffff";
5907
+ ctx2d.fillRect(0, 0, 140, 180);
5908
+ ctx2d.strokeStyle = "#cbd5e1";
5909
+ ctx2d.lineWidth = 1;
5910
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
5911
+ ctx2d.fillStyle = "#334155";
5912
+ ctx2d.font = 'bold 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
5913
+ ctx2d.textAlign = "center";
5914
+ ctx2d.fillText(`Page ${pageIdx + 1}`, 70, 95);
5915
+ }
5916
+ });
5917
+ }
5918
+ return items;
5919
+ },
5672
5920
  zoomIn: () => {
5673
5921
  isUserZoomed = true;
5674
5922
  scale += 0.15;