From fd4f35e8791ff6681a790efc2b6c4cd3d7094663 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Feb 2025 00:50:19 +0200 Subject: [PATCH 1/5] feat(import/single): support UTF-16 LE with BOM for HTML --- package-lock.json | 30 ++++++-- package.json | 10 +-- .../samples/IREN Reports Q2 FY25 Results.htm | Bin 0 -> 693586 bytes src/services/import/single.spec.ts | 71 ++++++++++-------- src/services/import/single.ts | 19 ++++- 5 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 src/services/import/samples/IREN Reports Q2 FY25 Results.htm diff --git a/package-lock.json b/package-lock.json index 894c82f64..8eb6db485 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "better-sqlite3": "11.8.1", "bootstrap": "5.3.3", "boxicons": "2.1.4", + "chardet": "2.0.0", "cheerio": "1.0.0", "chokidar": "4.0.3", "cls-hooked": "4.2.2", @@ -97,6 +98,7 @@ "source-map-support": "0.5.21", "split.js": "1.6.5", "stream-throttle": "0.1.3", + "strip-bom": "5.0.0", "striptags": "3.2.0", "swagger-ui-express": "5.0.1", "tmp": "0.2.3", @@ -6175,6 +6177,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chardet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz", + "integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==", + "license": "MIT" + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -11889,6 +11897,16 @@ "node": ">=4" } }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -15917,13 +15935,15 @@ } }, "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz", + "integrity": "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-eof": { diff --git a/package.json b/package.json index e14d45979..401f3d5ad 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "server:start-test": "npm run server:switch && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts", "server:qstart": "npm run server:switch && npm run server:start", "server:switch": "rimraf ./node_modules/better-sqlite3 && npm install", - "electron:start": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./electron-main.ts --inspect=5858 .", "electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev electron --inspect=5858 .", "electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"", @@ -37,30 +36,23 @@ "electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"", "electron:qstart": "npm run electron:switch && npm run electron:start", "electron:switch": "electron-rebuild", - "electron-forge:start": "npm run build:prepare-dist && electron-forge start", "electron-forge:make": "npm run build:prepare-dist && electron-forge make", "electron-forge:package": "npm run build:prepare-dist && electron-forge package", - "docs:build-backend": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts", "docs:build-frontend": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js", "docs:build": "npm run docs:build-backend && npm run docs:build-frontend", - "build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts", "build:prepare-dist": "npm run build:webpack && rimraf ./dist && tsc && tsx ./bin/copy-dist.ts", - "test": "cross-env TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest", "test:coverage": "cross-env TRILIUM_DATA_DIR=./integration-tests/db vitest --coverage", "test:playwright": "playwright test", - "test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "test:integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", - "dev:watch-dist": "tsx ./bin/watch-dist.ts", "dev:prettier-check": "prettier . --check", "dev:prettier-fix": "prettier . --write", - "chore:update-build-info": "tsx bin/update-build-info.ts", "chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts", "chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000", @@ -89,6 +81,7 @@ "better-sqlite3": "11.8.1", "bootstrap": "5.3.3", "boxicons": "2.1.4", + "chardet": "2.0.0", "cheerio": "1.0.0", "chokidar": "4.0.3", "cls-hooked": "4.2.2", @@ -155,6 +148,7 @@ "source-map-support": "0.5.21", "split.js": "1.6.5", "stream-throttle": "0.1.3", + "strip-bom": "5.0.0", "striptags": "3.2.0", "swagger-ui-express": "5.0.1", "tmp": "0.2.3", diff --git a/src/services/import/samples/IREN Reports Q2 FY25 Results.htm b/src/services/import/samples/IREN Reports Q2 FY25 Results.htm new file mode 100644 index 0000000000000000000000000000000000000000..361ceb340c4e2520d6d5be64b239de901ad29ef0 GIT binary patch literal 693586 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 literal 0 HcmV?d00001 diff --git a/src/services/import/single.spec.ts b/src/services/import/single.spec.ts index 716eb7b91..e650c8e98 100644 --- a/src/services/import/single.spec.ts +++ b/src/services/import/single.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { beforeAll, describe, expect, it } from "vitest"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; @@ -12,38 +12,47 @@ import { initializeTranslations } from "../i18n.js"; import single from "./single.js"; const scriptDir = dirname(fileURLToPath(import.meta.url)); -describe("processNoteContent", () => { - it("treats single MDX as Markdown", async () => { - const mdxSample = fs.readFileSync(path.join(scriptDir, "samples", "Text Note.mdx")); - const taskContext = TaskContext.getInstance("import-mdx", "import", { - textImportedAsText: true - }); +async function testImport(fileName: string, mimetype: string): Promise { + const mdxSample = fs.readFileSync(path.join(scriptDir, "samples", fileName)); + const taskContext = TaskContext.getInstance("import-mdx", "import", { + textImportedAsText: true + }); - await new Promise((resolve, reject) => { - cls.init(async () => { - initializeTranslations(); - sql_init.initializeDb(); - await sql_init.dbReady; + return new Promise((resolve, reject) => { + cls.init(async () => { + const rootNote = becca.getNote("root"); + if (!rootNote) { + reject("Missing root note."); + } - const rootNote = becca.getNote("root"); - if (!rootNote) { - reject("Missing root note."); - } - - const importedNote = single.importSingleFile(taskContext, { - originalname: "Text Note.mdx", - mimetype: "text/mdx", - buffer: mdxSample - }, rootNote as BNote); - try { - expect(importedNote.mime).toBe("text/html"); - expect(importedNote.type).toBe("text"); - expect(importedNote.title).toBe("Text Note"); - } catch (e) { - reject(e); - } - resolve(); - }); + const importedNote = single.importSingleFile(taskContext, { + originalname: fileName, + mimetype, + buffer: mdxSample + }, rootNote as BNote); + resolve(importedNote); }); }); +} + +describe("processNoteContent", () => { + beforeAll(async () => { + initializeTranslations(); + sql_init.initializeDb(); + await sql_init.dbReady; + }); + + it("treats single MDX as Markdown", async () => { + const importedNote = await testImport("Text Note.mdx", "text/mdx"); + expect(importedNote.mime).toBe("text/html"); + expect(importedNote.type).toBe("text"); + expect(importedNote.title).toBe("Text Note"); + }); + + it("supports HTML note with UTF-16 (w/ BOM) from Microsoft Outlook", async () => { + const importedNote = await testImport("IREN Reports Q2 FY25 Results.htm", "text/html"); + expect(importedNote.mime).toBe("text/html"); + expect(importedNote.title).toBe("IREN Reports Q2 FY25 Results"); + expect(importedNote.getContent().toString().substring(0, 5)).toEqual("s into

From cadd78524c2188320c99ed9420e1d5fbe412b63c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Feb 2025 01:01:15 +0200 Subject: [PATCH 2/5] feat(import/single): support UTF-16 LE with BOM for code notes --- .../import/samples/UTF-16LE Code Note.json | Bin 0 -> 50 bytes src/services/import/single.spec.ts | 27 ++++++++++++------ src/services/import/single.ts | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 src/services/import/samples/UTF-16LE Code Note.json diff --git a/src/services/import/samples/UTF-16LE Code Note.json b/src/services/import/samples/UTF-16LE Code Note.json new file mode 100644 index 0000000000000000000000000000000000000000..a9d06ee69761ef2fd6f752ba345bc3dd19329a1a GIT binary patch literal 50 tcmezWubP32K>-St7(5tK8FGLypFxSi3Mi_?P!41l0Yy@PY%Ydc1_1Zq2#5dx literal 0 HcmV?d00001 diff --git a/src/services/import/single.spec.ts b/src/services/import/single.spec.ts index e650c8e98..f4e0cff11 100644 --- a/src/services/import/single.spec.ts +++ b/src/services/import/single.spec.ts @@ -10,15 +10,17 @@ import cls from "../cls.js"; import sql_init from "../sql_init.js"; import { initializeTranslations } from "../i18n.js"; import single from "./single.js"; +import stripBom from "strip-bom"; const scriptDir = dirname(fileURLToPath(import.meta.url)); -async function testImport(fileName: string, mimetype: string): Promise { - const mdxSample = fs.readFileSync(path.join(scriptDir, "samples", fileName)); +async function testImport(fileName: string, mimetype: string) { + const buffer = fs.readFileSync(path.join(scriptDir, "samples", fileName)); const taskContext = TaskContext.getInstance("import-mdx", "import", { - textImportedAsText: true + textImportedAsText: true, + codeImportedAsCode: true }); - return new Promise((resolve, reject) => { + return new Promise<{ buffer: Buffer, importedNote: BNote }>((resolve, reject) => { cls.init(async () => { const rootNote = becca.getNote("root"); if (!rootNote) { @@ -28,9 +30,12 @@ async function testImport(fileName: string, mimetype: string): Promise { const importedNote = single.importSingleFile(taskContext, { originalname: fileName, mimetype, - buffer: mdxSample + buffer: buffer }, rootNote as BNote); - resolve(importedNote); + resolve({ + buffer, + importedNote + }); }); }); } @@ -43,16 +48,22 @@ describe("processNoteContent", () => { }); it("treats single MDX as Markdown", async () => { - const importedNote = await testImport("Text Note.mdx", "text/mdx"); + const { importedNote } = await testImport("Text Note.mdx", "text/mdx"); expect(importedNote.mime).toBe("text/html"); expect(importedNote.type).toBe("text"); expect(importedNote.title).toBe("Text Note"); }); it("supports HTML note with UTF-16 (w/ BOM) from Microsoft Outlook", async () => { - const importedNote = await testImport("IREN Reports Q2 FY25 Results.htm", "text/html"); + const { importedNote } = await testImport("IREN Reports Q2 FY25 Results.htm", "text/html"); expect(importedNote.mime).toBe("text/html"); expect(importedNote.title).toBe("IREN Reports Q2 FY25 Results"); expect(importedNote.getContent().toString().substring(0, 5)).toEqual(" { + const { importedNote, buffer } = await testImport("UTF-16LE Code Note.json", "application/json"); + expect(importedNote.mime).toBe("application/json"); + expect(importedNote.getContent().toString()).toStrictEqual(stripBom(buffer.toString("utf-16le"))); + }); }) diff --git a/src/services/import/single.ts b/src/services/import/single.ts index e4ff7a068..f9b96cfcd 100644 --- a/src/services/import/single.ts +++ b/src/services/import/single.ts @@ -71,7 +71,7 @@ function importFile(taskContext: TaskContext, file: File, parentNote: BNote) { function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote) { const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); - const content = file.buffer.toString("utf-8"); + const content = processStringOrBuffer(file.buffer); const detectedMime = mimeService.getMime(file.originalname) || file.mimetype; const mime = mimeService.normalizeMimeType(detectedMime); From 77ee7f96c1f8f976a2ed9b163d6158d26b380fa5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Feb 2025 01:06:25 +0200 Subject: [PATCH 3/5] feat(import/single): support UTF-16 LE with BOM for text notes --- src/services/import/samples/UTF-16LE Text Note.txt | Bin 0 -> 46 bytes src/services/import/single.spec.ts | 6 ++++++ src/services/import/single.ts | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/services/import/samples/UTF-16LE Text Note.txt diff --git a/src/services/import/samples/UTF-16LE Text Note.txt b/src/services/import/samples/UTF-16LE Text Note.txt new file mode 100644 index 0000000000000000000000000000000000000000..c76e1ddd765fdb79d94c6fd718927a5a352d4607 GIT binary patch literal 46 ucmezWFMuJ3A(0`IA&)_Up@boop#n%MFr+i&1KGtuRtAtR0%AP|E(QP-{RyD} literal 0 HcmV?d00001 diff --git a/src/services/import/single.spec.ts b/src/services/import/single.spec.ts index f4e0cff11..6e35ebe1a 100644 --- a/src/services/import/single.spec.ts +++ b/src/services/import/single.spec.ts @@ -66,4 +66,10 @@ describe("processNoteContent", () => { expect(importedNote.mime).toBe("application/json"); expect(importedNote.getContent().toString()).toStrictEqual(stripBom(buffer.toString("utf-16le"))); }); + + it("supports plain text note with UTF-16", async () => { + const { importedNote } = await testImport("UTF-16LE Text Note.txt", "text/plain"); + expect(importedNote.mime).toBe("text/html"); + expect(importedNote.getContent().toString()).toBe("

Plain text goes here.

"); + }); }) diff --git a/src/services/import/single.ts b/src/services/import/single.ts index f9b96cfcd..f55c84bfc 100644 --- a/src/services/import/single.ts +++ b/src/services/import/single.ts @@ -91,7 +91,7 @@ function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote) function importPlainText(taskContext: TaskContext, file: File, parentNote: BNote) { const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); - const plainTextContent = file.buffer.toString("utf-8"); + const plainTextContent = processStringOrBuffer(file.buffer); const htmlContent = convertTextToHtml(plainTextContent); const { note } = noteService.createNewNote({ From c925ae5f15fb37a9e83d1af5cc34c24fd49a6b79 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Feb 2025 01:09:24 +0200 Subject: [PATCH 4/5] feat(import/single): support UTF-16 LE with BOM for markdown notes --- src/services/import/samples/UTF-16LE Text Note.md | Bin 0 -> 76 bytes src/services/import/single.spec.ts | 6 ++++++ src/services/import/single.ts | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/services/import/samples/UTF-16LE Text Note.md diff --git a/src/services/import/samples/UTF-16LE Text Note.md b/src/services/import/samples/UTF-16LE Text Note.md new file mode 100644 index 0000000000000000000000000000000000000000..a0fec98d61b864cf262f553a82ac404bcb7c76f8 GIT binary patch literal 76 zcmezWPnki1!Gj@{AqNQaf$VZ1Sp;OKFmM530Faf)kjan-lqmtKssNG-4Cz2MsSL$H NRtAtR0%AR&S^#4j4m1D& literal 0 HcmV?d00001 diff --git a/src/services/import/single.spec.ts b/src/services/import/single.spec.ts index 6e35ebe1a..74b4746ab 100644 --- a/src/services/import/single.spec.ts +++ b/src/services/import/single.spec.ts @@ -72,4 +72,10 @@ describe("processNoteContent", () => { expect(importedNote.mime).toBe("text/html"); expect(importedNote.getContent().toString()).toBe("

Plain text goes here.

"); }); + + it("supports markdown note with UTF-16", async () => { + const { importedNote } = await testImport("UTF-16LE Text Note.md", "text/markdown"); + expect(importedNote.mime).toBe("text/html"); + expect(importedNote.getContent().toString()).toBe("

Hello world

\n

Plain text goes here.

\n"); + }); }) diff --git a/src/services/import/single.ts b/src/services/import/single.ts index f55c84bfc..85bd3d48a 100644 --- a/src/services/import/single.ts +++ b/src/services/import/single.ts @@ -127,7 +127,7 @@ function convertTextToHtml(text: string) { function importMarkdown(taskContext: TaskContext, file: File, parentNote: BNote) { const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); - const markdownContent = file.buffer.toString("utf-8"); + const markdownContent = processStringOrBuffer(file.buffer); let htmlContent = markdownService.renderToHtml(markdownContent, title); if (taskContext.data?.safeImport) { From bedc61c3d0346c62bb72605a5456503205a820ce Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Feb 2025 01:37:02 +0200 Subject: [PATCH 5/5] feat(import/zip): support UTF-16 LE with BOM (closes #1241) --- .../IREN.Reports.Q2.FY25.Results_files.zip | Bin 0 -> 22428 bytes src/services/import/single.ts | 19 +---- src/services/import/zip.spec.ts | 65 ++++++++++-------- src/services/import/zip.ts | 4 +- src/services/utils.ts | 32 +++++++++ 5 files changed, 73 insertions(+), 47 deletions(-) create mode 100644 src/services/import/samples/IREN.Reports.Q2.FY25.Results_files.zip diff --git a/src/services/import/samples/IREN.Reports.Q2.FY25.Results_files.zip b/src/services/import/samples/IREN.Reports.Q2.FY25.Results_files.zip new file mode 100644 index 0000000000000000000000000000000000000000..86c9de9b5c89968bf36494767960305f95f39570 GIT binary patch literal 22428 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 literal 0 HcmV?d00001 diff --git a/src/services/import/single.ts b/src/services/import/single.ts index 85bd3d48a..b572aea7f 100644 --- a/src/services/import/single.ts +++ b/src/services/import/single.ts @@ -3,14 +3,12 @@ import type BNote from "../../becca/entities/bnote.js"; import type TaskContext from "../task_context.js"; -import chardet from "chardet"; -import stripBom from "strip-bom"; import noteService from "../../services/notes.js"; import imageService from "../../services/image.js"; import protectedSessionService from "../protected_session.js"; import markdownService from "./markdown.js"; import mimeService from "./mime.js"; -import { getNoteTitle } from "../../services/utils.js"; +import { getNoteTitle, processStringOrBuffer } from "../../services/utils.js"; import importUtils from "./utils.js"; import htmlSanitizer from "../html_sanitizer.js"; import type { File } from "./common.js"; @@ -148,21 +146,6 @@ function importMarkdown(taskContext: TaskContext, file: File, parentNote: BNote) return note; } -function processStringOrBuffer(data: string | Buffer) { - if (!Buffer.isBuffer(data)) { - return data; - } - - const detectedEncoding = chardet.detect(data); - switch (detectedEncoding) { - case "UTF-16LE": - return stripBom(data.toString("utf-16le")); - case "UTF-8": - default: - return data.toString("utf-8"); - } -} - function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) { let content = processStringOrBuffer(file.buffer); diff --git a/src/services/import/zip.spec.ts b/src/services/import/zip.spec.ts index c29459f93..67f0175f3 100644 --- a/src/services/import/zip.spec.ts +++ b/src/services/import/zip.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { beforeAll, describe, expect, it } from "vitest"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; @@ -12,35 +12,46 @@ import sql_init from "../sql_init.js"; import { initializeTranslations } from "../i18n.js"; const scriptDir = dirname(fileURLToPath(import.meta.url)); -describe("processNoteContent", () => { - it("treats single MDX as Markdown in ZIP as text note", async () => { - const mdxSample = fs.readFileSync(path.join(scriptDir, "samples", "mdx.zip")); - const taskContext = TaskContext.getInstance("import-mdx", "import", { - textImportedAsText: true - }); +async function testImport(fileName: string) { + const mdxSample = fs.readFileSync(path.join(scriptDir, "samples", fileName)); + const taskContext = TaskContext.getInstance("import-mdx", "import", { + textImportedAsText: true + }); - await new Promise((resolve, reject) => { - cls.init(async () => { - initializeTranslations(); - sql_init.initializeDb(); - await sql_init.dbReady; + return new Promise<{ importedNote: BNote; rootNote: BNote }>((resolve, reject) => { + cls.init(async () => { + const rootNote = becca.getNote("root"); + if (!rootNote) { + expect(rootNote).toBeTruthy(); + return; + } - const rootNote = becca.getNote("root"); - if (!rootNote) { - expect(rootNote).toBeTruthy(); - return; - } - - const importedNote = await zip.importZip(taskContext, mdxSample, rootNote as BNote); - try { - expect(importedNote.mime).toBe("text/mdx"); - expect(importedNote.type).toBe("text"); - expect(importedNote.title).toBe("Text Note"); - } catch (e) { - reject(e); - } - resolve(); + const importedNote = await zip.importZip(taskContext, mdxSample, rootNote as BNote); + resolve({ + importedNote, + rootNote }); }); }); +} + +describe("processNoteContent", () => { + beforeAll(async () => { + initializeTranslations(); + sql_init.initializeDb(); + await sql_init.dbReady; + }); + + it("treats single MDX as Markdown in ZIP as text note", async () => { + const { importedNote } = await testImport("mdx.zip"); + expect(importedNote.mime).toBe("text/mdx"); + expect(importedNote.type).toBe("text"); + expect(importedNote.title).toBe("Text Note"); + }); + + it("can import email from Microsoft Outlook with UTF-16 with BOM", async () => { + const { rootNote, importedNote } = await testImport("IREN.Reports.Q2.FY25.Results_files.zip"); + const htmlNote = rootNote.children.find((ch) => ch.title === "IREN Reports Q2 FY25 Results"); + expect(htmlNote?.getContent().toString().substring(0, 4)).toEqual("