Loading...

功能特性

媒体支持

  • 文件类型:
    • 图片
    • 视频
    • 音频
    • 其他 (PDF, Word等)
  • 视频平台:
    • 本地视频文件
    • YouTube, Vimeo, Dailymotion, Youku, Coub
  • 嵌入支持:
    • 推特推文

媒体功能

  • 可编辑的标题说明
  • 可调整大小的元素

上传功能

  • 多种上传方式:
    • 工具栏按钮文件选择器
    • 从文件系统拖放
    • 从剪贴板粘贴(图片)
    • 外部媒体URL嵌入
  • 上传体验:
    • 实时进度跟踪
    • 上传过程中预览
    • 上传或嵌入完成后自动将占位符转换为相应的媒体元素(图片/视频/音频/文件)
    • 错误处理
    • 文件大小验证
    • 类型验证

套件使用

安装

最快捷的媒体支持方式是使用MediaKit,它包含预配置的ImagePluginVideoPluginAudioPluginFilePluginMediaEmbedPluginPlaceholderPluginCaptionPlugin及其Plate UI组件。

添加套件

将套件添加到你的插件中:

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:

.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图片元素的插件。

OptionsImagePluginOptions

  • 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

管理上传过程中媒体占位符的插件。处理文件上传、拖放和剪贴板粘贴事件。

Optionsobject

Collapse all
  • 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

跟踪当前正在上传的文件。

Parameters

Collapse all
  • id string

    占位符元素的唯一标识符。

  • file File

    正在上传的文件。

api.placeholder.getUploadingFile

获取当前正在上传的文件。

Parameters

Collapse all
  • id string

    占位符元素的唯一标识符。

Returns

Collapse all
  • File | undefined

    如果找到则返回上传文件,否则返回undefined。

api.placeholder.removeUploadingFile

上传完成或失败后从上传跟踪状态中移除文件。

Parameters

Collapse all
  • id string

    要移除的占位符元素的唯一标识符。

转换方法

tf.insert.media

使用上传占位符将媒体文件插入编辑器。

Parameters

Collapse all
  • files FileList

    要上传的文件。根据配置的文件类型和限制进行验证。

  • options optional object

    插入节点的转换选项。

Optionsobject

Collapse all
  • at optional Path

    插入媒体的位置。默认为当前选区。

  • nextBlock optional boolean

    是否在媒体后插入新块。

    • 默认: true

根据配置的限制(大小、数量、类型)验证文件,为每个文件创建占位符元素,处理多个文件顺序上传,维护撤销/重做操作的上传历史记录,如果验证失败则触发错误处理。

错误代码:

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

在编辑器中插入图片元素。

Parameters

Collapse all
  • url string | ArrayBuffer

    图片的 URL 或 ArrayBuffer。

  • options optional InsertNodesOptions

    插入图片元素的额外选项。

OptionsInsertImageOptions

Collapse all
  • nextBlock optional boolean

    如果为 true,图片将被插入到下一个块中。

tf.insert.mediaEmbed

在当前选区插入媒体嵌入元素。

OptionsInsertMediaEmbedOptions

Collapse all
  • url optional string

    媒体嵌入的 URL。

    • 默认值: ''
  • key optional string

    媒体嵌入元素的键。

    • 默认值: KEYS.mediaEmbed
  • insertNodesOptions optional InsertNodesOptions

    插入节点的额外选项。

Hooks

useResizable

处理媒体元素的可调整大小属性。

State

Collapse all
  • 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' 或像素)。

Returnsobject

Collapse all
  • wrapperRef React.RefObject<HTMLDivElement>

    最外层包装 div 的 React 引用。

  • wrapperProps.style CSSProperties

    包装 div 的 CSS 样式。

  • props.style CSSProperties

    可调整大小元素的 CSS 样式。

  • context.onResize () => void

    元素调整大小时调用的回调函数。

useMediaState

媒体元素的状态钩子。

Parameters

Collapse all
  • options.urlParsers optional EmbedUrlParser[]

    用于解析媒体元素 URL 的 URL 解析器数组。

    • EmbedUrlParser: (url: string) => EmbedUrlData | undefined

Returnsobject

Collapse all
  • align string

    媒体元素的对齐方式。

  • focus boolean

    媒体元素是否当前获得焦点。

  • selected boolean

    媒体元素是否当前被选中。

  • readOnly boolean

    编辑器是否处于只读模式。

  • embed EmbedUrlData

    媒体元素的解析嵌入数据。

  • isTweet boolean

    媒体元素是否为推文。

  • isVideo boolean

    媒体元素是否为视频。

  • isYoutube boolean

    媒体元素是否为 YouTube 视频。

useMediaToolbarButton

媒体工具栏按钮的行为钩子。

Parameters

Collapse all
  • options.nodeType optional string

    要插入的媒体节点类型。

Returnsobject

Collapse all
  • props.onClick () => void

    插入媒体节点并使编辑器获得焦点的回调函数。

useFloatingMediaEditButton

处理浮动媒体编辑按钮。

Returnsobject

Collapse all
  • props.onClick () => void

    处理按钮点击的回调函数。

useFloatingMediaUrlInput

处理媒体元素的 URL 输入字段。

Props

Collapse all
  • defaultValue string

    URL 输入字段的默认值。

Returnsobject

Collapse all
  • props.onChange () => void

    处理输入变化的回调函数。

  • props.autoFocus boolean

    URL 输入字段是否应在挂载时获得焦点。

  • props.defaultValue string

    URL 输入字段的默认值。

useImage

图片元素的钩子。

Returnsobject

Collapse all
  • props.src string

    媒体元素的 URL。

  • props.alt string

    图片的说明文字。

  • props.draggable boolean

    图片是否可拖动。

工具函数

parseMediaUrl

解析媒体 URL 以进行插件特定的处理。

Parameters

Collapse all
  • options.pluginKey string

    媒体插件的键。

  • options.url optional string

    要解析的媒体 URL。

parseVideoUrl

解析视频 URL 并提取视频 ID 和提供商特定的嵌入 URL。

Parameters

Collapse all
  • url string

    要解析的视频 URL。

ReturnsEmbedUrlData | undefined

    如果解析成功,返回包含视频 ID 和提供商的对象;如果 URL 无效或不支持,则返回 undefined。

parseTwitterUrl

解析 Twitter URL 并提取推文 ID。

Parameters

Collapse all
  • url string

    Twitter URL。

Returns

Collapse all
  • EmbedUrlData | undefined

    如果解析成功,返回包含推文 ID 和提供商的对象。 如果 URL 无效或不匹配任何支持的视频提供商,则返回 undefined。

parseIframeUrl

解析 iframe 嵌入的 URL。

Parameters

Collapse all
  • url string

    iframe 的 URL 或嵌入代码。

isImageUrl

检查 URL 是否为有效的图片 URL。

Parameters

Collapse all
  • url string

    要检查的 URL。

Returnsboolean

    URL 是否为有效的图片 URL。

submitFloatingMedia

提交浮动媒体元素。

Parameters

Collapse all
  • options.element TMediaElement

    要提交的浮动媒体元素。

  • options.pluginKey optional string

    媒体插件的键。

withImageUpload

为编辑器实例添加图片上传功能。

Parameters

Collapse all
  • plugin PlatePlugin

    Plate 插件。

withImageEmbed

为编辑器实例添加图片相关功能。

Parameters

Collapse all
  • plugin PlatePlugin

    Plate 插件。

类型

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>;
}