diff --git a/packages/trilium-core/src/routes/api/import.ts b/packages/trilium-core/src/routes/api/import.ts
index 89b3018881..15ce84860a 100644
--- a/packages/trilium-core/src/routes/api/import.ts
+++ b/packages/trilium-core/src/routes/api/import.ts
@@ -5,7 +5,7 @@ type ImportRequest
= Omit, "file"> & { file?: File };
import becca from "../../becca/becca.js";
import type BNote from "../../becca/entities/bnote.js";
-// import enexImportService from "../../services/import/enex.js";
+import enexImportService from "../../services/import/enex.js";
import opmlImportService from "../../services/import/opml.js";
import singleImportService from "../../services/import/single.js";
import zipImportService from "../../services/import/zip.js";
@@ -62,18 +62,18 @@ async function importNotesToBranch(req: ImportRequest<{ parentNoteId: string }>)
return importResult;
}
} else if (extension === ".enex" && options.explodeArchives) {
- throw "ENEX import is currently not supported. Please use the desktop app to import ENEX files and then sync with the server.";
- // const importResult = await enexImportService.importEnex(taskContext, file, parentNote);
- // if (!Array.isArray(importResult)) {
- // note = importResult;
- // } else {
- // return importResult;
- // }
+ const importResult = await enexImportService.importEnex(taskContext, file, parentNote);
+ if (!Array.isArray(importResult)) {
+ note = importResult;
+ } else {
+ return importResult;
+ }
} else {
note = singleImportService.importSingleFile(taskContext, file, parentNote);
}
} catch (e: unknown) {
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
+ console.warn(e);
const message = `Import failed with following error: '${errMessage}'. More details might be in the logs.`;
taskContext.reportError(message);
diff --git a/packages/trilium-core/src/services/import/enex.ts b/packages/trilium-core/src/services/import/enex.ts
index 18ab4e888d..252a7dc119 100644
--- a/packages/trilium-core/src/services/import/enex.ts
+++ b/packages/trilium-core/src/services/import/enex.ts
@@ -1,8 +1,6 @@
import type { AttributeType } from "@triliumnext/commons";
import { dayjs } from "@triliumnext/commons";
import sax from "sax";
-import stream from "stream";
-import { Throttle } from "stream-throttle";
import type BNote from "../../becca/entities/bnote.js";
import date_utils from "../utils/date.js";
@@ -59,8 +57,8 @@ interface Note {
let note: Partial = {};
let resource: Resource;
-function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote): Promise {
- const saxStream = sax.createStream(true);
+function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote): BNote {
+ const parser = sax.parser(true);
const rootNoteTitle = file.originalname.toLowerCase().endsWith(".enex") ? file.originalname.substr(0, file.originalname.length - 5) : file.originalname;
@@ -138,15 +136,14 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
}
}
- saxStream.on("error", (e) => {
- // unhandled errors will throw, since this is a proper node event emitter.
+ parser.onerror = (e) => {
getLog().error(`error when parsing ENEX file: ${e}`);
- // clear the error
- (saxStream._parser as any).error = null;
- saxStream._parser.resume();
- });
+ // clear the error and resume
+ parser.error = null;
+ parser.resume();
+ };
- saxStream.on("text", (text) => {
+ parser.ontext = (text) => {
const currentTag = getCurrentTag();
const previousTag = getPreviousTag();
@@ -209,13 +206,9 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
}
// unknown tags are just ignored
}
- });
+ };
- saxStream.on("attribute", (attr) => {
- // an attribute. attr has "name" and "value"
- });
-
- saxStream.on("opentag", (tag) => {
+ parser.onopentag = (tag) => {
path.push(tag.name);
if (tag.name === "note") {
@@ -235,7 +228,7 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
note.resources.push(resource);
}
}
- });
+ };
const sql = getSql();
@@ -381,38 +374,22 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
updateDates(noteEntity, utcDateCreated, utcDateModified);
}
- saxStream.on("closetag", (tag) => {
+ parser.onclosetag = (tag) => {
path.pop();
if (tag === "note") {
saveNote();
}
- });
+ };
- saxStream.on("opencdata", () => {
- //console.log("opencdata");
- });
-
- saxStream.on("cdata", (text) => {
+ parser.oncdata = (text) => {
note.content += text;
- });
+ };
- saxStream.on("closecdata", () => {
- //console.log("closecdata");
- });
+ const content = typeof file.buffer === "string" ? file.buffer : new TextDecoder().decode(file.buffer);
+ parser.write(content).close();
- return new Promise((resolve, reject) => {
- // resolve only when we parse the whole document AND saving of all notes have been finished
- saxStream.on("end", () => resolve(rootNote));
-
- const bufferStream = new stream.PassThrough();
- bufferStream.end(file.buffer);
-
- bufferStream
- // rate limiting to improve responsiveness during / after import
- .pipe(new Throttle({ rate: 500000 }))
- .pipe(saxStream);
- });
+ return rootNote;
}
function formatDateTimeToLocalDbFormat(
diff --git a/packages/trilium-core/src/services/utils/index.ts b/packages/trilium-core/src/services/utils/index.ts
index 25785e2d1e..a1427becbf 100644
--- a/packages/trilium-core/src/services/utils/index.ts
+++ b/packages/trilium-core/src/services/utils/index.ts
@@ -199,7 +199,15 @@ export function randomSecureToken(bytes = 32) {
}
export function safeExtractMessageAndStackFromError(err: unknown): [errMessage: string, errStack: string | undefined] {
- return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const;
+ if (err instanceof Error) {
+ return [err.message, err.stack] as const;
+ }
+
+ if (typeof err === "string") {
+ return [err, undefined] as const;
+ }
+
+ return ["Unknown Error", undefined] as const;
}
export function isEmptyOrWhitespace(str: string | null | undefined) {