diff --git a/apps/api/plane/settings/common.py b/apps/api/plane/settings/common.py index d90ee10f891..e9df54fd834 100644 --- a/apps/api/plane/settings/common.py +++ b/apps/api/plane/settings/common.py @@ -402,6 +402,7 @@ "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 @@ "application/x-sql", # Gzip "application/x-gzip", - # Markdown - "text/markdown", ] # Seed directory path diff --git a/apps/web/core/components/icons/attachment/attachment-icon.tsx b/apps/web/core/components/icons/attachment/attachment-icon.tsx index dcbf652cd80..1ac6c2a033b 100644 --- a/apps/web/core/components/icons/attachment/attachment-icon.tsx +++ b/apps/web/core/components/icons/attachment/attachment-icon.tsx @@ -47,6 +47,9 @@ export const getFileIcon = (fileType: string, size: number = 28) => { case "js": return ; case "txt": + case "md": + case "markdown": + case "mdx": return ; case "svg": return ; diff --git a/packages/editor/src/core/constants/config.ts b/packages/editor/src/core/constants/config.ts index 64508e63e0a..9e126fa2a7e 100644 --- a/packages/editor/src/core/constants/config.ts +++ b/packages/editor/src/core/constants/config.ts @@ -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", diff --git a/packages/services/src/file/helper.ts b/packages/services/src/file/helper.ts index b8e96283986..8f3f5b8ee97 100644 --- a/packages/services/src/file/helper.ts +++ b/packages/services/src/file/helper.ts @@ -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 = { + 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 => { 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 ""; };