@itshixun/qckeditor-plugin-file
@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';