'use client';
import * as React from 'react';
import { Plate, usePlateEditor } from 'platejs/react';
import { EditorKit } from '@/components/editor/editor-kit';
import { Editor, EditorContainer } from '@/components/ui/editor';
import { DEMO_VALUES } from './values/demo-values';
export default function Demo({ id }: { id: string }) {
const editor = usePlateEditor({
plugins: EditorKit,
value: DEMO_VALUES[id],
});
return (
<Plate editor={editor}>
<EditorContainer variant="demo">
<Editor />
</EditorContainer>
</Plate>
);
}
套件使用
安装
最快捷的媒体支持方式是使用MediaKit
,它包含预配置的ImagePlugin
、VideoPlugin
、AudioPlugin
、FilePlugin
、MediaEmbedPlugin
、PlaceholderPlugin
和CaptionPlugin
及其Plate UI组件。
'use client';
import { CaptionPlugin } from '@platejs/caption/react';
import {
AudioPlugin,
FilePlugin,
ImagePlugin,
MediaEmbedPlugin,
PlaceholderPlugin,
VideoPlugin,
} from '@platejs/media/react';
import { KEYS } from 'platejs';
import { AudioElement } from '@/components/ui/media-audio-node';
import { MediaEmbedElement } from '@/components/ui/media-embed-node';
import { FileElement } from '@/components/ui/media-file-node';
import { ImageElement } from '@/components/ui/media-image-node';
import { PlaceholderElement } from '@/components/ui/media-placeholder-node';
import { MediaPreviewDialog } from '@/components/ui/media-preview-dialog';
import { MediaUploadToast } from '@/components/ui/media-upload-toast';
import { VideoElement } from '@/components/ui/media-video-node';
export const MediaKit = [
ImagePlugin.configure({
options: { disableUploadInsert: true },
render: { afterEditable: MediaPreviewDialog, node: ImageElement },
}),
MediaEmbedPlugin.withComponent(MediaEmbedElement),
VideoPlugin.withComponent(VideoElement),
AudioPlugin.withComponent(AudioElement),
FilePlugin.withComponent(FileElement),
PlaceholderPlugin.configure({
options: { disableEmptyPlaceholder: true },
render: { afterEditable: MediaUploadToast, node: PlaceholderElement },
}),
CaptionPlugin.configure({
options: {
query: {
allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],
},
},
}),
];
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
- Integration with UploadThing
- Use slash commands for quick insertion
- Displays clickable placeholders for various media types (image, video, audio, file)
- Opens a popover with two tabs when the placeholder is clicked:
- Upload tab: Allows uploading local files directly
- Embed tab: Enables pasting embed links for media content
- Image-specific features:
- Better loading rendering and image replacement
- Alignment options
- Expand/collapse view
- Download button
- Video-specific features:
- Lazy load
- Alignment options
- Caption support
- View original source
- Floating toolbar appears at the top right of media elements:
- Alignment dropdown menu
- Caption button
- Expand button
- Download button
- Beautifully crafted UI
插件
ImagePlugin
用于void图片元素的插件。
VideoPlugin
用于void视频元素的插件。扩展MediaPluginOptions
。
AudioPlugin
用于void音频元素的插件。扩展MediaPluginOptions
。
FilePlugin
用于void文件元素的插件。扩展MediaPluginOptions
。
MediaEmbedPlugin
用于void媒体嵌入元素的插件。扩展MediaPluginOptions
。
PlaceholderPlugin
管理上传过程中媒体占位符的插件。处理文件上传、拖放和剪贴板粘贴事件。
- 默认:
false
- 默认:
false
- 默认:
5
- 默认:
true
不同文件类型的配置。默认配置:
{
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'
无文件上传时禁用空占位符。
禁用拖放文件上传功能。
如果uploadConfig
未指定,可一次上传的最大文件数。
允许上传多个相同类型的文件。
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
处理媒体元素的可调整大小属性。
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插件ImagePluginVideoPluginAudioPluginFilePluginMediaEmbedPluginPlaceholderPluginAPIapi.placeholder.addUploadingFileapi.placeholder.getUploadingFileapi.placeholder.removeUploadingFile转换方法tf.insert.mediatf.insert.imagePlaceholdertf.insert.videoPlaceholdertf.insert.audioPlaceholdertf.insert.filePlaceholdertf.insert.imagetf.insert.mediaEmbedHooksuseResizableuseMediaStateuseMediaToolbarButtonuseFloatingMediaEditButtonuseFloatingMediaUrlInputuseImage工具函数parseMediaUrlparseVideoUrlparseTwitterUrlparseIframeUrlisImageUrlsubmitFloatingMediawithImageUploadwithImageEmbed类型TMediaElementTPlaceholderElementEmbedUrlData