feat: use blacklist approach for file editability in Monaco Editor

Switch from whitelist to blacklist pattern for determining editable files.
Files are now editable unless they are known binary types (audio, archives,
executables, Office documents, fonts, etc.), enabling support for any
runtime-generated text files without needing to add extensions one by one.
This commit is contained in:
yyh
2026-01-21 16:53:01 +08:00
parent b5e31c0f25
commit d444a8eadc
3 changed files with 86 additions and 15 deletions

View File

@ -2,9 +2,9 @@ import type { AppAssetTreeView } from '@/types/app-asset'
import { useMemo } from 'react'
import {
getFileExtension,
isCodeOrTextFile,
isImageFile,
isMarkdownFile,
isTextLikeFile,
isVideoFile,
} from '../utils/file-utils'
@ -17,24 +17,21 @@ export type FileTypeInfo = {
isMediaFile: boolean
}
/**
* Hook to determine file type information based on file node.
* Returns flags for markdown, code/text, image, video files.
*/
export function useFileTypeInfo(fileNode: AppAssetTreeView | undefined): FileTypeInfo {
return useMemo(() => {
const ext = getFileExtension(fileNode?.name, fileNode?.extension)
const markdown = isMarkdownFile(ext)
const codeOrText = isCodeOrTextFile(ext)
const image = isImageFile(ext)
const video = isVideoFile(ext)
const editable = isTextLikeFile(ext)
const codeOrText = editable && !markdown
return {
isMarkdown: markdown,
isCodeOrText: codeOrText,
isImage: image,
isVideo: video,
isEditable: markdown || codeOrText,
isEditable: editable,
isMediaFile: image || video,
}
}, [fileNode?.name, fileNode?.extension])

View File

@ -12,7 +12,7 @@ import {
useCreateAppAssetFolder,
useRenameAppAssetNode,
} from '@/service/use-app-asset'
import { getFileExtension, isCodeOrTextFile, isMarkdownFile } from '../utils/file-utils'
import { getFileExtension, isTextLikeFile } from '../utils/file-utils'
import { createDraftTreeNode, insertDraftTreeNode } from '../utils/tree-utils'
type UseInlineCreateNodeOptions = {
@ -86,7 +86,7 @@ export function useInlineCreateNode({
parentId: pendingCreateParentId,
})
const extension = getFileExtension(trimmedName, createdFile.extension)
if (isMarkdownFile(extension) || isCodeOrTextFile(extension))
if (isTextLikeFile(extension))
storeApi.getState().openTab(createdFile.id, { pinned: true })
Toast.notify({
type: 'success',

View File

@ -2,10 +2,80 @@ import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/type
const MARKDOWN_EXTENSIONS = ['md', 'markdown', 'mdx']
const CODE_EXTENSIONS = ['json', 'yaml', 'yml', 'toml', 'js', 'jsx', 'ts', 'tsx', 'py', 'schema']
const TEXT_EXTENSIONS = ['txt', 'log', 'ini', 'env']
const IGNORE_EXTENSIONS = ['gitignore', 'dockerignore', 'prettierignore', 'eslintignore', 'npmignore', 'hgignore']
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico']
const VIDEO_EXTENSIONS = ['mp4', 'mov', 'webm', 'mpeg', 'mpg', 'm4v', 'avi']
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico', 'tiff', 'psd', 'heic', 'heif', 'avif']
const VIDEO_EXTENSIONS = ['mp4', 'mov', 'webm', 'mpeg', 'mpg', 'm4v', 'avi', 'mkv', 'flv', 'wmv', '3gp']
const BINARY_EXTENSIONS = [
'mp3',
'wav',
'ogg',
'flac',
'm4a',
'aac',
'wma',
'aiff',
'opus',
'zip',
'tar',
'gz',
'rar',
'7z',
'bz2',
'xz',
'tgz',
'tbz2',
'lz',
'lzma',
'cab',
'iso',
'dmg',
'exe',
'dll',
'so',
'dylib',
'bin',
'o',
'obj',
'class',
'pyc',
'pyo',
'pyd',
'wasm',
'app',
'msi',
'deb',
'rpm',
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'odt',
'ods',
'odp',
'rtf',
'epub',
'mobi',
'ttf',
'otf',
'woff',
'woff2',
'eot',
'db',
'sqlite',
'sqlite3',
'mdb',
'accdb',
'jar',
'war',
'ear',
'apk',
'ipa',
'aab',
'lock',
]
export function getFileExtension(name?: string, extension?: string): string {
if (extension)
@ -31,8 +101,12 @@ export function isMarkdownFile(extension: string): boolean {
return MARKDOWN_EXTENSIONS.includes(extension)
}
export function isCodeOrTextFile(extension: string): boolean {
return CODE_EXTENSIONS.includes(extension) || TEXT_EXTENSIONS.includes(extension) || IGNORE_EXTENSIONS.includes(extension)
export function isBinaryFile(extension: string): boolean {
return BINARY_EXTENSIONS.includes(extension)
}
export function isTextLikeFile(extension: string): boolean {
return !isBinaryFile(extension) && !isImageFile(extension) && !isVideoFile(extension)
}
export function isImageFile(extension: string): boolean {