mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	Merge branch 'develop' into feature/MFA
This commit is contained in:
		
						commit
						f42ecb2e83
					
				@ -343,9 +343,8 @@ type EventMappings = {
 | 
			
		||||
    noteContextRemoved: {
 | 
			
		||||
        ntxIds: string[];
 | 
			
		||||
    };
 | 
			
		||||
    exportSvg: {
 | 
			
		||||
        ntxId: string | null | undefined;
 | 
			
		||||
    };
 | 
			
		||||
    exportSvg: { ntxId: string | null | undefined; };
 | 
			
		||||
    exportPng: { ntxId: string | null | undefined; };
 | 
			
		||||
    geoMapCreateChildNote: {
 | 
			
		||||
        ntxId: string | null | undefined; // TODO: deduplicate ntxId
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -91,6 +91,7 @@ import type { AppContext } from "./../components/app_context.js";
 | 
			
		||||
import type { WidgetsByParent } from "../services/bundle.js";
 | 
			
		||||
import SwitchSplitOrientationButton from "../widgets/floating_buttons/switch_layout_button.js";
 | 
			
		||||
import ToggleReadOnlyButton from "../widgets/floating_buttons/toggle_read_only_button.js";
 | 
			
		||||
import PngExportButton from "../widgets/floating_buttons/png_export_button.js";
 | 
			
		||||
 | 
			
		||||
export default class DesktopLayout {
 | 
			
		||||
 | 
			
		||||
@ -214,6 +215,7 @@ export default class DesktopLayout {
 | 
			
		||||
                                                                .child(new GeoMapButtons())
 | 
			
		||||
                                                                .child(new CopyImageReferenceButton())
 | 
			
		||||
                                                                .child(new SvgExportButton())
 | 
			
		||||
                                                                .child(new PngExportButton())
 | 
			
		||||
                                                                .child(new BacklinksWidget())
 | 
			
		||||
                                                                .child(new ContextualHelpButton())
 | 
			
		||||
                                                                .child(new HideFloatingButtonsButton())
 | 
			
		||||
 | 
			
		||||
@ -609,9 +609,20 @@ function createImageSrcUrl(note: { noteId: string; title: string }) {
 | 
			
		||||
 */
 | 
			
		||||
function downloadSvg(nameWithoutExtension: string, svgContent: string) {
 | 
			
		||||
    const filename = `${nameWithoutExtension}.svg`;
 | 
			
		||||
    const dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
 | 
			
		||||
    triggerDownload(filename, dataUrl);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Downloads the given data URL on the client device, with a custom file name.
 | 
			
		||||
 *
 | 
			
		||||
 * @param fileName the name to give the downloaded file.
 | 
			
		||||
 * @param dataUrl the data URI to download.
 | 
			
		||||
 */
 | 
			
		||||
function triggerDownload(fileName: string, dataUrl: string) {
 | 
			
		||||
    const element = document.createElement("a");
 | 
			
		||||
    element.setAttribute("href", `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`);
 | 
			
		||||
    element.setAttribute("download", filename);
 | 
			
		||||
    element.setAttribute("href", dataUrl);
 | 
			
		||||
    element.setAttribute("download", fileName);
 | 
			
		||||
 | 
			
		||||
    element.style.display = "none";
 | 
			
		||||
    document.body.appendChild(element);
 | 
			
		||||
@ -621,6 +632,56 @@ function downloadSvg(nameWithoutExtension: string, svgContent: string) {
 | 
			
		||||
    document.body.removeChild(element);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Given a string representation of an SVG, renders the SVG to PNG and triggers a download of the file on the client device.
 | 
			
		||||
 *
 | 
			
		||||
 * Note that the SVG must specify its width and height as attributes in order for it to be rendered.
 | 
			
		||||
 *
 | 
			
		||||
 * @param nameWithoutExtension the name of the file. The .png suffix is automatically added to it.
 | 
			
		||||
 * @param svgContent the content of the SVG file download.
 | 
			
		||||
 * @returns `true` if the operation succeeded (width/height present), or `false` if the download was not triggered.
 | 
			
		||||
 */
 | 
			
		||||
function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) {
 | 
			
		||||
    const mime = "image/svg+xml";
 | 
			
		||||
 | 
			
		||||
    // First, we need to determine the width and the height from the input SVG.
 | 
			
		||||
    const svgDocument = (new DOMParser()).parseFromString(svgContent, mime);
 | 
			
		||||
    const width = svgDocument.documentElement?.getAttribute("width");
 | 
			
		||||
    const height = svgDocument.documentElement?.getAttribute("height");
 | 
			
		||||
 | 
			
		||||
    if (!width || !height) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Convert the image to a blob.
 | 
			
		||||
    const svgBlob = new Blob([ svgContent ], {
 | 
			
		||||
        type: mime
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // Create an image element and load the SVG.
 | 
			
		||||
    const imageEl = new Image();
 | 
			
		||||
    imageEl.width = parseFloat(width);
 | 
			
		||||
    imageEl.height = parseFloat(height);
 | 
			
		||||
    imageEl.src = URL.createObjectURL(svgBlob);
 | 
			
		||||
    imageEl.onload = () => {
 | 
			
		||||
        // Draw the image with a canvas.
 | 
			
		||||
        const canvasEl = document.createElement("canvas");
 | 
			
		||||
        canvasEl.width = imageEl.width;
 | 
			
		||||
        canvasEl.height = imageEl.height;
 | 
			
		||||
        document.body.appendChild(canvasEl);
 | 
			
		||||
 | 
			
		||||
        const ctx = canvasEl.getContext("2d");
 | 
			
		||||
        ctx?.drawImage(imageEl, 0, 0);
 | 
			
		||||
        URL.revokeObjectURL(imageEl.src);
 | 
			
		||||
 | 
			
		||||
        const imgUri = canvasEl.toDataURL("image/png")
 | 
			
		||||
        triggerDownload(`${nameWithoutExtension}.png`, imgUri);
 | 
			
		||||
        document.body.removeChild(canvasEl);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Compares two semantic version strings.
 | 
			
		||||
 * Returns:
 | 
			
		||||
@ -719,6 +780,7 @@ export default {
 | 
			
		||||
    copyHtmlToClipboard,
 | 
			
		||||
    createImageSrcUrl,
 | 
			
		||||
    downloadSvg,
 | 
			
		||||
    downloadSvgAsPng,
 | 
			
		||||
    compareVersions,
 | 
			
		||||
    isUpdateAvailable,
 | 
			
		||||
    isLaunchBarConfig
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								src/public/app/widgets/floating_buttons/png_export_button.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/public/app/widgets/floating_buttons/png_export_button.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
import { t } from "../../services/i18n.js";
 | 
			
		||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
 | 
			
		||||
 | 
			
		||||
const TPL = `
 | 
			
		||||
<button type="button"
 | 
			
		||||
        class="export-svg-button"
 | 
			
		||||
        title="${t("png_export_button.button_title")}">
 | 
			
		||||
        <span class="bx bxs-file-png"></span>
 | 
			
		||||
</button>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export default class PngExportButton extends NoteContextAwareWidget {
 | 
			
		||||
    isEnabled() {
 | 
			
		||||
        return super.isEnabled() && ["mermaid"].includes(this.note?.type ?? "") && this.note?.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    doRender() {
 | 
			
		||||
        super.doRender();
 | 
			
		||||
 | 
			
		||||
        this.$widget = $(TPL);
 | 
			
		||||
        this.$widget.on("click", () => this.triggerEvent("exportPng", { ntxId: this.ntxId }));
 | 
			
		||||
        this.contentSized();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -40,6 +40,10 @@ const TPL = `\
 | 
			
		||||
            flex-grow: 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .note-detail-split .note-detail-split-editor .note-detail-code {
 | 
			
		||||
            contain: size !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .note-detail-split .note-detail-error-container {
 | 
			
		||||
            font-family: var(--monospace-font-family);
 | 
			
		||||
            margin: 5px;
 | 
			
		||||
@ -184,7 +188,7 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Vertical vs horizontal layout
 | 
			
		||||
        const layoutOrientation = options.get("splitEditorOrientation") ?? "horizontal";
 | 
			
		||||
        const layoutOrientation = (!utils.isMobile() ? options.get("splitEditorOrientation") ?? "horizontal" : "vertical");
 | 
			
		||||
        if (this.layoutOrientation === layoutOrientation && this.isReadOnly === isReadOnly) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -217,4 +217,12 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
 | 
			
		||||
        utils.downloadSvg(this.note.title, this.svg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async exportPngEvent({ ntxId }: EventData<"exportPng">) {
 | 
			
		||||
        if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        utils.downloadSvgAsPng(this.note.title, this.svg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1724,5 +1724,8 @@
 | 
			
		||||
  "toggle_read_only_button": {
 | 
			
		||||
    "unlock-editing": "Unlock editing",
 | 
			
		||||
    "lock-editing": "Lock editing"
 | 
			
		||||
  },
 | 
			
		||||
  "png_export_button": {
 | 
			
		||||
    "button_title": "Export diagram as PNG"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								src/services/export/single.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/services/export/single.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
import { describe, expect, it } from "vitest";
 | 
			
		||||
import BNote from "../../becca/entities/bnote.js";
 | 
			
		||||
import { mapByNoteType } from "./single.js";
 | 
			
		||||
 | 
			
		||||
describe("Note type mappings", () => {
 | 
			
		||||
    it("supports mermaid note", () => {
 | 
			
		||||
        const note = new BNote({
 | 
			
		||||
            type: "mermaid",
 | 
			
		||||
            title: "New note"
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        expect(mapByNoteType(note, "", "html")).toMatchObject({
 | 
			
		||||
            extension: "mermaid",
 | 
			
		||||
            mime: "text/vnd.mermaid"
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@ -8,6 +8,7 @@ import becca from "../../becca/becca.js";
 | 
			
		||||
import type TaskContext from "../task_context.js";
 | 
			
		||||
import type BBranch from "../../becca/entities/bbranch.js";
 | 
			
		||||
import type { Response } from "express";
 | 
			
		||||
import type BNote from "../../becca/entities/bnote.js";
 | 
			
		||||
 | 
			
		||||
function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response) {
 | 
			
		||||
    const note = branch.getNote();
 | 
			
		||||
@ -20,9 +21,21 @@ function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "ht
 | 
			
		||||
        return [400, `Unrecognized format '${format}'`];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { payload, extension, mime } = mapByNoteType(note, note.getContent(), format);
 | 
			
		||||
    const fileName = `${note.title}.${extension}`;
 | 
			
		||||
 | 
			
		||||
    res.setHeader("Content-Disposition", getContentDisposition(fileName));
 | 
			
		||||
    res.setHeader("Content-Type", `${mime}; charset=UTF-8`);
 | 
			
		||||
 | 
			
		||||
    res.send(payload);
 | 
			
		||||
 | 
			
		||||
    taskContext.increaseProgressCount();
 | 
			
		||||
    taskContext.taskSucceeded();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mapByNoteType(note: BNote, content: string | Buffer<ArrayBufferLike>, format: "html" | "markdown") {
 | 
			
		||||
    let payload, extension, mime;
 | 
			
		||||
 | 
			
		||||
    let content = note.getContent();
 | 
			
		||||
    if (typeof content !== "string") {
 | 
			
		||||
        throw new Error("Unsupported content type for export.");
 | 
			
		||||
    }
 | 
			
		||||
@ -52,21 +65,17 @@ function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "ht
 | 
			
		||||
        payload = content;
 | 
			
		||||
        extension = "excalidraw";
 | 
			
		||||
        mime = "application/json";
 | 
			
		||||
    } else if (note.type === "mermaid") {
 | 
			
		||||
        payload = content;
 | 
			
		||||
        extension = "mermaid";
 | 
			
		||||
        mime = "text/vnd.mermaid";
 | 
			
		||||
    } else if (note.type === "relationMap" || note.type === "search") {
 | 
			
		||||
        payload = content;
 | 
			
		||||
        extension = "json";
 | 
			
		||||
        mime = "application/json";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const fileName = `${note.title}.${extension}`;
 | 
			
		||||
 | 
			
		||||
    res.setHeader("Content-Disposition", getContentDisposition(fileName));
 | 
			
		||||
    res.setHeader("Content-Type", `${mime}; charset=UTF-8`);
 | 
			
		||||
 | 
			
		||||
    res.send(payload);
 | 
			
		||||
 | 
			
		||||
    taskContext.increaseProgressCount();
 | 
			
		||||
    taskContext.taskSucceeded();
 | 
			
		||||
    return { payload, extension, mime };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function inlineAttachments(content: string) {
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,15 @@ describe("#getMime", () => {
 | 
			
		||||
            ["test.excalidraw"], "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
 | 
			
		||||
        [
 | 
			
		||||
            "File extension ('.mermaid') that is defined in EXTENSION_TO_MIME",
 | 
			
		||||
            ["test.mermaid"], "text/vnd.mermaid"
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
            "File extension ('.mermaid') that is defined in EXTENSION_TO_MIME",
 | 
			
		||||
            ["test.mmd"], "text/vnd.mermaid"
 | 
			
		||||
        ],
 | 
			
		||||
 | 
			
		||||
        [
 | 
			
		||||
            "File extension with inconsistent capitalization that is defined in EXTENSION_TO_MIME",
 | 
			
		||||
            ["test.gRoOvY"], "text/x-groovy"
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@
 | 
			
		||||
import mimeTypes from "mime-types";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import type { TaskData } from "../task_context_interface.js";
 | 
			
		||||
import type { NoteType } from "../../becca/entities/rows.js";
 | 
			
		||||
 | 
			
		||||
const CODE_MIME_TYPES = new Set([
 | 
			
		||||
    "application/json",
 | 
			
		||||
@ -68,7 +69,9 @@ const EXTENSION_TO_MIME = new Map<string, string>([
 | 
			
		||||
    [".scala", "text/x-scala"],
 | 
			
		||||
    [".swift", "text/x-swift"],
 | 
			
		||||
    [".ts", "text/x-typescript"],
 | 
			
		||||
    [".excalidraw", "application/json"]
 | 
			
		||||
    [".excalidraw", "application/json"],
 | 
			
		||||
    [".mermaid", "text/vnd.mermaid"],
 | 
			
		||||
    [".mmd", "text/vnd.mermaid"]
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
/** @returns false if MIME is not detected */
 | 
			
		||||
@ -85,7 +88,7 @@ function getMime(fileName: string) {
 | 
			
		||||
    return mimeFromExt || mimeTypes.lookup(fileNameLc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getType(options: TaskData, mime: string) {
 | 
			
		||||
function getType(options: TaskData, mime: string): NoteType {
 | 
			
		||||
    const mimeLc = mime?.toLowerCase();
 | 
			
		||||
 | 
			
		||||
    switch (true) {
 | 
			
		||||
@ -98,6 +101,9 @@ function getType(options: TaskData, mime: string) {
 | 
			
		||||
        case mime.startsWith("image/"):
 | 
			
		||||
            return "image";
 | 
			
		||||
 | 
			
		||||
        case mime === "text/vnd.mermaid":
 | 
			
		||||
            return "mermaid";
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            return "file";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								src/services/import/samples/New note.mermaid
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/services/import/samples/New note.mermaid
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
graph TD;
 | 
			
		||||
    A-->B;
 | 
			
		||||
    A-->C;
 | 
			
		||||
    B-->D;
 | 
			
		||||
    C-->D;
 | 
			
		||||
							
								
								
									
										5
									
								
								src/services/import/samples/New note.mmd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/services/import/samples/New note.mmd
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
graph TD;
 | 
			
		||||
    A-->B;
 | 
			
		||||
    A-->C;
 | 
			
		||||
    B-->D;
 | 
			
		||||
    C-->D;
 | 
			
		||||
@ -96,4 +96,22 @@ describe("processNoteContent", () => {
 | 
			
		||||
        expect(importedNote.type).toBe("canvas");
 | 
			
		||||
        expect(importedNote.title).toBe("New note");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("imports .mermaid as mermaid note", async () => {
 | 
			
		||||
        const { importedNote } = await testImport("New note.mermaid", "application/json");
 | 
			
		||||
        expect(importedNote).toMatchObject({
 | 
			
		||||
            mime: "text/vnd.mermaid",
 | 
			
		||||
            type: "mermaid",
 | 
			
		||||
            title: "New note"
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("imports .mmd as mermaid note", async () => {
 | 
			
		||||
        const { importedNote } = await testImport("New note.mmd", "application/json");
 | 
			
		||||
        expect(importedNote).toMatchObject({
 | 
			
		||||
            mime: "text/vnd.mermaid",
 | 
			
		||||
            type: "mermaid",
 | 
			
		||||
            title: "New note"
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,10 @@ function importSingleFile(taskContext: TaskContext, file: File, parentNote: BNot
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mime === "text/vnd.mermaid") {
 | 
			
		||||
        return importCustomType(taskContext, file, parentNote, "mermaid", mime);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (taskContext?.data?.codeImportedAsCode && mimeService.getType(taskContext.data, mime) === "code") {
 | 
			
		||||
        return importCodeNote(taskContext, file, parentNote);
 | 
			
		||||
    }
 | 
			
		||||
@ -93,6 +97,24 @@ function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote)
 | 
			
		||||
    return note;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function importCustomType(taskContext: TaskContext, file: File, parentNote: BNote, type: NoteType, mime: string) {
 | 
			
		||||
    const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
 | 
			
		||||
    const content = processStringOrBuffer(file.buffer);
 | 
			
		||||
 | 
			
		||||
    const { note } = noteService.createNewNote({
 | 
			
		||||
        parentNoteId: parentNote.noteId,
 | 
			
		||||
        title,
 | 
			
		||||
        content,
 | 
			
		||||
        type,
 | 
			
		||||
        mime: mime,
 | 
			
		||||
        isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    taskContext.increaseProgressCount();
 | 
			
		||||
 | 
			
		||||
    return note;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function importPlainText(taskContext: TaskContext, file: File, parentNote: BNote) {
 | 
			
		||||
    const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
 | 
			
		||||
    const plainTextContent = processStringOrBuffer(file.buffer);
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ const noteTypes = [
 | 
			
		||||
    { type: "relationMap", defaultMime: "application/json" },
 | 
			
		||||
    { type: "book", defaultMime: "" },
 | 
			
		||||
    { type: "noteMap", defaultMime: "" },
 | 
			
		||||
    { type: "mermaid", defaultMime: "text/plain" },
 | 
			
		||||
    { type: "mermaid", defaultMime: "text/vnd.mermaid" },
 | 
			
		||||
    { type: "canvas", defaultMime: "application/json" },
 | 
			
		||||
    { type: "webView", defaultMime: "" },
 | 
			
		||||
    { type: "launcher", defaultMime: "" },
 | 
			
		||||
 | 
			
		||||
@ -181,6 +181,8 @@ export function removeTextFileExtension(filePath: string) {
 | 
			
		||||
        case ".html":
 | 
			
		||||
        case ".htm":
 | 
			
		||||
        case ".excalidraw":
 | 
			
		||||
        case ".mermaid":
 | 
			
		||||
        case ".mmd":
 | 
			
		||||
            return filePath.substring(0, filePath.length - extension.length);
 | 
			
		||||
        default:
 | 
			
		||||
            return filePath;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user