mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Replace unpdf and officeparser with lightweight document parsers
Switch PDF extraction to Obsidian's bundled PDF.js via loadPdfJs() instead of unpdf. Replace officeparser with mammoth for DOCX and custom fflate-based ZIP parsers for PPTX/XLSX/ODF formats. Eliminates heavy dependencies, removes all dynamic eval and .wasm references, and simplifies the esbuild plugin configuration.
This commit is contained in:
parent
9f6994e30d
commit
9457fa5b44
8 changed files with 382 additions and 745 deletions
|
|
@ -1,18 +1,26 @@
|
|||
import { extractText, getDocumentProxy } from 'unpdf';
|
||||
import { loadPdfJs } from 'obsidian';
|
||||
import { unzipSync, strFromU8 } from 'fflate';
|
||||
import mammoth from 'mammoth/mammoth.browser.js';
|
||||
import type { IPageText } from '../Types/SearchTypes';
|
||||
import { Exception } from './Exception';
|
||||
import { parseOffice } from 'officeparser';
|
||||
|
||||
// Handles PDF format
|
||||
// Uses Obsidian's own bundled PDF.js via loadPdfJs() rather than shipping our own
|
||||
export async function readPDF(arrayBuffer: ArrayBuffer): Promise<IPageText[]> {
|
||||
try {
|
||||
const pdf = await getDocumentProxy(new Uint8Array(arrayBuffer), { isEvalSupported: false });
|
||||
const pages = (await extractText(pdf, { mergePages: false })).text;
|
||||
const pdfjs = await loadPdfJs();
|
||||
const pdf = await pdfjs.getDocument({ data: new Uint8Array(arrayBuffer) }).promise;
|
||||
|
||||
const pageTexts: IPageText[] = pages.map((pageText, index) => ({
|
||||
text: pageText,
|
||||
pageNumber: index + 1
|
||||
}));
|
||||
const pageTexts: IPageText[] = [];
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i);
|
||||
const content = await page.getTextContent();
|
||||
const text = content.items
|
||||
.map((item: { str?: string }) => item.str ?? '')
|
||||
.join(' ');
|
||||
pageTexts.push({ text, pageNumber: i });
|
||||
page.cleanup?.();
|
||||
}
|
||||
await pdf.destroy?.();
|
||||
|
||||
return pageTexts;
|
||||
} catch (error) {
|
||||
|
|
@ -31,18 +39,137 @@ export async function readPDF(arrayBuffer: ArrayBuffer): Promise<IPageText[]> {
|
|||
}
|
||||
}
|
||||
|
||||
// Handles document formats: DOCX, PPTX, XLSX, ODT, ODP, ODS
|
||||
export async function readDocument(arrayBuffer: ArrayBuffer): Promise<IPageText[]> {
|
||||
// Handles document formats: DOCX, PPTX, XLSX, ODT, ODP, ODS.
|
||||
//
|
||||
// DOCX goes through mammoth (battle-tested: tables, lists, footnotes). The remaining
|
||||
// formats are ZIP-of-XML containers we read with fflate + the platform DOMParser, so
|
||||
// no heavy parser is bundled. We only extract plain text (no OCR, no images) — the LLM
|
||||
// can read files directly and use its own vision engine when it needs to.
|
||||
export async function readDocument(arrayBuffer: ArrayBuffer, extension: string): Promise<IPageText[]> {
|
||||
try {
|
||||
const ast = await parseOffice(arrayBuffer, {
|
||||
extractAttachments: true,
|
||||
ocr: true,
|
||||
ocrLanguage: "eng+esp"
|
||||
});
|
||||
let text: string;
|
||||
switch (extension.toLowerCase()) {
|
||||
case 'docx':
|
||||
text = (await mammoth.extractRawText({ arrayBuffer })).value;
|
||||
break;
|
||||
case 'xlsx':
|
||||
text = extractXlsx(arrayBuffer);
|
||||
break;
|
||||
case 'pptx':
|
||||
text = extractPptx(arrayBuffer);
|
||||
break;
|
||||
case 'odt':
|
||||
case 'odp':
|
||||
case 'ods':
|
||||
text = extractOdf(arrayBuffer);
|
||||
break;
|
||||
default:
|
||||
throw Exception.new(`Unsupported document type: .${extension}`);
|
||||
}
|
||||
|
||||
// OfficeParser doesn't currently expose page data (page number etc)
|
||||
return [{ text: (await ast.to('text')).value, pageNumber: 1 }] as IPageText[];
|
||||
// These formats expose no page structure, so we return a single page
|
||||
return [{ text, pageNumber: 1 }] as IPageText[];
|
||||
} catch (error) {
|
||||
return [{ text: `Failed to read document: ${Exception.messageFrom(error)}`, pageNumber: 1 }] as IPageText[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Shared helpers for the ZIP-of-XML formats (xlsx / pptx / odf) ---
|
||||
|
||||
// Throws on legacy OLE2 binaries (.xls/.ppt/.doc) — they aren't ZIPs. Callers wrap
|
||||
// this so the failure surfaces as a clean "failed to read" message.
|
||||
function unzip(arrayBuffer: ArrayBuffer): Record<string, Uint8Array> {
|
||||
return unzipSync(new Uint8Array(arrayBuffer));
|
||||
}
|
||||
|
||||
function parseXml(bytes: Uint8Array): Document {
|
||||
return new DOMParser().parseFromString(strFromU8(bytes), 'application/xml');
|
||||
}
|
||||
|
||||
/** Numeric sort key from a path like "ppt/slides/slide12.xml" -> 12. */
|
||||
function ordinal(path: string): number {
|
||||
const match = path.match(/(\d+)\.xml$/);
|
||||
return match ? Number(match[1]) : 0;
|
||||
}
|
||||
|
||||
// --- XLSX: values live as indices into a shared-strings table ---
|
||||
function extractXlsx(arrayBuffer: ArrayBuffer): string {
|
||||
const files = unzip(arrayBuffer);
|
||||
|
||||
// Resolve the shared-strings table (cells of type "s" point into this).
|
||||
let shared: string[] = [];
|
||||
if (files['xl/sharedStrings.xml']) {
|
||||
const dom = parseXml(files['xl/sharedStrings.xml']);
|
||||
shared = Array.from(dom.getElementsByTagName('si')).map((si) =>
|
||||
Array.from(si.getElementsByTagName('t'))
|
||||
.map((t) => t.textContent ?? '')
|
||||
.join('')
|
||||
);
|
||||
}
|
||||
|
||||
const sheets = Object.keys(files)
|
||||
.filter((name) => /^xl\/worksheets\/sheet\d+\.xml$/.test(name))
|
||||
.sort((a, b) => ordinal(a) - ordinal(b));
|
||||
|
||||
const rows: string[] = [];
|
||||
for (const name of sheets) {
|
||||
const dom = parseXml(files[name]);
|
||||
for (const row of Array.from(dom.getElementsByTagName('row'))) {
|
||||
const cells = Array.from(row.getElementsByTagName('c')).map((cell) => {
|
||||
const type = cell.getAttribute('t');
|
||||
if (type === 'inlineStr') {
|
||||
// Text stored directly in the cell: <c t="inlineStr"><is><t>…</t></is></c>
|
||||
return Array.from(cell.getElementsByTagName('t'))
|
||||
.map((t) => t.textContent ?? '')
|
||||
.join('');
|
||||
}
|
||||
const value = cell.getElementsByTagName('v')[0];
|
||||
if (!value) return '';
|
||||
return type === 's'
|
||||
? shared[Number(value.textContent)] ?? ''
|
||||
: value.textContent ?? '';
|
||||
});
|
||||
rows.push(cells.join('\t'));
|
||||
}
|
||||
}
|
||||
return rows.join('\n');
|
||||
}
|
||||
|
||||
// --- PPTX: one XML per slide; sort numerically so output is in slide order ---
|
||||
function extractPptx(arrayBuffer: ArrayBuffer): string {
|
||||
const files = unzip(arrayBuffer);
|
||||
|
||||
const slides = Object.keys(files)
|
||||
.filter((name) => /^ppt\/slides\/slide\d+\.xml$/.test(name))
|
||||
.sort((a, b) => ordinal(a) - ordinal(b));
|
||||
|
||||
return slides
|
||||
.map((name) => {
|
||||
const dom = parseXml(files[name]);
|
||||
return Array.from(dom.getElementsByTagName('a:t'))
|
||||
.map((t) => t.textContent ?? '')
|
||||
.join(' ');
|
||||
})
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
// --- ODF (odt / ods / odp): everything lives in a single content.xml ---
|
||||
function extractOdf(arrayBuffer: ArrayBuffer): string {
|
||||
const files = unzip(arrayBuffer);
|
||||
if (!files['content.xml']) return '';
|
||||
|
||||
const dom = parseXml(files['content.xml']);
|
||||
|
||||
// Single document-order pass over paragraphs (text:p) and headings (text:h).
|
||||
// getElementsByTagName("*") preserves order, keeping body text, spreadsheet cells,
|
||||
// and slide text in the sequence they appear.
|
||||
const out: string[] = [];
|
||||
const all = dom.getElementsByTagName('*');
|
||||
for (let i = 0; i < all.length; i++) {
|
||||
const tag = all[i].tagName; // prefixed, e.g. "text:p"
|
||||
if (tag === 'text:p' || tag === 'text:h') {
|
||||
out.push(all[i].textContent ?? '');
|
||||
}
|
||||
}
|
||||
return out.join('\n');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -410,8 +410,9 @@ This plugin is built on the shoulders of many excellent projects:
|
|||
- Official SDKs: [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript), [@google/genai](https://github.com/google/generative-ai-js), [openai](https://github.com/openai/openai-node)
|
||||
|
||||
**Document Processing**
|
||||
- [unpdf](https://github.com/unjs/unpdf) - PDF parsing and text extraction
|
||||
- [officeparser](https://github.com/nicktomlin/officeparser) - Office document parsing (DOCX, PPTX, XLSX, ODT, ODP, ODS)
|
||||
- [mammoth](https://github.com/mwilliamson/mammoth.js) - DOCX text extraction
|
||||
- [fflate](https://github.com/101arrowz/fflate) - Unzipping Office Open XML / OpenDocument files (PPTX, XLSX, ODT, ODP, ODS)
|
||||
- PDF text extraction via Obsidian's bundled [PDF.js](https://mozilla.github.io/pdf.js/) (`loadPdfJs()`)
|
||||
|
||||
**UI Framework**
|
||||
- [Svelte](https://svelte.dev) - Reactive UI components
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export class InputService {
|
|||
}
|
||||
|
||||
if (isDocumentFile(fileType)) {
|
||||
const content = await readDocument(await file.arrayBuffer());
|
||||
const content = await readDocument(await file.arrayBuffer(), fileType);
|
||||
attachments.push(new Attachment(
|
||||
file.name,
|
||||
MimeType.TEXT_PLAIN,
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export class VaultService {
|
|||
if (isDocumentFile(fileExtension)) {
|
||||
const arrayBuffer = await this.readBinaryData(file, allowAccessToPluginRoot);
|
||||
if (arrayBuffer) {
|
||||
return (await readDocument(arrayBuffer))[0].text;
|
||||
return (await readDocument(arrayBuffer, fileExtension))[0].text;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -418,7 +418,7 @@ export class VaultService {
|
|||
content = await readPDF(arrayBuffer);
|
||||
} else if (isDocumentFile(fileExtension)) {
|
||||
const arrayBuffer = await this.vault.readBinary(file);
|
||||
content = await readDocument(arrayBuffer);
|
||||
content = await readDocument(arrayBuffer, fileExtension);
|
||||
} else {
|
||||
content = [{ text: await this.vault.cachedRead(file), pageNumber: 1 }] as IPageText[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,26 +41,23 @@ function copyDir(src, dest) {
|
|||
|
||||
// Neutralises `new Function(...)` literals in the bundled output.
|
||||
//
|
||||
// All such literals come from dependencies, never from our own source:
|
||||
// - unpdf/PDF.js: 3x feature-detection probes `new Function("")` wrapped in
|
||||
// try/catch (a throw makes them return `false` = "eval not supported", the
|
||||
// safe answer), plus 1x PostScript JIT compiler gated behind
|
||||
// `isEvalSupported` (we pass `false`, so it is never entered).
|
||||
// All such literals come from dependencies, never from our own source, and every
|
||||
// one is dead code in our usage:
|
||||
// - bluebird (transitive via mammoth): 7x promise-method JIT compilers, ALL
|
||||
// gated behind `canEvaluate = typeof navigator == "undefined"`. Obsidian always
|
||||
// defines `navigator`, so `canEvaluate` is false and bluebird takes its pure-JS
|
||||
// fallbacks — the `new Function` branches are never entered. (call_get.js x2,
|
||||
// join.js x3, promisify.js x2.)
|
||||
// - diff2html/@profoundlogic/hogan: 2x template compilers reachable only via
|
||||
// Hogan.compile(), which diff2html invokes only for runtime `rawTemplates`.
|
||||
// We use diff2html's precompiled defaults, so these never run.
|
||||
//
|
||||
// Every occurrence is therefore dead code in our usage. We rewrite them to a
|
||||
// throwing stub so the bundle contains no `new Function(` literal, which clears
|
||||
// the Obsidian scanner's "Dynamic Code Execution" warning. The build FAILS if
|
||||
// the count drifts from EXPECTED_NEW_FUNCTION_COUNT, so a dependency change can
|
||||
// never silently reintroduce a live (or un-neutralised) dynamic-eval path.
|
||||
// 9 = 5 PDF.js feature-detection probes (`new Function(""),!0}catch{return!1}`)
|
||||
// + 2 PDF.js PostScript JIT compilers (gated behind `isEvalSupported`, which
|
||||
// DocumentHelper passes as `false`) + 2 Hogan template compilers (reachable
|
||||
// only via Hogan.compile(), unused with diff2html's precompiled templates).
|
||||
// unpdf >=1.6 vendors PDF.js twice (main + worker/legacy), so the PDF.js probe
|
||||
// and JIT sites each appear in duplicate; the Hogan pair is unchanged.
|
||||
// We rewrite them to a throwing stub so the bundle contains no `new Function(`
|
||||
// literal, which clears the Obsidian scanner's "Dynamic Code Execution" warning. The
|
||||
// build FAILS if the count drifts from EXPECTED_NEW_FUNCTION_COUNT, so a dependency
|
||||
// change can never silently reintroduce a live (or un-neutralised) dynamic-eval path.
|
||||
//
|
||||
// 9 = 7 bluebird (canEvaluate-gated) + 2 Hogan.
|
||||
const EXPECTED_NEW_FUNCTION_COUNT = 9;
|
||||
const NEW_FUNCTION_STUB = "VKBlockedDynamicFn";
|
||||
function neutraliseDynamicEval(outfile) {
|
||||
|
|
@ -94,98 +91,15 @@ function neutraliseDynamicEval(outfile) {
|
|||
);
|
||||
}
|
||||
|
||||
// Neutralises the `.wasm` filename literals in the bundled output.
|
||||
//
|
||||
// Every reference comes from unpdf's vendored PDF.js, which loads optional
|
||||
// WebAssembly modules only when *rendering/decoding image streams*:
|
||||
// - qcms_bg.wasm — colour management (ICC profile transforms)
|
||||
// - jbig2.wasm — JBIG2 decoder (scanned bilevel images)
|
||||
// - openjpeg.wasm — OpenJPEG decoder (JPEG2000 images)
|
||||
// Our PDF code (Helpers/DocumentHelper.ts) only calls extractText for text and
|
||||
// never renders, so none of these loaders is entered. Each is also gated behind
|
||||
// private-field checks and resolves its file via a relative URL we do not ship,
|
||||
// so even if reached it falls back to PDF.js's pure-JS path. (unpdf >=1.6
|
||||
// vendors PDF.js twice, so qcms_bg.wasm appears in duplicate.)
|
||||
//
|
||||
// The literals are therefore dead, but their `.wasm` filenames trip Obsidian's
|
||||
// "unrecognised .wasm" scanner (native binary that can't be statically
|
||||
// reviewed). We rewrite each `*.wasm` filename to a non-`.wasm` name so no
|
||||
// `.wasm` literal remains; the surrounding (unreachable) code stays intact. The
|
||||
// build FAILS if an unexpected `.wasm` literal appears (or an expected one
|
||||
// vanishes), so a dependency change can never silently reintroduce an
|
||||
// undocumented .wasm reference.
|
||||
//
|
||||
// Keyed by literal filename → number of occurrences expected in the bundle.
|
||||
const EXPECTED_WASM_REFS = {
|
||||
"qcms_bg.wasm": 2,
|
||||
"jbig2.wasm": 1,
|
||||
"openjpeg.wasm": 1,
|
||||
};
|
||||
const WASM_REF_SUFFIX_FROM = ".wasm";
|
||||
const WASM_REF_SUFFIX_TO = ".disabled-wasm";
|
||||
function neutraliseWasmRef(outfile) {
|
||||
if (!existsSync(outfile)) return;
|
||||
let contents = readFileSync(outfile, "utf-8");
|
||||
// Match the whole quoted/templated literal ending in `.wasm`, anchored on the
|
||||
// quote/backtick. The negated class `[^'"`]` cannot overlap the delimiters, so
|
||||
// this matches linearly — a `[\w.-]*\.wasm` form instead backtracks
|
||||
// catastrophically on the megabytes of base64 in the dev inline sourcemap and
|
||||
// hangs the watcher at 100% CPU.
|
||||
const wasmLiterals = contents.match(/['"`][^'"`]*\.wasm['"`]/g) || [];
|
||||
// Extract just the `<name>.wasm` basename from each literal: drop the quotes,
|
||||
// and for template literals drop any `${...}` interpolation prefix (qcms refs
|
||||
// look like `` `${...}qcms_bg.wasm` ``). The basename is the trailing run of
|
||||
// filename characters ending in `.wasm`.
|
||||
const found = {};
|
||||
for (const lit of wasmLiterals) {
|
||||
const inner = lit.slice(1, -1);
|
||||
const name = (inner.match(/[\w.-]+\.wasm$/) || [inner])[0];
|
||||
found[name] = (found[name] || 0) + 1;
|
||||
}
|
||||
// Any literal we don't recognise → fail loudly (new undocumented .wasm).
|
||||
const unknown = Object.keys(found).filter((n) => !(n in EXPECTED_WASM_REFS));
|
||||
if (unknown.length) {
|
||||
throw new Error(
|
||||
`neutraliseWasmRef: unexpected \`.wasm\` literal(s) in ${outfile}: ` +
|
||||
`${unknown.join(", ")}. A dependency introduced a new .wasm reference — ` +
|
||||
`re-audit that it is unreachable, then add it to EXPECTED_WASM_REFS.`,
|
||||
);
|
||||
}
|
||||
// Each expected filename must be present with the expected count.
|
||||
for (const [name, count] of Object.entries(EXPECTED_WASM_REFS)) {
|
||||
if ((found[name] || 0) !== count) {
|
||||
throw new Error(
|
||||
`neutraliseWasmRef: expected ${count} \`${name}\` literal(s) in ` +
|
||||
`${outfile} but found ${found[name] || 0}. The .wasm references ` +
|
||||
`changed shape — re-audit before shipping, then update ` +
|
||||
`EXPECTED_WASM_REFS.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Rewrite only the `.wasm` extension on the literals we've vetted, so no
|
||||
// `.wasm` string remains for the scanner to flag.
|
||||
for (const name of Object.keys(EXPECTED_WASM_REFS)) {
|
||||
contents = contents.split(name).join(
|
||||
name.slice(0, -WASM_REF_SUFFIX_FROM.length) + WASM_REF_SUFFIX_TO,
|
||||
);
|
||||
}
|
||||
writeFileSync(outfile, contents);
|
||||
console.log(
|
||||
`🛡️ Neutralised ${wasmLiterals.length} \`.wasm\` reference(s) ` +
|
||||
`(${Object.keys(EXPECTED_WASM_REFS).join(", ")}) in ${outfile}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Runs the dynamic-eval and .wasm neutralisation on every build and rebuild, for
|
||||
// both dev and production, so the dev bundle matches what ships and the post-build
|
||||
// step is exercised continuously rather than only at release time. Registered last
|
||||
// so it transforms the finalised main.js.
|
||||
// Runs the dynamic-eval neutralisation on every build and rebuild, for both dev and
|
||||
// production, so the dev bundle matches what ships and the post-build step is
|
||||
// exercised continuously rather than only at release time. Registered last so it
|
||||
// transforms the finalised main.js.
|
||||
const dynamicEvalPlugin = {
|
||||
name: "neutralise-dynamic-eval",
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
neutraliseDynamicEval(build.initialOptions.outfile);
|
||||
neutraliseWasmRef(build.initialOptions.outfile);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -253,64 +167,8 @@ const cssMergerPlugin = {
|
|||
}
|
||||
};
|
||||
|
||||
// officeparser's browser bundle is an IIFE (var officeParser = (()=> { ... })()) that doesn't
|
||||
// set module.exports, so esbuild can't resolve its exports. This plugin intercepts the resolve
|
||||
// and appends a CJS export line so imports work correctly.
|
||||
//
|
||||
// The browser bundle contains a dynamic-require shim (from its own esbuild bundling) that our
|
||||
// esbuild would otherwise resolve against the `external` list, emitting require("fs") etc. in
|
||||
// the final output. On Obsidian mobile there is no "fs" module, so we replace that shim with a
|
||||
// stub that always throws, keeping the bundle self-contained.
|
||||
//
|
||||
// Every identifier in the shim is minified and churns between releases — not just the outer
|
||||
// variable name (Au in v5, Vm in v6, Gs in v7.1.0, dl in v7.2.1) but also the closure params
|
||||
// (r/t/e in v7.1.0 became n/e/t in v7.2.1). So the regex captures whatever identifiers appear
|
||||
// rather than hardcoding any of them. If the shim's *shape* changes such that nothing matches,
|
||||
// the build FAILS loudly — a silent no-op here previously let the stale v6 `Vm` regex match
|
||||
// nothing on v7, and the hardcoded v7.1.0 param names broke against v7.2.1.
|
||||
const officeParserPlugin = {
|
||||
name: "officeparser-cjs-shim",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^officeparser$/ }, () => ({
|
||||
path: join(process.cwd(), 'node_modules', 'officeparser', 'dist', 'officeparser.browser.iife.js'),
|
||||
namespace: 'officeparser-shim',
|
||||
}));
|
||||
build.onLoad({ filter: /.*/, namespace: 'officeparser-shim' }, async (args) => {
|
||||
let contents = readFileSync(args.path, 'utf-8');
|
||||
// Capture the (minified) outer var in group 1 and every other minified identifier in its
|
||||
// own group, reusing the captures via backreferences so we match regardless of which letters
|
||||
// esbuild chose this release. Group map: 1=outer var, 2=arrow param, 3/4=getter params,
|
||||
// 5=function param.
|
||||
const id = "[A-Za-z_$][\\w$]*";
|
||||
const shimPattern = new RegExp(
|
||||
`var (${id})=\\((${id})=>typeof require<"u"\\?require:typeof Proxy<"u"\\?` +
|
||||
`new Proxy\\(\\2,\\{get:\\((${id}),(${id})\\)=>\\(typeof require<"u"\\?require:\\3\\)` +
|
||||
`\\[\\4\\]\\}\\):\\2\\)\\(function\\((${id})\\)\\{if\\(typeof require<"u"\\)` +
|
||||
`return require\\.apply\\(this,arguments\\);` +
|
||||
`throw Error\\('Dynamic require of "'\\+\\5\\+'" is not supported'\\)\\}\\)`,
|
||||
);
|
||||
if (!shimPattern.test(contents)) {
|
||||
throw new Error(
|
||||
"officeparser-cjs-shim: dynamic-require shim not found in officeparser browser bundle. " +
|
||||
"officeparser's bundling likely changed shape — re-audit the IIFE and update shimPattern " +
|
||||
"before shipping, or require(\"fs\") may leak into the mobile bundle.",
|
||||
);
|
||||
}
|
||||
contents = contents.replace(
|
||||
shimPattern,
|
||||
'var $1=(function(r){throw Error(\'Dynamic require of "\'+r+\'" is not supported\')})',
|
||||
);
|
||||
return {
|
||||
contents: contents + '\nmodule.exports = officeParser;\n',
|
||||
loader: 'js',
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const buildOptions = {
|
||||
plugins: [
|
||||
officeParserPlugin,
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: "injected" },
|
||||
preprocess: sveltePreprocess(),
|
||||
|
|
|
|||
8
global.d.ts
vendored
8
global.d.ts
vendored
|
|
@ -12,3 +12,11 @@ declare module '*.png' {
|
|||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
// mammoth ships type declarations for its default entry but not for the browser
|
||||
// build we import (mammoth/mammoth.browser.js) for mobile safety. The browser build
|
||||
// exposes the same public API, so we re-use mammoth's own types.
|
||||
declare module 'mammoth/mammoth.browser.js' {
|
||||
import mammoth from 'mammoth';
|
||||
export = mammoth;
|
||||
}
|
||||
|
|
|
|||
763
package-lock.json
generated
763
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -52,15 +52,15 @@
|
|||
"diff": "^9.0.0",
|
||||
"diff2html": "^3.4.56",
|
||||
"express": "^5.2.1",
|
||||
"fflate": "^0.8.3",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.17.0",
|
||||
"lowlight": "^3.3.0",
|
||||
"officeparser": "^7.2.2",
|
||||
"mammoth": "^1.12.0",
|
||||
"openai": "^6.45.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"regex-parser": "^2.3.1",
|
||||
"unpdf": "^1.6.2",
|
||||
"uuid": "^14.0.1",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue