@files-preview-app/preview-file 1.3.0 → 1.3.1

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/react.js CHANGED
@@ -2,6 +2,7 @@ import { memo, forwardRef, useRef, useImperativeHandle, useEffect } from 'react'
2
2
  import DOMPurify6 from 'dompurify';
3
3
  import * as pdfjsLib from 'pdfjs-dist';
4
4
  import * as docx from 'docx-preview';
5
+ import * as fflate from 'fflate';
5
6
  import { unzipSync, strFromU8, unzip } from 'fflate';
6
7
  import * as XLSX from 'xlsx';
7
8
  import hljs from 'highlight.js';
@@ -667,9 +668,11 @@ var ToolbarController = class {
667
668
  type: "button",
668
669
  "data-action-id": id
669
670
  });
670
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
671
- if (svg) {
672
- btn.innerHTML = sanitizeSVG(svg);
671
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
672
+ if (internalSvg) {
673
+ btn.innerHTML = internalSvg;
674
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
675
+ btn.innerHTML = sanitizeSVG(iconHtml);
673
676
  } else {
674
677
  btn.textContent = title || id;
675
678
  }
@@ -5669,8 +5672,17 @@ var DocPlugin = class {
5669
5672
  let scale = 1;
5670
5673
  let extractedRawText = "";
5671
5674
  let isFallback = false;
5675
+ let chartSvg = "";
5672
5676
  try {
5673
5677
  const cfbf = new CfbfReader(ctx.buffer);
5678
+ try {
5679
+ const pkg = cfbf.readStream("package_stream");
5680
+ if (pkg && pkg.length > 100) {
5681
+ chartSvg = this.parseOdfChartToSvg(pkg);
5682
+ }
5683
+ } catch (chartErr) {
5684
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
5685
+ }
5674
5686
  const wordDocStream = cfbf.readStream("WordDocument");
5675
5687
  if (!wordDocStream || wordDocStream.length < 512) {
5676
5688
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5688,7 +5700,7 @@ var DocPlugin = class {
5688
5700
  extractedRawText = fallback;
5689
5701
  isFallback = true;
5690
5702
  }
5691
- const rawPages = this.splitIntoPages(extractedRawText);
5703
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5692
5704
  const totalPages = Math.max(1, rawPages.length);
5693
5705
  let currentPage = 1;
5694
5706
  const pageCards = [];
@@ -5930,7 +5942,7 @@ var DocPlugin = class {
5930
5942
  for (const run of [...ansiRuns, ...utf16Runs]) {
5931
5943
  const trimmed = run.trim();
5932
5944
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
5933
- if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
5945
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^EMBED\b/i.test(trimmed) && !trimmed.includes("ChartDocument") && !/^[\W_0-9]+$/.test(trimmed)) {
5934
5946
  seen.add(trimmed);
5935
5947
  candidateLines.push(trimmed);
5936
5948
  }
@@ -5941,9 +5953,114 @@ var DocPlugin = class {
5941
5953
  heuristicTextExtraction(buffer) {
5942
5954
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5943
5955
  }
5944
- cleanWordDocFields(text) {
5956
+ /**
5957
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
5958
+ */
5959
+ parseOdfChartToSvg(zipBytes) {
5960
+ try {
5961
+ const unzipped = fflate.unzipSync(zipBytes);
5962
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
5963
+ if (!contentXml) return "";
5964
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
5965
+ if (rowsMatch.length < 2) return "";
5966
+ const headers = [];
5967
+ const firstRow = rowsMatch[0];
5968
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
5969
+ for (const h of headerCells) {
5970
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
5971
+ }
5972
+ const categories = [];
5973
+ const seriesValues = headers.map(() => []);
5974
+ for (let r = 1; r < rowsMatch.length; r++) {
5975
+ const rowStr = rowsMatch[r];
5976
+ if (!rowStr) continue;
5977
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
5978
+ if (cells.length > 0 && cells[0]) {
5979
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
5980
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
5981
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
5982
+ const cellStr = cells[c];
5983
+ if (!cellStr) continue;
5984
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
5985
+ const series = seriesValues[c - 1];
5986
+ if (series) {
5987
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
5988
+ }
5989
+ }
5990
+ }
5991
+ }
5992
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
5993
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
5994
+ let cIdx = 0;
5995
+ for (const cm of colorMatches) {
5996
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
5997
+ cIdx++;
5998
+ }
5999
+ let maxVal = 10;
6000
+ for (const s of seriesValues) {
6001
+ for (const v of s) {
6002
+ if (v > maxVal) maxVal = v;
6003
+ }
6004
+ }
6005
+ maxVal = Math.ceil(maxVal * 1.15);
6006
+ const width = 560;
6007
+ const height = 280;
6008
+ const padLeft = 45;
6009
+ const padRight = 100;
6010
+ const padTop = 20;
6011
+ const padBottom = 40;
6012
+ const chartW = width - padLeft - padRight;
6013
+ const chartH = height - padTop - padBottom;
6014
+ let svg = `<svg viewBox="0 0 ${width} ${height}" width="100%" height="auto" style="max-width: 560px; height: 280px; margin: 16px auto; display: block; font-family: Calibri, sans-serif; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 1px 4px rgba(0,0,0,0.05);">`;
6015
+ for (let step = 0; step <= 4; step++) {
6016
+ const yVal = (maxVal / 4 * step).toFixed(1);
6017
+ const yPos = padTop + chartH - step / 4 * chartH;
6018
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6019
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6020
+ }
6021
+ const numCats = categories.length;
6022
+ const numSeries = headers.length;
6023
+ const groupW = chartW / numCats;
6024
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6025
+ const groupPad = (groupW - barW * numSeries) / 2;
6026
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6027
+ const groupX = padLeft + catIdx * groupW + groupPad;
6028
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6029
+ const val = seriesValues[sIdx][catIdx] || 0;
6030
+ const barH = val / maxVal * chartH;
6031
+ const barX = groupX + sIdx * barW;
6032
+ const barY = padTop + chartH - barH;
6033
+ const col = colors[sIdx % colors.length];
6034
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6035
+ }
6036
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6037
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6038
+ }
6039
+ let legendY = padTop + 20;
6040
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6041
+ const col = colors[sIdx % colors.length];
6042
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6043
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6044
+ legendY += 20;
6045
+ }
6046
+ svg += "</svg>";
6047
+ return svg;
6048
+ } catch (e) {
6049
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6050
+ return "";
6051
+ }
6052
+ }
6053
+ cleanWordDocFields(text, chartSvg = "") {
5945
6054
  if (!text) return "";
5946
6055
  let cleaned = text.replace(
6056
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6057
+ () => chartSvg ? `
6058
+
6059
+ ${chartSvg}
6060
+
6061
+ ` : ""
6062
+ );
6063
+ cleaned = cleaned.replace(
5947
6064
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5948
6065
  (_match, url, label) => {
5949
6066
  const cleanUrl = url.trim();
@@ -5951,18 +6068,23 @@ var DocPlugin = class {
5951
6068
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5952
6069
  }
5953
6070
  );
5954
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6071
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6072
+ if (/[\x00-\x1F]/.test(res)) return "";
6073
+ return res.trim();
6074
+ });
5955
6075
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
5956
6076
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6077
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6078
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
5957
6079
  return cleaned;
5958
6080
  }
5959
- splitIntoPages(text) {
6081
+ splitIntoPages(text, chartSvg = "") {
5960
6082
  if (!text) return [""];
5961
- const cleanedText = this.cleanWordDocFields(text);
6083
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
5962
6084
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5963
6085
  const explicitParts = normalized.split(/[\x0C\f]|\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
5964
6086
  if (explicitParts.length === 0) explicitParts.push(normalized);
5965
- const maxLinesPerPage = 32;
6087
+ const maxLinesPerPage = 34;
5966
6088
  const charsPerLine = 80;
5967
6089
  const finalPages = [];
5968
6090
  for (const part of explicitParts) {
@@ -5970,8 +6092,8 @@ var DocPlugin = class {
5970
6092
  let currentLines = [];
5971
6093
  let count = 0;
5972
6094
  for (const line of lines) {
5973
- const plainLine = line.replace(/<[^>]+>/g, "");
5974
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6095
+ const isSvg = line.includes("<svg");
6096
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
5975
6097
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5976
6098
  finalPages.push(currentLines.join("\n"));
5977
6099
  currentLines = [];
@@ -6011,9 +6133,44 @@ var DocPlugin = class {
6011
6133
  tableLines = [];
6012
6134
  }
6013
6135
  };
6136
+ const sanitizeOptions = {
6137
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
6138
+ ADD_ATTR: [
6139
+ "href",
6140
+ "target",
6141
+ "rel",
6142
+ "style",
6143
+ "viewBox",
6144
+ "width",
6145
+ "height",
6146
+ "x",
6147
+ "y",
6148
+ "x1",
6149
+ "y1",
6150
+ "x2",
6151
+ "y2",
6152
+ "fill",
6153
+ "stroke",
6154
+ "stroke-width",
6155
+ "stroke-dasharray",
6156
+ "rx",
6157
+ "font-size",
6158
+ "text-anchor"
6159
+ ]
6160
+ };
6014
6161
  let i = 0;
6015
6162
  while (i < lines.length) {
6016
6163
  let line = lines[i];
6164
+ if (line.includes("<svg")) {
6165
+ if (inList) {
6166
+ html += "</ul>";
6167
+ inList = false;
6168
+ }
6169
+ flushTable();
6170
+ html += line;
6171
+ i++;
6172
+ continue;
6173
+ }
6017
6174
  let tabCount = (line.match(/\t/g) || []).length;
6018
6175
  if (tabCount > 0) {
6019
6176
  let j = i;
@@ -6047,7 +6204,6 @@ var DocPlugin = class {
6047
6204
  i++;
6048
6205
  continue;
6049
6206
  }
6050
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6051
6207
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
6052
6208
  if (inList) {
6053
6209
  html += "</ul>";