feat(import): allow importing .excalidraw files

This commit is contained in:
Elian Doran 2025-03-14 22:13:31 +02:00
parent ad8c1a4a29
commit d9764365cb
No known key found for this signature in database
6 changed files with 34 additions and 13 deletions

View File

@ -21,6 +21,11 @@ describe("#getMime", () => {
["test.ts"], "text/x-typescript"
],
[
"File extension ('.excalidraw') that is defined in EXTENSION_TO_MIME",
["test.excalidraw"], "application/json"
],
[
"File extension with inconsistent capitalization that is defined in EXTENSION_TO_MIME",
["test.gRoOvY"], "text/x-groovy"
@ -41,7 +46,7 @@ describe("#getMime", () => {
const [testDesc, fnParams, expected] = testCase;
it(`${testDesc}: '${fnParams} should return '${expected}'`, () => {
const actual = mimeService.getMime(...fnParams);
expect(actual).toEqual(expected);
expect(actual, testDesc).toEqual(expected);
});
});
});
@ -60,7 +65,7 @@ describe("#getType", () => {
],
[
"w/ import options set and an image mime type it should return 'image'",
"w/ import options set and an image mime type it should return 'image'",
[{}, "image/jpeg"], "image"
],
@ -75,22 +80,22 @@ describe("#getType", () => {
],
[
"w/ codeImportedAsCode: true and a mime type that is in CODE_MIME_TYPES it should return 'code'",
"w/ codeImportedAsCode: true and a mime type that is in CODE_MIME_TYPES it should return 'code'",
[{codeImportedAsCode: true}, "text/css"], "code"
],
[
"w/ codeImportedAsCode: false and a mime type that is in CODE_MIME_TYPES it should return 'file' not 'code'",
"w/ codeImportedAsCode: false and a mime type that is in CODE_MIME_TYPES it should return 'file' not 'code'",
[{codeImportedAsCode: false}, "text/css"], "file"
],
[
"w/ textImportedAsText: true and 'text/html' mime type it should return 'text'",
"w/ textImportedAsText: true and 'text/html' mime type it should return 'text'",
[{textImportedAsText: true}, "text/html"], "text"
],
[
"w/ textImportedAsText: true and 'text/markdown' mime type it should return 'text'",
"w/ textImportedAsText: true and 'text/markdown' mime type it should return 'text'",
[{textImportedAsText: true}, "text/markdown"], "text"
],
@ -125,23 +130,23 @@ describe("#normalizeMimeType", () => {
const testCases: TestCase<typeof mimeService.normalizeMimeType, string | undefined>[] = [
[
"empty mime should return undefined",
"empty mime should return undefined",
[""], undefined
],
[
"a mime that's defined in CODE_MIME_TYPES should return the same mime",
"a mime that's defined in CODE_MIME_TYPES should return the same mime",
["text/x-python"], "text/x-python"
],
[
"a mime (with capitalization inconsistencies) that's defined in CODE_MIME_TYPES should return the same mime in lowercase",
"a mime (with capitalization inconsistencies) that's defined in CODE_MIME_TYPES should return the same mime in lowercase",
["text/X-pYthOn"], "text/x-python"
],
[
"a mime that's non defined in CODE_MIME_TYPES should return undefined",
"a mime that's non defined in CODE_MIME_TYPES should return undefined",
["application/zip"], undefined
],
[
"a mime that's defined in CODE_MIME_TYPES with a 'rewrite rule' should return the rewritten mime",
"a mime that's defined in CODE_MIME_TYPES with a 'rewrite rule' should return the rewritten mime",
["text/markdown"], "text/x-markdown"
]
];

View File

@ -67,7 +67,8 @@ const EXTENSION_TO_MIME = new Map<string, string>([
[".rb", "text/x-ruby"],
[".scala", "text/x-scala"],
[".swift", "text/x-swift"],
[".ts", "text/x-typescript"]
[".ts", "text/x-typescript"],
[".excalidraw", "application/json"]
]);
/** @returns false if MIME is not detected */

View File

@ -0,0 +1 @@
{"type":"excalidraw","version":2,"elements":[],"files":{},"appState":{"scrollX":0,"scrollY":0,"zoom":{"value":1}}}

View File

@ -89,4 +89,11 @@ describe("processNoteContent", () => {
expect(importedNote.mime).toBe("text/html");
expect(importedNote.getContent().toString()).toBe("<h2>Hello world</h2>\n<p>Plain text goes here.</p>\n");
});
it("supports excalidraw note", async () => {
const { importedNote } = await testImport("New note.excalidraw", "application/json");
expect(importedNote.mime).toBe("application/json");
expect(importedNote.type).toBe("canvas");
expect(importedNote.title).toBe("New note");
});
});

View File

@ -12,6 +12,7 @@ import { getNoteTitle, processStringOrBuffer } from "../../services/utils.js";
import importUtils from "./utils.js";
import htmlSanitizer from "../html_sanitizer.js";
import type { File } from "./common.js";
import type { NoteType } from "../../becca/entities/rows.js";
function importSingleFile(taskContext: TaskContext, file: File, parentNote: BNote) {
const mime = mimeService.getMime(file.originalname) || file.mimetype;
@ -73,11 +74,16 @@ function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote)
const detectedMime = mimeService.getMime(file.originalname) || file.mimetype;
const mime = mimeService.normalizeMimeType(detectedMime);
let type: NoteType = "code";
if (file.originalname.endsWith(".excalidraw")) {
type = "canvas";
}
const { note } = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title,
content,
type: "code",
type,
mime: mime,
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
});

View File

@ -180,6 +180,7 @@ export function removeTextFileExtension(filePath: string) {
case ".markdown":
case ".html":
case ".htm":
case ".excalidraw":
return filePath.substring(0, filePath.length - extension.length);
default:
return filePath;