feat: support .md and .mdx file uploads

Plain-text files have no magic bytes, so the file-type signature
detector returned an empty MIME type and the backend rejected them.
Add an extension-based fallback for markdown/mdx, allow text/mdx in
the API and editor allow-lists, and pick a file icon for these
extensions.
This commit is contained in:
sriram veeraghanta
2026-05-03 00:01:24 +05:30
parent a62fe8a781
commit 86c6b69a54
4 changed files with 33 additions and 2 deletions
+1 -2
View File
@@ -402,6 +402,7 @@ ATTACHMENT_MIME_TYPES = [
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain",
"text/markdown",
"text/mdx",
"application/rtf",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.text",
@@ -469,8 +470,6 @@ ATTACHMENT_MIME_TYPES = [
"application/x-sql",
# Gzip
"application/x-gzip",
# Markdown
"text/markdown",
]
# Seed directory path
@@ -47,6 +47,9 @@ export const getFileIcon = (fileType: string, size: number = 28) => {
case "js":
return <JavaScriptIcon height={size} width={size} />;
case "txt":
case "md":
case "markdown":
case "mdx":
return <TxtIcon height={size} width={size} />;
case "svg":
return <SvgIcon height={size} width={size} />;
@@ -33,6 +33,7 @@ export const ACCEPTED_ATTACHMENT_MIME_TYPES = [
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain",
"text/markdown",
"text/mdx",
"application/rtf",
"audio/mpeg",
"audio/wav",
+28
View File
@@ -10,6 +10,28 @@ import { fileTypeFromBuffer } from "file-type";
import type { TFileMetaDataLite, TFileSignedURLResponse } from "@plane/types";
import { DANGEROUS_EXTENSIONS } from "@plane/constants";
/**
* @description Map of file extensions to MIME types for plain-text formats that
* file-type signature detection cannot identify (no magic bytes).
*/
const EXTENSION_MIME_TYPE_MAP: Record<string, string> = {
md: "text/markdown",
markdown: "text/markdown",
mdx: "text/mdx",
};
/**
* @description Resolve a MIME type from the file extension for known text formats.
* @param {string} filename
* @returns {string} MIME type if extension is known, empty string otherwise
*/
const detectMimeTypeFromExtension = (filename: string): string => {
const parts = filename.split(".");
if (parts.length < 2) return "";
const extension = parts[parts.length - 1]?.toLowerCase() || "";
return EXTENSION_MIME_TYPE_MAP[extension] || "";
};
/**
* @description Filename validation - checks for double extensions and dangerous patterns
* @param {string} filename
@@ -103,6 +125,12 @@ const validateAndDetectFileType = async (file: File): Promise<string> => {
console.warn("Error detecting file type from signature:", _error);
}
// Plain-text formats (markdown, mdx, …) have no magic bytes — fall back to extension.
const extensionType = detectMimeTypeFromExtension(file.name);
if (extensionType) {
return extensionType;
}
// fallback for unknown files
return "";
};