# @itshixun/qckeditor-vue3

Vue 3 组件库，用于 CKEditor 5 富文本编辑器。

## 安装

```bash
npm install @itshixun/qckeditor-vue3
# 或
pnpm add @itshixun/qckeditor-vue3
# 或
yarn add @itshixun/qckeditor-vue3
```

### 对等依赖

本库需要以下对等依赖：

```bash
npm install vue@^3.0.0 ckeditor5@>=42.0.0
```

## 使用

### 引入样式

在使用组件前，需要先引入 CKEditor 的样式文件：

```typescript
import 'ckeditor5/ckeditor5.css';
```

### 基础编辑器（CKEditor）

底层 CKEditor 封装组件，支持任意 CKEditor 构建：

```vue
<template>
  <CKEditor v-model="content" :editor="ClassicEditor" :config="editorConfig" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { ClassicEditor } from 'ckeditor5';
import { CKEditor } from '@itshixun/qckeditor-vue3';
import 'ckeditor5/ckeditor5.css';

const content = ref('');
const editorConfig = {
  toolbar: ['bold', 'italic', 'link', 'bulletedList', 'numberedList'],
};
</script>
```

### 经典编辑器（QCKClassic）

预配置的经典编辑器组件：

```vue
<template>
  <QCKClassic v-model="content" :config="editorConfig" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { QCKClassic } from '@itshixun/qckeditor-vue3';
import 'ckeditor5/ckeditor5.css';

const content = ref('');
const editorConfig = {
  toolbar: ['bold', 'italic', 'link'],
};
</script>
```

### 专业版编辑器（QCKEditorPro）

点击激活的编辑器，提升页面性能：

```vue
<template>
  <QCKEditorPro
    v-model="content"
    :config="editorConfig"
    placeholder="点击编辑内容"
    :min-height="100"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { QCKEditorPro } from '@itshixun/qckeditor-vue3';
import 'ckeditor5/ckeditor5.css';

const content = ref('');
const editorConfig = {
  toolbar: ['bold', 'italic'],
};
</script>
```

### 内容展示（QCKContent）

安全渲染编辑器内容：

```vue
<template>
  <QCKContent :content="htmlContent" />
</template>

<script setup lang="ts">
import { QCKContent } from '@itshixun/qckeditor-vue3';
import 'ckeditor5/ckeditor5.css';

const htmlContent = '<p>Hello <strong>World</strong></p>';
</script>
```

## 上传适配器

本库提供了三种上传适配器，用于处理编辑器中的文件上传（图片、音视频等）：

### 1. XHR 上传适配器（createXHRUploadAdapter）

基于 XMLHttpRequest 的标准上传方式，适用于大多数后端直传场景：

```ts
import { FileRepository } from 'ckeditor5';
import { createXHRUploadAdapter } from '@itshixun/qckeditor-vue3';

const editorConfig = {
  // ...其他配置
  extraPlugins: [
    function CustomUploadAdapterPlugin(editor) {
      editor.plugins.get(FileRepository).createUploadAdapter = (loader) => {
        return createXHRUploadAdapter(loader, {
          uploadUrl: 'https://api.example.com/upload',
          headers: { Authorization: 'Bearer token' },
          withCredentials: true,
          fieldName: 'file',
          extraFormData: { category: 'images' },
        });
      };
    },
  ],
};
```

### 2. 自定义异步上传适配器（createAsyncUploadAdapter）

支持自定义异步上传流程，适用于 S3、华为云 OBS 等需要多步上传的场景：

```ts
import { FileRepository } from 'ckeditor5';
import { createAsyncUploadAdapter } from '@itshixun/qckeditor-vue3';

const editorConfig = {
  extraPlugins: [
    function CustomUploadAdapterPlugin(editor) {
      editor.plugins.get(FileRepository).createUploadAdapter = (loader) => {
        return createAsyncUploadAdapter(loader, async (file, updateProgress) => {
          // 示例：上传到对象存储（预签名 URL 模式）

          // 第1步：从后端获取预签名上传 URL
          const presignRes = await fetch('/api/presign-upload', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ filename: file.name, contentType: file.type }),
          });
          const { uploadUrl, finalUrl } = await presignRes.json();

          // 第2步：直传到对象存储（使用 XMLHttpRequest 以便监听进度）
          await new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open('PUT', uploadUrl, true);
            xhr.setRequestHeader('Content-Type', file.type);

            xhr.upload.addEventListener('progress', (evt) => {
              if (evt.lengthComputable && updateProgress) {
                updateProgress(Math.round((evt.loaded / evt.total) * 100));
              }
            });
            xhr.addEventListener('load', () => {
              xhr.status >= 200 && xhr.status < 300 ? resolve(void 0) : reject('上传失败');
            });
            xhr.addEventListener('error', () => reject('上传出错'));
            xhr.send(file);
          });

          // 第3步：返回最终访问 URL
          return finalUrl;
        });
      };
    },
  ],
};
```

也可以返回 `UploadResponse` 对象：

```ts
createAsyncUploadAdapter(loader, async (file, updateProgress) => {
  // ...上传逻辑

  return {
    default: 'https://cdn.example.com/image.jpg',  // 默认 URL（必须）
    url: 'https://cdn.example.com/image.jpg',      // 可选
    // 还可以返回其他字段
  };
});
```

### 3. Mock 上传适配器（createMockUploadAdapter）

用于演示环境，模拟上传过程和进度更新：

```ts
import { FileRepository } from 'ckeditor5';
import { createMockUploadAdapter } from '@itshixun/qckeditor-vue3';

const editorConfig = {
  extraPlugins: [
    function CustomUploadAdapterPlugin(editor) {
      editor.plugins.get(FileRepository).createUploadAdapter = (loader) => {
        return createMockUploadAdapter(loader);
      };
    },
  ],
};
```

## Props

### CKEditor

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| editor | Function | 必填 | CKEditor 构建类 |
| config | Object | {} | 编辑器配置 |
| tagName | String | 'div' | 渲染标签 |
| disabled | Boolean | false | 是否禁用 |
| disableTwoWayDataBinding | Boolean | false | 禁用双向绑定 |

### QCKClassic

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| config | Object | {} | 编辑器配置 |
| tagName | String | 'div' | 渲染标签 |
| disabled | Boolean | false | 是否禁用 |
| disableTwoWayDataBinding | Boolean | false | 禁用双向绑定 |

### QCKEditorPro

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| config | Object | {} | 编辑器配置 |
| content | String | - | 初始内容 |
| placeholder | String | '点击编辑内容' | 占位文本 |
| minHeight | Number | 95 | 最小高度(px) |
| height | Number | 0 | 固定高度(px) |
| fitHeight | Boolean | false | 适应父容器高度 |
| contentBgColor | String | 'rgb(0 0 0 / 2%)' | 背景色 |
| contentBgHoverColor | String | 'rgb(0 0 0 / 4%)' | 悬停背景色 |
| borderRadius | Number | 4 | 圆角大小 |
| sanitize | Boolean | true | 是否清洗 HTML |

### QCKContent

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| content | String | - | HTML 内容 |
| containerClass | String | '' | 容器类名 |
| sanitize | Boolean | true | 是否清洗 HTML |

## 事件

所有编辑器组件都支持以下事件：

| 事件 | 参数 | 说明 |
|------|------|------|
| ready | (editor) | 编辑器就绪 |
| destroy | - | 编辑器销毁 |
| blur | (event, editor) | 失去焦点 |
| focus | (event, editor) | 获得焦点 |
| input | (data, event, editor) | 内容变化 |

## 类型导出

```typescript
import type { Props, ExtractEditorType } from '@itshixun/qckeditor-vue3';
```

## 注意事项

1. **CKEditor 授权**: 本库使用 CKEditor 5 的 GPL 授权。如需商业使用，请购买 [CKEditor 商业授权](https://ckeditor.com/pricing/)。

2. **样式文件**: 必须导入 `ckeditor5/ckeditor5.css` 以确保编辑器样式正确。

3. **HTML 清洗**: `QCKContent` 和 `QCKEditorPro` 默认开启 XSS 防护，会自动过滤危险标签和属性。

## License

MIT
