From 6835c9167d4bd1f2e5ee0b7c487a5b63cb5c7a4c Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Sun, 28 Jun 2026 15:08:50 +0100 Subject: [PATCH] refactor: replace mammoth with native DOCX parser using fflate Remove mammoth dependency and implement lightweight DOCX text extraction using fflate + DOMParser. Extracts text from body, headers/footers, and footnotes/endnotes by parsing w:p/w:t elements. Make readDocument() synchronous. Add PDF.js type definitions to eliminate unsafe any access. --- Helpers/DocumentHelper.ts | 72 +++++++++++-- README.md | 3 +- Services/InputService.ts | 2 +- Services/VaultService.ts | 4 +- esbuild.config.mjs | 9 +- global.d.ts | 10 +- package-lock.json | 210 -------------------------------------- package.json | 1 - 8 files changed, 68 insertions(+), 243 deletions(-) diff --git a/Helpers/DocumentHelper.ts b/Helpers/DocumentHelper.ts index 39e6bd1..17ed905 100644 --- a/Helpers/DocumentHelper.ts +++ b/Helpers/DocumentHelper.ts @@ -1,13 +1,30 @@ 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'; +// Minimal structural types for the slice of PDF.js we use. Obsidian's `loadPdfJs()` +// is typed as `Promise` and we don't bundle pdfjs-dist, so we describe just the +// shape we touch to keep this file free of unsafe-`any` access. +interface PdfTextItem { str?: string; } +interface PdfTextContent { items: PdfTextItem[]; } +interface PdfPage { + getTextContent(): Promise; + cleanup?(): void; +} +interface PdfDocument { + numPages: number; + getPage(pageNumber: number): Promise; + destroy?(): Promise; +} +interface PdfJs { + getDocument(src: { data: Uint8Array }): { promise: Promise }; +} + // Uses Obsidian's own bundled PDF.js via loadPdfJs() rather than shipping our own export async function readPDF(arrayBuffer: ArrayBuffer): Promise { try { - const pdfjs = await loadPdfJs(); + const pdfjs = (await loadPdfJs()) as PdfJs; const pdf = await pdfjs.getDocument({ data: new Uint8Array(arrayBuffer) }).promise; const pageTexts: IPageText[] = []; @@ -15,7 +32,7 @@ export async function readPDF(arrayBuffer: ArrayBuffer): Promise { const page = await pdf.getPage(i); const content = await page.getTextContent(); const text = content.items - .map((item: { str?: string }) => item.str ?? '') + .map((item) => item.str ?? '') .join(' '); pageTexts.push({ text, pageNumber: i }); page.cleanup?.(); @@ -41,16 +58,15 @@ export async function readPDF(arrayBuffer: ArrayBuffer): Promise { // 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 { +// Every format is a ZIP-of-XML container 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 function readDocument(arrayBuffer: ArrayBuffer, extension: string): IPageText[] { try { let text: string; switch (extension.toLowerCase()) { case 'docx': - text = (await mammoth.extractRawText({ arrayBuffer })).value; + text = extractDocx(arrayBuffer); break; case 'xlsx': text = extractXlsx(arrayBuffer); @@ -74,7 +90,7 @@ export async function readDocument(arrayBuffer: ArrayBuffer, extension: string): } } -// --- Shared helpers for the ZIP-of-XML formats (xlsx / pptx / odf) --- +// --- Shared helpers for the ZIP-of-XML formats (docx / 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. @@ -92,6 +108,40 @@ function ordinal(path: string): number { return match ? Number(match[1]) : 0; } +// --- DOCX: body in word/document.xml; = paragraph, = text run. +// Headers/footers and foot/endnotes live in sibling parts that share the same +// / shape, so they run through the same paragraph extractor. --- +function extractDocx(arrayBuffer: ArrayBuffer): string { + const files = unzip(arrayBuffer); + + // Joined text of every paragraph in a parsed part. Runs are joined with + // '' (not a space) because Word splits a single word across multiple runs + // for spell-check / formatting — a space here would shred words like "Hel"+"lo". + // Empty paragraphs (incl. the separator entries in foot/endnotes) are dropped. + const paragraphs = (bytes: Uint8Array): string => + Array.from(parseXml(bytes).getElementsByTagName('w:p')) + .map((p) => + Array.from(p.getElementsByTagName('w:t')) + .map((t) => t.textContent ?? '') + .join('') + ) + .filter((line) => line.length > 0) + .join('\n'); + + // Body first, then headers/footers (grouped + numerically ordered), then notes. + const headerFooter = Object.keys(files) + .filter((name) => /^word\/(header|footer)\d+\.xml$/.test(name)) + .sort(); + + const parts: string[] = []; + if (files['word/document.xml']) parts.push(paragraphs(files['word/document.xml'])); + for (const name of headerFooter) parts.push(paragraphs(files[name])); + if (files['word/footnotes.xml']) parts.push(paragraphs(files['word/footnotes.xml'])); + if (files['word/endnotes.xml']) parts.push(paragraphs(files['word/endnotes.xml'])); + + return parts.filter((p) => p.length > 0).join('\n\n'); +} + // --- XLSX: values live as indices into a shared-strings table --- function extractXlsx(arrayBuffer: ArrayBuffer): string { const files = unzip(arrayBuffer); @@ -172,4 +222,4 @@ function extractOdf(arrayBuffer: ArrayBuffer): string { } } return out.join('\n'); -} +} \ No newline at end of file diff --git a/README.md b/README.md index 96751b9..e247cbf 100644 --- a/README.md +++ b/README.md @@ -410,8 +410,7 @@ 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** -- [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) +- [fflate](https://github.com/101arrowz/fflate) - Unzipping Office Open XML / OpenDocument files for text extraction (DOCX, PPTX, XLSX, ODT, ODP, ODS) - PDF text extraction via Obsidian's bundled [PDF.js](https://mozilla.github.io/pdf.js/) (`loadPdfJs()`) **UI Framework** diff --git a/Services/InputService.ts b/Services/InputService.ts index c6557d7..9da21d3 100644 --- a/Services/InputService.ts +++ b/Services/InputService.ts @@ -48,7 +48,7 @@ export class InputService { } if (isDocumentFile(fileType)) { - const content = await readDocument(await file.arrayBuffer(), fileType); + const content = readDocument(await file.arrayBuffer(), fileType); attachments.push(new Attachment( file.name, MimeType.TEXT_PLAIN, diff --git a/Services/VaultService.ts b/Services/VaultService.ts index dcf87dd..d311543 100644 --- a/Services/VaultService.ts +++ b/Services/VaultService.ts @@ -98,7 +98,7 @@ export class VaultService { if (isDocumentFile(fileExtension)) { const arrayBuffer = await this.readBinaryData(file, allowAccessToPluginRoot); if (arrayBuffer) { - return (await readDocument(arrayBuffer, fileExtension))[0].text; + return (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, fileExtension); + content = readDocument(arrayBuffer, fileExtension); } else { content = [{ text: await this.vault.cachedRead(file), pageNumber: 1 }] as IPageText[]; } diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 343f244..c8b16d4 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -43,11 +43,6 @@ function copyDir(src, dest) { // // 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. @@ -57,8 +52,8 @@ function copyDir(src, dest) { // 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; +// 2 = Hogan. +const EXPECTED_NEW_FUNCTION_COUNT = 2; const NEW_FUNCTION_STUB = "VKBlockedDynamicFn"; function neutraliseDynamicEval(outfile) { if (!existsSync(outfile)) return; diff --git a/global.d.ts b/global.d.ts index be39c8f..5ba61a0 100644 --- a/global.d.ts +++ b/global.d.ts @@ -11,12 +11,4 @@ declare module '*.svg' { 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; -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 213bf40..f842b02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "highlight.js": "^11.11.1", "katex": "^0.17.0", "lowlight": "^3.3.0", - "mammoth": "^1.12.0", "openai": "^6.45.0", "path-browserify": "^1.0.1", "regex-parser": "^2.3.1", @@ -2299,15 +2298,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.13", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", - "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -2671,12 +2661,6 @@ "node": "*" } }, - "node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "license": "MIT" - }, "node_modules/body-parser": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", @@ -3016,12 +3000,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, "node_modules/crelt": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", @@ -3250,12 +3228,6 @@ "node": ">=0.3.1" } }, - "node_modules/dingbat-to-unicode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", - "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", - "license": "BSD-2-Clause" - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3276,15 +3248,6 @@ "dev": true, "license": "MIT" }, - "node_modules/duck": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", - "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", - "license": "BSD", - "dependencies": { - "underscore": "^1.13.1" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5390,12 +5353,6 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -6084,18 +6041,6 @@ "node": ">=4.0" } }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -6157,15 +6102,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -6488,17 +6424,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lop": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", - "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", - "license": "BSD-2-Clause", - "dependencies": { - "duck": "^0.1.12", - "option": "~0.2.1", - "underscore": "^1.13.1" - } - }, "node_modules/lowlight": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", @@ -6534,39 +6459,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/mammoth": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.12.0.tgz", - "integrity": "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w==", - "license": "BSD-2-Clause", - "dependencies": { - "@xmldom/xmldom": "^0.8.6", - "argparse": "~1.0.3", - "base64-js": "^1.5.1", - "bluebird": "~3.4.0", - "dingbat-to-unicode": "^1.0.1", - "jszip": "^3.7.1", - "lop": "^0.4.2", - "path-is-absolute": "^1.0.0", - "underscore": "^1.13.1", - "xmlbuilder": "^10.0.0" - }, - "bin": { - "mammoth": "bin/mammoth" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/mammoth/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7137,12 +7029,6 @@ } } }, - "node_modules/option": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", - "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==", - "license": "BSD-2-Clause" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7224,12 +7110,6 @@ "node": ">=8" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7268,15 +7148,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -7395,12 +7266,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7527,33 +7392,6 @@ "dev": true, "license": "MIT" }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -7986,12 +7824,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -8154,12 +7986,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -8207,21 +8033,6 @@ "node": ">= 0.4" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -8872,12 +8683,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/underscore": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", - "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", - "license": "MIT" - }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", @@ -8990,12 +8795,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/uuid": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", @@ -9400,15 +9199,6 @@ } } }, - "node_modules/xmlbuilder": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", - "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", diff --git a/package.json b/package.json index 9c01213..2163286 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "highlight.js": "^11.11.1", "katex": "^0.17.0", "lowlight": "^3.3.0", - "mammoth": "^1.12.0", "openai": "^6.45.0", "path-browserify": "^1.0.1", "regex-parser": "^2.3.1",