┌─────────────────────────────────────────────────────────────────┐ │ 📄 shadcn/directory/udecode/plate/(plugins)/(elements)/media.cn │ └─────────────────────────────────────────────────────────────────┘
╔══════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
title: 媒体组件 docs:
最快捷的媒体支持方式是使用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,
],
});
从UploadThing获取密钥并添加到.env:
UPLOADTHING_TOKEN=xxx
</Steps>
npm install @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,
}
</Steps>
ImagePlugin用于void图片元素的插件。
<API name="ImagePlugin"> <APIOptions type="ImagePluginOptions"> <APIItem name="uploadImage" type="(dataUrl: string | ArrayBuffer) => Promise<string | ArrayBuffer> | string | ArrayBuffer" optional> 上传图片到服务器的函数。接收: - 来自`FileReader.readAsDataURL`的数据URL(字符串) - 来自剪贴板数据的ArrayBuffer 返回: - 上传图片的URL字符串 - 如果不需要上传则返回原始数据URL/ArrayBuffer - **默认:** 返回原始输入 </APIItem> <APIItem name="disableUploadInsert" type="boolean" optional> 禁用数据插入时的文件上传。 - **默认:** `false` </APIItem> <APIItem name="disableEmbedInsert" type="boolean" optional> 禁用数据插入时的URL嵌入。 - **默认:** `false` </APIItem> <APIItem name="isUrl" type="function" optional> 检查文本字符串是否为URL的函数。 </APIItem> <APIItem name="transformUrl" type="function" optional> 转换URL的函数。 </APIItem> </APIOptions> </API>VideoPlugin用于void视频元素的插件。扩展MediaPluginOptions。
AudioPlugin用于void音频元素的插件。扩展MediaPluginOptions。
FilePlugin用于void文件元素的插件。扩展MediaPluginOptions。
MediaEmbedPlugin用于void媒体嵌入元素的插件。扩展MediaPluginOptions。
PlaceholderPlugin管理上传过程中媒体占位符的插件。处理文件上传、拖放和剪贴板粘贴事件。
<API name="PlaceholderPlugin"> <APIOptions type="object"> <APIItem name="uploadConfig" type="Partial<Record<AllowedFileType, MediaItemConfig>>" optional> 不同文件类型的配置。默认配置: ```ts { 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'` <APISubList> <APISubListItem parent="uploadConfig" name="mediaType" type="MediaKeys"> 此配置对应的媒体插件键: `'audio' | 'file' | 'image' | 'video'` </APISubListItem> <APISubListItem parent="uploadConfig" name="maxFileCount" type="number" optional> 此类型文件可上传的最大数量。 </APISubListItem> <APISubListItem parent="uploadConfig" name="maxFileSize" type="FileSize" optional> 此类型文件的最大文件大小。格式: `${1|2|4|8|16|32|64|128|256|512|1024}${B|KB|MB|GB}` </APISubListItem> <APISubListItem parent="uploadConfig" name="minFileCount" type="number" optional> 此类型文件必须上传的最小数量。 </APISubListItem> </APISubList> </APIItem> <APIItem name="disableEmptyPlaceholder" type="boolean" optional> 无文件上传时禁用空占位符。 - **默认:** `false` </APIItem> <APIItem name="disableFileDrop" type="boolean" optional> 禁用拖放文件上传功能。 - **默认:** `false` </APIItem> <APIItem name="maxFileCount" type="number" optional> 如果`uploadConfig`未指定,可一次上传的最大文件数。 - **默认:** `5` </APIItem> <APIItem name="multiple" type="boolean" optional> 允许上传多个相同类型的文件。 - **默认:** `true` </APIItem> </APIOptions> </API>api.placeholder.addUploadingFile跟踪当前正在上传的文件。
<API name="addUploadingFile"> <APIParameters> <APIItem name="id" type="string"> 占位符元素的唯一标识符。 </APIItem> <APIItem name="file" type="File"> 正在上传的文件。 </APIItem> </APIParameters> </API>api.placeholder.getUploadingFile获取当前正在上传的文件。
<API name="getUploadingFile"> <APIParameters> <APIItem name="id" type="string"> 占位符元素的唯一标识符。 </APIItem> </APIParameters> <APIReturns> <APIItem type="File | undefined"> 如果找到则返回上传文件,否则返回undefined。 </APIItem> </APIReturns> </API>api.placeholder.removeUploadingFile上传完成或失败后从上传跟踪状态中移除文件。
<API name="removeUploadingFile"> <APIParameters> <APIItem name="id" type="string"> 要移除的占位符元素的唯一标识符。 </APIItem> </APIParameters> </API>tf.insert.media使用上传占位符将媒体文件插入编辑器。
<API name="insertMedia"> <APIParameters> <APIItem name="files" type="FileList"> 要上传的文件。根据配置的文件类型和限制进行验证。 </APIItem> <APIItem name="options" type="object" optional> 插入节点的转换选项。 </APIItem> </APIParameters> <APIOptions type="object"> <APIItem name="at" type="Path" optional> 插入媒体的位置。默认为当前选区。 </APIItem> <APIItem name="nextBlock" type="boolean" optional> 是否在媒体后插入新块。 - **默认:** `true` </APIItem> </APIOptions> </API>根据配置的限制(大小、数量、类型)验证文件,为每个文件创建占位符元素,处理多个文件顺序上传,维护撤销/重做操作的上传历史记录,如果验证失败则触发错误处理。
错误代码:
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在编辑器中插入图片元素。
<API name="insertImage"> <APIParameters> <APIItem name="url" type="string | ArrayBuffer"> 图片的 URL 或 ArrayBuffer。 </APIItem> <APIItem name="options" type="InsertNodesOptions" optional> 插入图片元素的额外选项。 </APIItem> </APIParameters> <APIOptions type="InsertImageOptions"> <APIItem name="nextBlock" type="boolean" optional> 如果为 true,图片将被插入到下一个块中。 </APIItem> </APIOptions> </API>tf.insert.mediaEmbed在当前选区插入媒体嵌入元素。
<API name="insertMediaEmbed"> <APIOptions type="InsertMediaEmbedOptions"> <APIItem name="url" type="string" optional> 媒体嵌入的 URL。 - **默认值:** `''` </APIItem> <APIItem name="key" type="string" optional> 媒体嵌入元素的键。 - **默认值:** `KEYS.mediaEmbed` </APIItem> <APIItem name="insertNodesOptions" type="InsertNodesOptions" optional> 插入节点的额外选项。 </APIItem> </APIOptions> </API>useResizable处理媒体元素的可调整大小属性。
<API name="useResizable"> <APIState> <APIItem name="align" type="'left' | 'center' | 'right'"> 可调整大小元素内容的对齐方式。 </APIItem> <APIItem name="minWidth" type="ResizeLength"> 可调整大小元素可以调整到的最小宽度。 </APIItem> <APIItem name="maxWidth" type="ResizeLength"> 可调整大小元素可以调整到的最大宽度。 </APIItem> <APIItem name="setNodeWidth" type="(width: number | string) => void"> 调整大小时设置节点宽度的函数。 </APIItem> <APIItem name="setWidth" type="(width: number | string) => void"> 直接设置可调整大小元素宽度的函数。 </APIItem> <APIItem name="width" type="Property.Width<string | number> | undefined"> 可调整大小元素的当前宽度(百分比、'auto' 或像素)。 </APIItem> </APIState> <APIReturns type="object"> <APIItem name="wrapperRef" type="React.RefObject<HTMLDivElement>"> 最外层包装 div 的 React 引用。 </APIItem> <APIItem name="wrapperProps.style" type="CSSProperties"> 包装 div 的 CSS 样式。 </APIItem> <APIItem name="props.style" type="CSSProperties"> 可调整大小元素的 CSS 样式。 </APIItem> <APIItem name="context.onResize" type="() => void"> 元素调整大小时调用的回调函数。 </APIItem> </APIReturns> </API>useMediaState媒体元素的状态钩子。
<API name="useMediaState"> <APIParameters> <APIItem name="options.urlParsers" type="EmbedUrlParser[]" optional> 用于解析媒体元素 URL 的 URL 解析器数组。- **`EmbedUrlParser`:** `(url: string) => EmbedUrlData | undefined`
</APIItem>
</APIParameters>
<APIReturns type="object">
<APIItem name="align" type="string">
媒体元素的对齐方式。
</APIItem>
<APIItem name="focus" type="boolean">
媒体元素是否当前获得焦点。
</APIItem>
<APIItem name="selected" type="boolean">
媒体元素是否当前被选中。
</APIItem>
<APIItem name="readOnly" type="boolean">
编辑器是否处于只读模式。
</APIItem>
<APIItem name="embed" type="EmbedUrlData">
媒体元素的解析嵌入数据。
</APIItem>
<APIItem name="isTweet" type="boolean">
媒体元素是否为推文。
</APIItem>
<APIItem name="isVideo" type="boolean">
媒体元素是否为视频。
</APIItem>
<APIItem name="isYoutube" type="boolean">
媒体元素是否为 YouTube 视频。
</APIItem>
</APIReturns>
</API>
useMediaToolbarButton媒体工具栏按钮的行为钩子。
<API name="useMediaToolbarButton"> <APIParameters> <APIItem name="options.nodeType" type="string" optional> 要插入的媒体节点类型。 </APIItem> </APIParameters> <APIReturns type="object"> <APIItem name="props.onClick" type="() => void"> 插入媒体节点并使编辑器获得焦点的回调函数。 </APIItem> </APIReturns> </API>useFloatingMediaEditButton处理浮动媒体编辑按钮。
<API name="useFloatingMediaEditButton"> <APIReturns type="object"> <APIItem name="props.onClick" type="() => void"> 处理按钮点击的回调函数。 </APIItem> </APIReturns> </API>useFloatingMediaUrlInput处理媒体元素的 URL 输入字段。
<API name="useFloatingMediaUrlInput"> <APIProps> <APIItem name="defaultValue" type="string"> URL 输入字段的默认值。 </APIItem> </APIProps> <APIReturns type="object"> <APIItem name="props.onChange" type="() => void"> 处理输入变化的回调函数。 </APIItem> <APIItem name="props.autoFocus" type="boolean"> URL 输入字段是否应在挂载时获得焦点。 </APIItem> <APIItem name="props.defaultValue" type="string"> URL 输入字段的默认值。 </APIItem> </APIReturns> </API>useImage图片元素的钩子。
<API name="useImage"> <APIReturns type="object"> <APIItem name="props.src" type="string"> 媒体元素的 URL。 </APIItem> <APIItem name="props.alt" type="string"> 图片的说明文字。 </APIItem> <APIItem name="props.draggable" type="boolean"> 图片是否可拖动。 </APIItem> </APIReturns> </API>parseMediaUrl解析媒体 URL 以进行插件特定的处理。
<API name="parseMediaUrl"> <APIParameters> <APIItem name="options.pluginKey" type="string"> 媒体插件的键。 </APIItem> <APIItem name="options.url" type="string" optional> 要解析的媒体 URL。 </APIItem> </APIParameters> </API>parseVideoUrl解析视频 URL 并提取视频 ID 和提供商特定的嵌入 URL。
<API name="parseVideoUrl"> <APIParameters> <APIItem name="url" type="string"> 要解析的视频 URL。 </APIItem> </APIParameters> <APIReturns type="EmbedUrlData | undefined"> 如果解析成功,返回包含视频 ID 和提供商的对象;如果 URL 无效或不支持,则返回 undefined。 </APIReturns> </API>parseTwitterUrl解析 Twitter URL 并提取推文 ID。
<API name="parseTwitterUrl"> <APIParameters> <APIItem name="url" type="string"> Twitter URL。 </APIItem> </APIParameters> <APIReturns> <APIItem type="EmbedUrlData | undefined"> 如果解析成功,返回包含推文 ID 和提供商的对象。 如果 URL 无效或不匹配任何支持的视频提供商,则返回 undefined。 </APIItem> </APIReturns> </API>parseIframeUrl解析 iframe 嵌入的 URL。
<API name="parseIframeUrl"> <APIParameters> <APIItem name="url" type="string"> iframe 的 URL 或嵌入代码。 </APIItem> </APIParameters> </API>isImageUrl检查 URL 是否为有效的图片 URL。
<API name="isImageUrl"> <APIParameters> <APIItem name="url" type="string"> 要检查的 URL。 </APIItem> </APIParameters> <APIReturns type="boolean"> URL 是否为有效的图片 URL。 </APIReturns> </API>submitFloatingMedia提交浮动媒体元素。
<API name="submitFloatingMedia"> <APIParameters> <APIItem name="options.element" type="TMediaElement"> 要提交的浮动媒体元素。 </APIItem> <APIItem name="options.pluginKey" type="string" optional> 媒体插件的键。 </APIItem> </APIParameters> </API>withImageUpload为编辑器实例添加图片上传功能。
<API name="withImageUpload"> <APIParameters> <APIItem name="plugin" type="PlatePlugin"> Plate 插件。 </APIItem> </APIParameters> </API>withImageEmbed为编辑器实例添加图片相关功能。
<API name="withImageEmbed"> <APIParameters> <APIItem name="plugin" type="PlatePlugin"> Plate 插件。 </APIItem> </APIParameters> </API>TMediaElementexport interface TMediaElement extends TElement {
url: string;
id?: string;
align?: 'center' | 'left' | 'right';
isUpload?: boolean;
name?: string;
placeholderId?: string;
}
TPlaceholderElementexport interface TPlaceholderElement extends TElement {
mediaType: string;
}
EmbedUrlDataexport interface EmbedUrlData {
url?: string;
provider?: string;
id?: string;
component?: React.FC<EmbedUrlData>;
}
║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
╚══════════════════════════════════════════════════════════════════════════════════════════════╝