feat: enhance Milkdown editor and file system functionality

- Normalize line endings in Markdown export for DOCX files.
- Improve selection serialization to Markdown with better handling of empty documents.
- Add a new `updateFile` function to the file system for updating file properties.
- Introduce video transcoding capabilities using FFmpeg, supporting various video formats.
- Update AGENTS.md for clearer plugin structure and responsibilities.
- Add scoped styles for TreeNodeItem component to improve UI consistency.
- Implement cross-origin isolation headers in Vite configuration for enhanced security.
- Remove obsolete test_cross.py file.
This commit is contained in:
2026-05-01 20:55:02 +08:00
parent 52ade88840
commit 70152c61b1
43 changed files with 3911 additions and 1373 deletions
+59 -1
View File
@@ -98,8 +98,12 @@ function isBinaryExtension(ext) {
function inferMimeType(name, fallback = '') {
const ext = getExtension(name)
const map = {
avi: 'video/x-msvideo',
md: 'text/markdown',
markdown: 'text/markdown',
mkv: 'video/x-matroska',
mov: 'video/quicktime',
mp4: 'video/mp4',
txt: 'text/plain',
json: 'application/json',
js: 'text/javascript',
@@ -126,11 +130,17 @@ function inferMimeType(name, fallback = '') {
ps1: 'text/plain',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
flv: 'video/x-flv',
m4v: 'video/x-m4v',
png: 'image/png',
gif: 'image/gif',
webp: 'image/webp',
svg: 'image/svg+xml',
pdf: 'application/pdf'
pdf: 'application/pdf',
ogg: 'video/ogg',
ogv: 'video/ogg',
webm: 'video/webm',
wmv: 'video/x-ms-wmv'
}
return fallback || map[ext] || 'application/octet-stream'
}
@@ -398,6 +408,43 @@ export function useFileSystem() {
return true
}
function updateFile(id, nextValue, options = {}) {
const file = records.value.find((item) => item.id === id && item.type === 'file')
if (!file) return false
const nextName = options.name || file.name
const isBlobValue = nextValue instanceof Blob
const nextContent = isBlobValue ? (options.content ?? '') : String(options.content ?? nextValue ?? '')
const nextSize = typeof options.size === 'number'
? options.size
: isBlobValue
? nextValue.size
: new Blob([nextContent]).size
if (nextSize > MAX_FILE_SIZE) {
error.value = '单个文件不能超过 1GB'
return false
}
file.name = nextName
file.updatedAt = Date.now()
file.mimeType = inferMimeType(nextName, options.mimeType || (isBlobValue ? nextValue.type : file.mimeType))
file.size = nextSize
file.storageKind = options.storageKind || (isBlobValue ? 'blob' : 'text')
file.content = nextContent
file.previewText = options.previewText ?? (file.storageKind === 'text' ? nextContent : '')
file.isTruncatedPreview = Boolean(options.isTruncatedPreview)
file.blob = isBlobValue ? nextValue : null
records.value = [...records.value]
error.value = null
persistRecord(file).catch(() => {
error.value = '保存文件失败,可能是浏览器存储空间不足'
})
touchParent(file.parentId)
return true
}
function createFolder(parentId, name) {
if (records.value.length >= MAX_NODES) {
error.value = `目录项数量不能超过 ${MAX_NODES}`
@@ -594,8 +641,12 @@ export function useFileSystem() {
function getFileIcon(name) {
const ext = getExtension(name)
const iconMap = {
avi: 'video',
md: 'markdown',
markdown: 'markdown',
mkv: 'video',
mov: 'video',
mp4: 'video',
txt: 'text',
json: 'json',
js: 'javascript',
@@ -617,13 +668,19 @@ export function useFileSystem() {
jpeg: 'image',
png: 'image',
gif: 'image',
flv: 'video',
m4v: 'video',
webp: 'image',
svg: 'image',
ogg: 'video',
ogv: 'video',
pdf: 'pdf',
doc: 'word',
docx: 'word',
ppt: 'ppt',
pptx: 'ppt',
webm: 'video',
wmv: 'video',
xls: 'excel',
xlsx: 'excel',
zip: 'zip'
@@ -681,6 +738,7 @@ export function useFileSystem() {
stats,
load,
createFile,
updateFile,
createFolder,
rename,
remove,