diff --git a/src/components/editor-provider.tsx b/src/components/editor-provider.tsx index 95db728..5beeef3 100644 --- a/src/components/editor-provider.tsx +++ b/src/components/editor-provider.tsx @@ -11,7 +11,7 @@ import { type LanguageName } from "~/utils/languages"; import { createEmptyTab, inferLanguage, - LANGUAGE_TO_EXTENSION, + LANGUAGE_EXTENSIONS, replaceFilenameExtension, type PasteTab, } from "~/utils/paste-tabs"; @@ -77,7 +77,7 @@ export function EditorProvider({ const updateActiveTabLanguage = (language: LanguageName) => { updateTab(activeTab.id, (tab) => { const currentExtension = tab.filename.split(".").pop()?.toLowerCase(); - const currentLanguageExtension = LANGUAGE_TO_EXTENSION[tab.language]; + const currentLanguageExtension = LANGUAGE_EXTENSIONS[tab.language]; return { ...tab, diff --git a/src/utils/paste-tabs.ts b/src/utils/paste-tabs.ts index 18afa1f..61ffbaa 100644 --- a/src/utils/paste-tabs.ts +++ b/src/utils/paste-tabs.ts @@ -95,23 +95,34 @@ export const LANGUAGE_EXTENSIONS = { sfm: "sfm", } as const satisfies Record; -export const LANGUAGE_TO_EXTENSION: Record = - LANGUAGE_EXTENSIONS; - -const EXTENSION_TO_LANGUAGE = Object.fromEntries( - Object.entries(LANGUAGE_TO_EXTENSION).map(([language, extension]) => [ +const EXTENSION_LANGUAGES = Object.fromEntries( + Object.entries(LANGUAGE_EXTENSIONS).map(([language, extension]) => [ extension, language, ]), ) as Record; +const COMPOUND_EXTENSIONS = ( + Object.entries(LANGUAGE_EXTENSIONS) as [LanguageName, string][] +) + .filter(([, ext]) => ext.includes(".")) + .sort((a, b) => b[1].length - a[1].length); + const createTabId = () => crypto.randomUUID(); export const inferLanguage = (filename: string) => { - const extension = filename.trim().split(".").pop()?.toLowerCase(); - if (!extension || extension === filename.trim().toLowerCase()) return null; + const trimmed = filename.trim(); + if (!trimmed) return null; + const lower = trimmed.toLowerCase(); - return EXTENSION_TO_LANGUAGE[extension] ?? "text"; + for (const [language, ext] of COMPOUND_EXTENSIONS) { + if (lower.endsWith(`.${ext}`)) return language; + } + + const extension = lower.split(".").pop(); + if (!extension || extension === lower) return null; + + return EXTENSION_LANGUAGES[extension] ?? "text"; }; export const replaceFilenameExtension = ( @@ -119,12 +130,22 @@ export const replaceFilenameExtension = ( language: LanguageName, ) => { const trimmedFilename = filename.trim(); - if (!trimmedFilename) return `file.${LANGUAGE_TO_EXTENSION[language]}`; + if (!trimmedFilename) return `file.${LANGUAGE_EXTENSIONS[language]}`; + + const newExt = LANGUAGE_EXTENSIONS[language]; + const lower = trimmedFilename.toLowerCase(); + + for (const [, ext] of COMPOUND_EXTENSIONS) { + if (lower.endsWith(`.${ext}`)) { + const cut = trimmedFilename.length - (ext.length + 1); + return `${trimmedFilename.slice(0, cut)}.${newExt}`; + } + } const lastDotIndex = trimmedFilename.lastIndexOf("."); if (lastDotIndex <= 0) return trimmedFilename; - return `${trimmedFilename.slice(0, lastDotIndex)}.${LANGUAGE_TO_EXTENSION[language]}`; + return `${trimmedFilename.slice(0, lastDotIndex)}.${newExt}`; }; export const createEmptyTab = ( @@ -132,7 +153,7 @@ export const createEmptyTab = ( language: LanguageName = "typescript", ) => ({ id: createTabId(), - filename: `file${index}.${LANGUAGE_TO_EXTENSION[language]}`, + filename: `file${index}.${LANGUAGE_EXTENSIONS[language]}`, language, content: "", });