功能特性
媒体支持
- 文件类型:
- 图片
- 视频
- 音频
- 其他 (PDF, Word等)
- 视频平台:
- 本地视频文件
- YouTube, Vimeo, Dailymotion, Youku, Coub
- 嵌入支持:
- 推特推文
媒体功能
- 可编辑的标题说明
- 可调整大小的元素
上传功能
- 多种上传方式:
- 工具栏按钮文件选择器
- 从文件系统拖放
- 从剪贴板粘贴(图片)
- 外部媒体URL嵌入
- 上传体验:
- 实时进度跟踪
- 上传过程中预览
- 上传或嵌入完成后自动将占位符转换为相应的媒体元素(图片/视频/音频/文件)
- 错误处理
- 文件大小验证
- 类型验证
套件使用
安装
最快捷的媒体支持方式是使用MediaKit
,它包含预配置的ImagePlugin
、VideoPlugin
、AudioPlugin
、FilePlugin
、MediaEmbedPlugin
、PlaceholderPlugin
和CaptionPlugin
及其Plate UI组件。
ImageElement
: 渲染图片元素VideoElement
: 渲染视频元素AudioElement
: 渲染音频元素FileElement
: 渲染文件元素MediaEmbedElement
: 渲染嵌入媒体PlaceholderElement
: 渲染上传占位符MediaUploadToast
: 显示上传进度通知MediaPreviewDialog
: 提供媒体预览功能
添加套件
将套件添加到你的插件中:
import { createPlateEditor } from 'platejs/react';
import { MediaKit } from '@/components/editor/plugins/media-kit';
const editor = createPlateEditor({
plugins: [
// ...其他插件
...MediaKit,
],
});
添加API路由
npx shadcn@latest add https://platejs.org/r/media-uploadthing-api
环境配置
从UploadThing获取密钥并添加到.env
:
UPLOADTHING_TOKEN=xxx
手动使用
安装
pnpm add @platejs/media
添加插件
在创建编辑器时将媒体插件包含到Plate插件数组中。
import {
AudioPlugin,
FilePlugin,
ImagePlugin,
MediaEmbedPlugin,
PlaceholderPlugin,
VideoPlugin,
} from '@platejs/media/react';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
ImagePlugin,
VideoPlugin,
AudioPlugin,
FilePlugin,
MediaEmbedPlugin,
PlaceholderPlugin,
],
});
配置插件
使用自定义组件和上传设置配置插件。
import {
AudioPlugin,
FilePlugin,
ImagePlugin,
MediaEmbedPlugin,
PlaceholderPlugin,
VideoPlugin,
} from '@platejs/media/react';
import { KEYS } from 'platejs';
import { createPlateEditor } from 'platejs/react';
import {
AudioElement,
FileElement,
ImageElement,
MediaEmbedElement,
PlaceholderElement,
VideoElement
} from '@/components/ui/media-nodes';
import { MediaUploadToast } from '@/components/ui/media-upload-toast';
const editor = createPlateEditor({
plugins: [
// ...其他插件
ImagePlugin.withComponent(ImageElement),
VideoPlugin.withComponent(VideoElement),
AudioPlugin.withComponent(AudioElement),
FilePlugin.withComponent(FileElement),
MediaEmbedPlugin.withComponent(MediaEmbedElement),
PlaceholderPlugin.configure({
options: { disableEmptyPlaceholder: true },
render: { afterEditable: MediaUploadToast, node: PlaceholderElement },
}),
],
});
withComponent
: 为每种媒体类型分配自定义渲染组件options.disableEmptyPlaceholder
: 无文件上传时禁止显示占位符render.afterEditable
: 在编辑器外部渲染上传进度提示
标题支持
要启用媒体标题,添加Caption Plugin:
import { CaptionPlugin } from '@platejs/caption/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件
// ...媒体插件
CaptionPlugin.configure({
options: {
query: {
allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],
},
},
}),
],
});
自定义上传实现
对于自定义上传实现,创建符合此接口的上传钩子:
interface UseUploadFileProps {
onUploadComplete?: (file: UploadedFile) => void;
onUploadError?: (error: unknown) => void;
headers?: Record<string, string>;
onUploadBegin?: (fileName: string) => void;
onUploadProgress?: (progress: { progress: number }) => void;
skipPolling?: boolean;
}
interface UploadedFile {
key: string; // 唯一标识符
url: string; // 上传文件的公开URL
name: string; // 原始文件名
size: number; // 文件大小(字节)
type: string; // MIME类型
}
使用S3预签名URL的示例实现:
export function useUploadFile({
onUploadComplete,
onUploadError,
onUploadProgress
}: UseUploadFileProps = {}) {
const [uploadedFile, setUploadedFile] = useState<UploadedFile>();
const [uploadingFile, setUploadingFile] = useState<File>();
const [progress, setProgress] = useState(0);
const [isUploading, setIsUploading] = useState(false);
async function uploadFile(file: File) {
setIsUploading(true);
setUploadingFile(file);
try {
// 从后端获取预签名URL和最终URL
const { presignedUrl, fileUrl, fileKey } = await fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
}).then(r => r.json());
// 使用预签名URL上传到S3
await axios.put(presignedUrl, file, {
headers: { 'Content-Type': file.type },
onUploadProgress: (progressEvent) => {
const progress = (progressEvent.loaded / progressEvent.total) * 100;
setProgress(progress);
onUploadProgress?.({ progress });
},
});
const uploadedFile = {
key: fileKey,
url: fileUrl,
name: file.name,
size: file.size,
type: file.type,
};
setUploadedFile(uploadedFile);
onUploadComplete?.(uploadedFile);
return uploadedFile;
} catch (error) {
onUploadError?.(error);
throw error;
} finally {
setProgress(0);
setIsUploading(false);
setUploadingFile(undefined);
}
}
return {
isUploading,
progress,
uploadFile,
uploadedFile,
uploadingFile,
};
}
然后将你的自定义上传钩子与媒体组件集成:
import { useUploadFile } from '@/hooks/use-upload-file'; // 你的自定义钩子
// 在你的PlaceholderElement组件中
export function PlaceholderElement({ className, children, element, ...props }) {
const { uploadFile, isUploading, progress } = useUploadFile({
onUploadComplete: (uploadedFile) => {
// 将占位符替换为实际媒体元素
const { url, type } = uploadedFile;
// 将占位符转换为适当的媒体类型
editor.tf.replace.placeholder({
id: element.id,
url,
type: getMediaType(type), // image, video, audio, file
});
},
onUploadError: (error) => {
console.error('上传失败:', error);
// 处理上传错误,可能显示提示
},
});
// 当文件被拖放或选择时使用uploadFile
// 这与PlaceholderPlugin的文件处理集成
}
添加工具栏按钮
你可以将MediaToolbarButton
添加到Toolbar以上传和插入媒体。
插入工具栏按钮
你可以将这些项添加到插入工具栏按钮来插入媒体元素:
{
icon: <ImageIcon />,
label: '图片',
value: KEYS.img,
}
Plate Plus
插件
ImagePlugin
用于void图片元素的插件。
uploadImage optional (dataUrl: string | ArrayBuffer) => Promise<string | ArrayBuffer> | string | ArrayBuffer
上传图片到服务器的函数。接收:
- 来自
FileReader.readAsDataURL
的数据URL(字符串) - 来自剪贴板数据的ArrayBuffer 返回:
- 上传图片的URL字符串
- 如果不需要上传则返回原始数据URL/ArrayBuffer
- 默认: 返回原始输入
- 来自
disableUploadInsert optional boolean
禁用数据插入时的文件上传。
- 默认:
false
- 默认:
disableEmbedInsert optional boolean
禁用数据插入时的URL嵌入。
- 默认:
false
- 默认:
isUrl optional function
检查文本字符串是否为URL的函数。
transformUrl optional function
转换URL的函数。
VideoPlugin
用于void视频元素的插件。扩展MediaPluginOptions
。
AudioPlugin
用于void音频元素的插件。扩展MediaPluginOptions
。
FilePlugin
用于void文件元素的插件。扩展MediaPluginOptions
。
MediaEmbedPlugin
用于void媒体嵌入元素的插件。扩展MediaPluginOptions
。
PlaceholderPlugin
管理上传过程中媒体占位符的插件。处理文件上传、拖放和剪贴板粘贴事件。
uploadConfig optional Partial<Record<AllowedFileType, MediaItemConfig>>
不同文件类型的配置。默认配置:
{ audio: { maxFileCount: 1, maxFileSize: '8MB', mediaType: KEYS.audio, minFileCount: 1, }, blob: { maxFileCount: 1, maxFileSize: '8MB', mediaType: KEYS.file, minFileCount: 1, }, image: { maxFileCount: 3, maxFileSize: '4MB', mediaType: KEYS.image, minFileCount: 1, }, pdf: { maxFileCount: 1, maxFileSize: '4MB', mediaType: KEYS.file, minFileCount: 1, }, text: { maxFileCount: 1, maxFileSize: '64KB', mediaType: KEYS.file, minFileCount: 1, }, video: { maxFileCount: 1, maxFileSize: '16MB', mediaType: KEYS.video, minFileCount: 1, }, }
支持的文件类型:
'image' | 'video' | 'audio' | 'pdf' | 'text' | 'blob'
disableEmptyPlaceholder optional boolean
无文件上传时禁用空占位符。
- 默认:
false
- 默认:
disableFileDrop optional boolean
禁用拖放文件上传功能。
- 默认:
false
- 默认:
maxFileCount optional number
如果
uploadConfig
未指定,可一次上传的最大文件数。- 默认:
5
- 默认:
multiple optional boolean
允许上传多个相同类型的文件。
- 默认:
true
- 默认:
API
api.placeholder.addUploadingFile
跟踪当前正在上传的文件。
api.placeholder.getUploadingFile
获取当前正在上传的文件。
api.placeholder.removeUploadingFile
上传完成或失败后从上传跟踪状态中移除文件。
转换方法
tf.insert.media
使用上传占位符将媒体文件插入编辑器。
根据配置的限制(大小、数量、类型)验证文件,为每个文件创建占位符元素,处理多个文件顺序上传,维护撤销/重做操作的上传历史记录,如果验证失败则触发错误处理。
错误代码:
enum UploadErrorCode {
INVALID_FILE_TYPE = 400,
TOO_MANY_FILES = 402,
INVALID_FILE_SIZE = 403,
TOO_LESS_FILES = 405,
TOO_LARGE = 413,
}
tf.insert.imagePlaceholder
插入一个在上传完成后转换为图片元素的占位符。
tf.insert.videoPlaceholder
插入一个在上传完成后转换为视频元素的占位符。
tf.insert.audioPlaceholder
插入一个在上传完成后转换为音频元素的占位符。
tf.insert.filePlaceholder
插入一个在上传完成后转换为文件元素的占位符。
tf.insert.image
在编辑器中插入图片元素。
tf.insert.mediaEmbed
在当前选区插入媒体嵌入元素。
Hooks
useResizable
处理媒体元素的可调整大小属性。
align 'left' | 'center' | 'right'
可调整大小元素内容的对齐方式。
minWidth ResizeLength
可调整大小元素可以调整到的最小宽度。
maxWidth ResizeLength
可调整大小元素可以调整到的最大宽度。
setNodeWidth (width: number | string) => void
调整大小时设置节点宽度的函数。
setWidth (width: number | string) => void
直接设置可调整大小元素宽度的函数。
width Property.Width<string | number> | undefined
可调整大小元素的当前宽度(百分比、'auto' 或像素)。
useMediaState
媒体元素的状态钩子。
useMediaToolbarButton
媒体工具栏按钮的行为钩子。
useFloatingMediaEditButton
处理浮动媒体编辑按钮。
useFloatingMediaUrlInput
处理媒体元素的 URL 输入字段。
useImage
图片元素的钩子。
工具函数
parseMediaUrl
解析媒体 URL 以进行插件特定的处理。
parseVideoUrl
解析视频 URL 并提取视频 ID 和提供商特定的嵌入 URL。
parseTwitterUrl
解析 Twitter URL 并提取推文 ID。
parseIframeUrl
解析 iframe 嵌入的 URL。
isImageUrl
检查 URL 是否为有效的图片 URL。
submitFloatingMedia
提交浮动媒体元素。
withImageUpload
为编辑器实例添加图片上传功能。
withImageEmbed
为编辑器实例添加图片相关功能。
类型
TMediaElement
export interface TMediaElement extends TElement {
url: string;
id?: string;
align?: 'center' | 'left' | 'right';
isUpload?: boolean;
name?: string;
placeholderId?: string;
}
TPlaceholderElement
export interface TPlaceholderElement extends TElement {
mediaType: string;
}
EmbedUrlData
export interface EmbedUrlData {
url?: string;
provider?: string;
id?: string;
component?: React.FC<EmbedUrlData>;
}
On This Page
功能特性媒体支持媒体功能上传功能套件使用安装添加套件添加API路由环境配置手动使用安装添加插件配置插件标题支持自定义上传实现添加工具栏按钮插入工具栏按钮Plate Plus插件ImagePlugin
VideoPlugin
AudioPlugin
FilePlugin
MediaEmbedPlugin
PlaceholderPlugin
APIapi.placeholder.addUploadingFile
api.placeholder.getUploadingFile
api.placeholder.removeUploadingFile
转换方法tf.insert.media
tf.insert.imagePlaceholder
tf.insert.videoPlaceholder
tf.insert.audioPlaceholder
tf.insert.filePlaceholder
tf.insert.image
tf.insert.mediaEmbed
HooksuseResizable
useMediaState
useMediaToolbarButton
useFloatingMediaEditButton
useFloatingMediaUrlInput
useImage
工具函数parseMediaUrl
parseVideoUrl
parseTwitterUrl
parseIframeUrl
isImageUrl
submitFloatingMedia
withImageUpload
withImageEmbed
类型TMediaElement
TPlaceholderElement
EmbedUrlData