npm.io
1.0.13 • Published 6d ago

@itshixun/qckeditor-plugin-file

Licence
MIT
Version
1.0.13
Deps
0
Size
47 kB
Vulns
0
Weekly
575

@itshixun/qckeditor-plugin-file

CKEditor 5 插件:通用文件上传。在编辑器中插入可下载的文件链接,支持本地上传和直接插入已有文件,支持多文件上传和自动文件类型识别。

安装

npm install @itshixun/qckeditor-plugin-file
# 或
pnpm add @itshixun/qckeditor-plugin-file

依赖 ckeditor5(peerDependencies,需自行安装)。

基本使用

1. 引入样式

只需引入插件自身的 CSS,文件链接的基础样式会自动包含:

import '@itshixun/qckeditor-plugin-file/dist/index.css';

无需单独安装或引入 @itshixun/qckeditor-plugin-file-display

2. 注册插件

types 为空数组或不配置时,不限制文件类型;配置非空数组时,仅允许指定扩展名。

import { ClassicEditor } from 'ckeditor5';
import { QstFile } from '@itshixun/qckeditor-plugin-file';

const editor = await ClassicEditor.create(element, {
  plugins: [
    // ... 其他插件
    QstFile,
  ],
  toolbar: [
    // ... 其他按钮
    'uploadFile',  // 工具栏按钮名称:上传文件
  ],
  qstFile: {
    upload: {
      types: ['txt', 'rar', 'zip', 'doc', 'docx', 'pdf', 'xls', 'xlsx'], // 限制为以下类型;空数组表示不限制
      maxSize: 100, // 100MB
    },
    progress: {
      showProgressBar: true,
      useReadOnlyLock: false,
    },
  },
});
3. Vue 中使用
<script setup lang="ts">
import { ClassicEditor } from 'ckeditor5';
import { QstFile } from '@itshixun/qckeditor-plugin-file';

const config = {
  editor: ClassicEditor,
  plugins: [/* ... */, QstFile],
  toolbar: ['bold', 'italic', 'uploadFile'],
  qstFile: {
    upload: {
      types: ['pdf', 'docx', 'xlsx'], // 限制为以下类型;空数组表示不限制
      maxSize: 50, // 50MB
    },
  },
};
</script>

<template>
  <ckeditor :editor="config.editor" :config="config" />
</template>

配置项

属性 类型 必填 说明
upload.types string[] 允许上传的文件扩展名列表。默认值为空数组;未配置或传入空数组时,均不限制文件类型
upload.maxSize number 最大文件大小(MB),超出会提示警告
progress.showProgressBar boolean 是否显示上传进度条,默认 true
progress.useReadOnlyLock boolean 上传期间是否锁定编辑器为只读模式,默认 false

上传配置

本插件依赖 CKEditor 5 的 FileRepository 插件处理文件上传。你需要配置一个自定义的 UploadAdapter,否则上传功能无法正常工作。

自定义 UploadAdapter 实现示例
import type { FileLoader, UploadAdapter } from 'ckeditor5';

class MyUploadAdapter implements UploadAdapter {
  private loader: FileLoader;
  private xhr?: XMLHttpRequest;

  constructor(loader: FileLoader) {
    this.loader = loader;
  }

  upload(): Promise<{ default: string }> {
    return this.loader.file.then(
      (file) =>
        new Promise((resolve, reject) => {
          this._initRequest();
          this._initListeners(resolve, reject, file!);
          this._sendRequest(file!);
        })
    );
  }

  abort(): void {
    if (this.xhr) {
      this.xhr.abort();
    }
  }

  private _initRequest(): void {
    const xhr = (this.xhr = new XMLHttpRequest());
    xhr.open('POST', 'https://your-api.com/upload', true);
    xhr.responseType = 'json';
  }

  private _initListeners(
    resolve: (value: { default: string }) => void,
    reject: (reason?: string) => void,
    file: File
  ): void {
    const xhr = this.xhr!;
    const loader = this.loader;
    const genericErrorText = `无法上传文件: ${file.name}.`;

    xhr.addEventListener('error', () => reject(genericErrorText));
    xhr.addEventListener('abort', () => reject());
    xhr.addEventListener('load', () => {
      const response = xhr.response;

      if (!response || response.error) {
        return reject(response?.error?.message || genericErrorText);
      }

      // 响应必须包含 default 字段(文件 URL)
      resolve({ default: response.url });
    });

    if (xhr.upload) {
      xhr.upload.addEventListener('progress', (evt) => {
        if (evt.lengthComputable) {
          loader.uploadTotal = evt.total;
          loader.uploaded = evt.loaded;
        }
      });
    }
  }

  private _sendRequest(file: File): void {
    const data = new FormData();
    data.append('file', file);
    this.xhr!.send(data);
  }
}
在编辑器中注册 UploadAdapter
function MyUploadAdapterPlugin(editor: any) {
  editor.plugins.get('FileRepository').createUploadAdapter = (loader: FileLoader) => {
    return new MyUploadAdapter(loader);
  };
}

const editor = await ClassicEditor.create(element, {
  plugins: [
    // ... 其他插件
    MyUploadAdapterPlugin, // 必须先注册,再注册 QstFile
    QstFile,
  ],
  toolbar: ['uploadFile'],
});

重要:

  • MyUploadAdapterPlugin 必须在 QstFile 之前注册到 plugins 数组中
  • 上传响应必须返回 { default: '文件URL' } 格式,default 字段会被提取为文件的 url
  • 上传失败时会自动移除占位文件元素,并通过 Notification 插件显示警告

命令说明

插件注册了两个命令,分别对应不同的使用场景:

fileInsert — 插入已有文件

通过文件 URL 直接插入已有文件(不上传):

editor.execute('fileInsert', {
  name: '示例文档.pdf',
  url: 'https://example.com/files/doc.pdf',
  filetype: 'pdf', // 可选,默认 'document'
});
uploadFile — 上传本地文件

通常由工具栏按钮自动触发,也可手动调用。支持多文件上传:

const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const files = Array.from(fileInput.files!);

editor.execute('uploadFile', { file: files });

输出 HTML

保存态(Data Downcast)
<a class="ck-file ck-qst-file ck-qst-file-pdf ck-qst-file--has-icon"
   href="https://example.com/files/doc.pdf"
   download="download"
   data-filetype="pdf">
   <span class="ck-qst-file-icon"><svg>...</svg></span>
   示例文档.pdf
</a>
编辑态(Editing Downcast)

编辑态会渲染为 CKEditor 5 的 Widget,带有选中高亮和进度条:

<a class="ck-file ck-qst-file ck-qst-file-pdf ck-qst-file--has-icon ck-widget"
   href="https://example.com/files/doc.pdf"
   download="download"
   data-filetype="pdf"
   contenteditable="false">
   <span class="ck-qst-file-icon"><svg>...</svg></span>
   示例文档.pdf
   <!-- 上传时显示进度条 -->
   <div class="ck-qst-file-progress-bar" style="width: 45%"></div>
</a>
文件类型 class

插件根据 filetype 属性在 class 中添加类型标识,方便自定义不同文件类型的样式:

文件类型 class 后缀
pdf ck-qst-file-pdf
doc / docx ck-qst-file-doc
xls / xlsx ck-qst-file-xls
ppt ck-qst-file-ppt
rar ck-qst-file-rar
img ck-qst-file-img
video ck-qst-file-video
music ck-qst-file-music
zip ck-qst-file-zip
其他 ck-qst-file-document

文件链接会根据 filetype 或文件名扩展名自动显示对应 SVG 图标;压缩文件(zip/rar/tar/7z 等)显示 zip.svg;无法识别时显示 unknown.svg。对于没有图标元素的历史内容,插件样式会提供 📎 emoji 后备。

数据兼容性

插件在 upcast(粘贴/加载 HTML)时兼容旧版格式:

新版本 旧版本
<a class="ck-qst-file"> <a class="ck-file">

旧版 HTML 会被自动转换为新版数据模型,保存时输出新版格式(同时保留 ck-file 类以兼容旧样式):

<!-- 旧版输入 -->
<a class="ck-file" href="..." download data-filetype="pdf">文档.pdf</a>

<!-- 新版输出 -->
<a class="ck-file ck-qst-file ck-qst-file-pdf" href="..." download data-filetype="pdf">文档.pdf</a>

注:从 v1.0.9 起,文件链接的视觉渲染(图标、文件名、基础样式)迁移至共享包 @itshixun/qckeditor-plugin-file-display。data downcast 仍保留 ck-file 旧类名,upcast 仍兼容旧版 <a class="ck-file">,已有内容无需改动。当与 qckeditor-plugin-select-resource 同时启用时,file 插件识别带 ck-qst-file 类但不含 ck-qst-file--resource 类的元素,不会将 select-resource 输出的 ck-qst-file--resource 内容识别为文件。

类型支持

导入插件后,EditorConfig 类型自动扩展,TypeScript 可直接识别 qstFile 配置:

const config: EditorConfig = {
  // 无类型错误,qstFile 已声明
  qstFile: {
    upload: { types: ['pdf'], maxSize: 1 }, // 1MB
  },
};

导出 API

import {
  QstFile,              // 主插件(Editing + Upload)
  QstFileEditing,       // 编辑插件(Schema + Conversion + fileInsert 命令)
  QstFileCommand,       // 插入已有文件的命令
  QstFileUpload,        // 上传功能聚合插件
  QstFileUploadEditing, // 上传编辑插件(自动上传处理 + 进度管理)
  QstFileUploadUI,      // 上传 UI 插件(工具栏按钮)
  QstFileUploadProgress, // 上传进度条插件
  QstFileUploadCommand, // 上传文件的命令
  insertFile,           // 工具函数:插入文件元素
  isFileAllowed,        // 工具函数:检查是否允许插入文件
  getFileType,          // 工具函数:根据 File 对象识别文件类型
  getIconType,          // 工具函数:根据 filetype 或文件名扩展名映射图标键
  type QstFileConfig,
  type FileInsertCommandOptions,
  type UploadFileCommandOptions,
} from '@itshixun/qckeditor-plugin-file';

Keywords