diff --git a/apps/server/src/services/import/markdown.spec.ts b/apps/server/src/services/import/markdown.spec.ts index 3aa67bd9d..3bb56580c 100644 --- a/apps/server/src/services/import/markdown.spec.ts +++ b/apps/server/src/services/import/markdown.spec.ts @@ -283,7 +283,7 @@ $$`; it("supports wikilink with root-relative path", () => { const input = `oh no my banana I bought on [[journal/monday]] has gone off! I’m taking it back to the [[other/shop]] for a refund`; - const expected = `

oh no my banana I bought on journal/monday has gone off! I’m taking it back to the other/shop for a refund

`; + const expected = `

oh no my banana I bought on journal/monday has gone off! I’m taking it back to the other/shop for a refund

`; expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected); }); diff --git a/apps/server/src/services/import/markdown.ts b/apps/server/src/services/import/markdown.ts index c945143fa..77b35eeba 100644 --- a/apps/server/src/services/import/markdown.ts +++ b/apps/server/src/services/import/markdown.ts @@ -215,7 +215,7 @@ function restoreFromMap(text: string, map: Map): string { } function processWikiLinks(paragraph: string) { - paragraph = paragraph.replaceAll(/\[\[([^\[\]]+)\]\]/g, `$1`); + paragraph = paragraph.replaceAll(/\[\[([^\[\]]+)\]\]/g, `$1`); return paragraph; } diff --git a/apps/server/src/services/import/zip.ts b/apps/server/src/services/import/zip.ts index 5e795ae54..1f3ed96cc 100644 --- a/apps/server/src/services/import/zip.ts +++ b/apps/server/src/services/import/zip.ts @@ -41,6 +41,7 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo const createdPaths: Record = { "/": importRootNote.noteId, "\\": importRootNote.noteId }; let metaFile: MetaFile | null = null; let firstNote: BNote | null = null; + let topLevelPath = ""; const createdNoteIds = new Set(); function getNewNoteId(origNoteId: string) { @@ -257,29 +258,33 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo saveAttributes(note, noteMeta); firstNote = firstNote || note; - return noteId; } function getEntityIdFromRelativeUrl(url: string, filePath: string) { - while (url.startsWith("./")) { - url = url.substr(2); + let absUrl; + if (!url.startsWith("/")) { + while (url.startsWith("./")) { + url = url.substr(2); + } + + let absUrl = path.dirname(filePath); + + while (url.startsWith("../")) { + absUrl = path.dirname(absUrl); + + url = url.substr(3); + } + + if (absUrl === ".") { + absUrl = ""; + } + + absUrl += `${absUrl.length > 0 ? "/" : ""}${url}`; + } else { + absUrl = topLevelPath + url; } - let absUrl = path.dirname(filePath); - - while (url.startsWith("../")) { - absUrl = path.dirname(absUrl); - - url = url.substr(3); - } - - if (absUrl === ".") { - absUrl = ""; - } - - absUrl += `${absUrl.length > 0 ? "/" : ""}${url}`; - const { noteMeta, attachmentMeta } = getMeta(absUrl); if (attachmentMeta && attachmentMeta.attachmentId && noteMeta.noteId) { @@ -527,20 +532,28 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo } } - // we're running two passes to make sure that the meta file is loaded before the rest of the files is processed. - + // we're running two passes in order to obtain critical information first (meta file and root) + const topLevelItems = new Set(); await readZipFile(fileBuffer, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => { const filePath = normalizeFilePath(entry.fileName); + // make sure that the meta file is loaded before the rest of the files is processed. if (filePath === "!!!meta.json") { const content = await readContent(zipfile, entry); metaFile = JSON.parse(content.toString("utf-8")); } + // determine the root of the .zip (i.e. if it has only one top-level folder then the root is that folder, or the root of the archive if there are multiple top-level folders). + const firstSlash = filePath.indexOf("/"); + const topLevelPath = (firstSlash !== -1 ? filePath.substring(0, firstSlash) : filePath); + topLevelItems.add(topLevelPath); + zipfile.readEntry(); }); + topLevelPath = (topLevelItems.size > 1 ? "" : topLevelItems.values().next().value ?? ""); + await readZipFile(fileBuffer, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => { const filePath = normalizeFilePath(entry.fileName);