# KC Plate Editor

基于 [Plate.js](https://platejs.org/) 构建的功能丰富的富文本编辑器 SDK。

> **私有项目** - 本项目仅供内部使用，未经授权不得分发。

## 版本信息

| 版本 | 状态 | 说明 |
|------|------|------|
| v0.6.1 | ✅ 最新版本 | Bug 修复、代码质量、单元测试、依赖升级 |
| v0.6.0 | ✅ 稳定版本 | 全面升级：AI SDK 6.x、Vite 7.x、依赖更新 |
| v0.5.8 | ✅ 稳定版本 | 内容格式转换优化（官方 API） |
| v0.5.6 | ✅ 稳定版本 | 增强上传配置灵活性 |
| v0.5.5 | ✅ 稳定版本 | 快捷键保存、本地存储、目录跳转优化 |
| v0.5.0 | ✅ 已发布私服 | 基础稳定版本 |

### v0.6.0 更新内容

- **依赖全面升级** - 更新至最新稳定版本
  - `ai` 5.0.28 → 6.0.45（重大升级）
  - `@ai-sdk/react` 2.0.28 → 3.0.47（重大升级）
  - `vite` 5.4.11 → 7.3.1（重大升级）
  - `platejs` 52.0.1 → 52.0.17
  - `@platejs/ai` 52.0.6 → 52.1.0
  - `@platejs/markdown` 52.0.4 → 52.1.0
  - `typescript` 5.9.2 → 5.9.3
  - `tailwindcss` 4.1.13 → 4.1.18
  - `zod` 4.1.12 → 4.3.5
  - `zustand` 5.0.8 → 5.0.10
- **代码精简** - 移除 use-chat.ts 中约 1300 行的测试代码
- **配置清理** - 移除废弃配置属性（uploadToken, authorization）
- **类型优化** - 优化插件类型定义，改进 EditorPluginKit 类型
- **HTTP 工具优化** - 简化请求头合并逻辑，移除兼容代码
- **导出顺序修复** - 修复 package.json exports 中 types 字段顺序

### v0.5.8 更新内容

- **内容格式转换** - 基于 Plate.js 官方 API 实现 JSON/HTML/Markdown 互转
- **统一内容接口** - 新增 `getContentAs()` 和 `setContentFrom()` 方法
- **官方 serializeHtml** - HTML 序列化使用官方静态渲染 API
- **官方 MarkdownPlugin** - Markdown 转换使用官方 `@platejs/markdown`
- **代码清理** - 移除旧的 DOM 清理方案，统一使用官方 API

### v0.5.6 更新内容

- **增强上传配置** - 新增 `UploadConfig` 详细配置接口
- **增强 AI 配置** - 新增 `AIConfig` 详细配置接口
- **自定义上传函数** - 支持 `customUpload` 完全自定义上传逻辑
- **自定义 AI 函数** - 支持 `customCopilot` 和 `customCommand` 自定义 AI 逻辑
- **文件验证** - 支持 `maxSize` 和 `acceptTypes` 文件验证
- **额外表单数据** - 支持 `extraData` 添加额外表单字段
- **灵活响应解析** - 支持多种服务端响应格式
- **配置辅助函数** - 新增 `getUploadConfig`、`getAiConfig` 等辅助函数

### v0.5.5 更新内容

- **快捷键保存** - 支持 `Ctrl+S` / `Cmd+S` 快捷键保存
- **本地存储** - 新增 `saveToLocalStorage()` 和 `clearLocalStorage()` 方法
- **目录跳转优化** - 修复目录点击跳转不准确的问题
- **onSave 回调** - 新增 `onSave` 属性用于处理快捷键保存事件

### v0.5.4 更新内容

- 修复编辑器容器双滚动条问题
- 优化 `EditorContainer` 的 `demo` 变体高度设置
- 修复工具栏吸顶在嵌套滚动容器中失效的问题

### v0.5.0 更新内容

- 优化字数统计、查找替换、目录功能，使用官方 Plate.js API
- 新增公共工具函数 `text-utils.ts`
- 统一常量管理（`HEADING_DEPTH`、`FONT_STACKS` 等）
- 重构示例页面为模块化结构
- 删除重复的演示页面，整合到 Examples

---

## 目录

- [特性](#特性)
- [安装](#安装)
- [快速开始](#快速开始)
- [集成注意事项](#集成注意事项) ⭐ **重要**
- [工具栏配置](#工具栏配置)
- [内容操作](#内容操作)
- [导出功能](#导出功能)
- [高级功能](#高级功能)
- [API 参考](#api-参考)
- [开发指南](#开发指南)

---

## 特性

### 核心功能

- **富文本编辑** - 标题、列表、表格、代码块等
- **AI 辅助** - 内置 AI 写作助手
- **拖拽排序** - 块级元素拖拽
- **Markdown** - 导入/导出 Markdown
- **多媒体** - 图片、视频、嵌入内容
- **协作** - 评论和建议模式
- **国际化** - 中英文支持
- **响应式** - 自适应工具栏

### 工具栏系统

- **可配置工具栏** - 灵活的工具栏配置
- **预设模板** - 完整/基础/极简/文档等预设
- **细粒度控制** - 精确控制每个功能

### 扩展功能

- **查找替换** - 支持大小写敏感
- **字数统计** - 中英文混合统计
- **文档大纲** - 自动生成导航
- **导出功能** - HTML/Markdown/PDF/图片
- **目录功能** - 左侧/右侧目录面板
- **自定义按钮** - 扩展工具栏

---

## 安装

### 从私服安装

```bash
# 配置私服（如果尚未配置）
npm config set registry https://your-private-registry.com

# 安装
npm install kc-plate-editor
# 或
pnpm add kc-plate-editor
```

### 对等依赖

```bash
npm install react react-dom platejs
```

---

## 快速开始

### 1. 引入样式

```tsx
import 'kc-plate-editor/style.css';
```

### 2. 基础用法

```tsx
import { PlateEditor } from 'kc-plate-editor';

function MyEditor() {
  return <PlateEditor />;
}
```

### 3. 受控模式

```tsx
import { useState } from 'react';
import { PlateEditor } from 'kc-plate-editor';
import type { Value } from 'platejs';

function MyEditor() {
  const [value, setValue] = useState<Value>([
    { type: 'p', children: [{ text: '开始编辑...' }] },
  ]);

  return (
    <PlateEditor
      value={value}
      onChange={setValue}
    />
  );
}
```

### 4. 使用 Ref 访问 API

```tsx
import { useRef } from 'react';
import { PlateEditor, type PlateEditorRef } from 'kc-plate-editor';

function MyEditor() {
  const editorRef = useRef<PlateEditorRef>(null);

  const handleSave = () => {
    const content = editorRef.current?.getContent();
    console.log('保存内容:', content);
  };

  return (
    <>
      <button onClick={handleSave}>保存</button>
      <PlateEditor ref={editorRef} />
    </>
  );
}
```

### 5. 配置提供者

```tsx
import { ConfigProvider, PlateEditor } from 'kc-plate-editor';

function App() {
  return (
    <ConfigProvider
      config={{
        locale: 'zh-CN',
        uploadApiUrl: '/api/upload',
        aiApiUrl: '/api/ai/copilot',
        customHeaders: {
          'Authorization': 'Bearer token',
        },
      }}
    >
      <PlateEditor />
    </ConfigProvider>
  );
}
```

### 6. 高级上传配置 (v0.5.6+)

```tsx
import { ConfigProvider, PlateEditor, type UploadConfig } from 'kc-plate-editor';

// 详细的上传配置
const uploadConfig: UploadConfig = {
  url: '/api/upload',           // 上传地址
  fieldName: 'file',            // 文件字段名
  maxSize: 10 * 1024 * 1024,    // 最大 10MB
  acceptTypes: ['image/*', 'video/*', 'application/pdf'],
  headers: {
    'X-Upload-Token': 'your-token',
  },
  extraData: {
    category: 'document',
    source: 'editor',
  },
};

function App() {
  return (
    <ConfigProvider
      config={{
        baseURL: 'https://api.example.com',  // API 基础地址
        upload: uploadConfig,
        locale: 'zh-CN',
      }}
    >
      <PlateEditor />
    </ConfigProvider>
  );
}
```

### 7. 自定义上传函数

```tsx
import { ConfigProvider, PlateEditor, type UploadResult } from 'kc-plate-editor';

// 完全自定义上传逻辑
async function customUpload(
  file: File, 
  onProgress?: (progress: number) => void
): Promise<UploadResult> {
  const formData = new FormData();
  formData.append('file', file);
  
  // 使用你自己的上传逻辑
  const response = await fetch('/api/my-upload', {
    method: 'POST',
    body: formData,
  });
  
  const result = await response.json();
  
  return {
    url: result.fileUrl,
    name: file.name,
    size: file.size,
    type: file.type,
    key: result.fileId,
  };
}

function App() {
  return (
    <ConfigProvider
      config={{
        upload: {
          customUpload,  // 使用自定义上传函数
        },
        locale: 'zh-CN',
      }}
    >
      <PlateEditor />
    </ConfigProvider>
  );
}
```

### 8. AI 配置 (v0.5.6+)

```tsx
import { ConfigProvider, PlateEditor, type AIConfig } from 'kc-plate-editor';

// 详细的 AI 配置
const aiConfig: AIConfig = {
  copilotUrl: '/api/ai/copilot',    // Copilot 自动补全 API
  commandUrl: '/api/ai/command',    // AI 命令/对话 API
  apiKey: 'your-api-key',           // API 密钥
  model: 'gpt-4',                   // 模型名称
  headers: {
    'X-Custom-Header': 'value',
  },
};

function App() {
  return (
    <ConfigProvider
      config={{
        baseURL: 'https://api.example.com',
        ai: aiConfig,
        locale: 'zh-CN',
      }}
    >
      <PlateEditor />
    </ConfigProvider>
  );
}
```

---

## 集成注意事项

> ⚠️ **重要**: 在将 kc-plate-editor 集成到现有项目时，请仔细阅读以下注意事项，避免常见问题。

### 1. 全局样式冲突

**问题**: 项目中的全局 CSS 可能会影响编辑器的显示效果。

**常见冲突**:

```css
/* ❌ 这些全局样式会破坏编辑器的列表显示 */
ul, ol {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* ❌ 这会影响编辑器内的链接样式 */
a {
  text-decoration: none;
  color: inherit;
}
```

**解决方案**:

```css
/* ✅ 方案1: 使用更具体的选择器，排除编辑器区域 */
ul:not([data-slate-node]), ol:not([data-slate-node]) {
  list-style: none;
}

/* ✅ 方案2: 在编辑器容器内重置样式 */
[data-plate-editor-container] ul,
[data-plate-editor-container] ol {
  list-style: revert;
  padding: revert;
  margin: revert;
}

/* ✅ 方案3: 直接注释掉全局样式（推荐） */
/* ul, ol { list-style: none; } */
```

### 2. 容器高度和滚动

**问题**: 编辑器需要明确的高度才能正确滚动。

```tsx
// ❌ 错误：没有设置高度，编辑器会无限增长
<div>
  <PlateEditor />
</div>

// ✅ 正确：设置固定高度或使用 flex 布局
<div style={{ height: 500 }}>
  <PlateEditor />
</div>

// ✅ 正确：使用 flex 布局填充可用空间
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
  <header>...</header>
  <div style={{ flex: 1, overflow: 'hidden' }}>
    <PlateEditor />
  </div>
</div>
```

**Ant Design Card 中使用**:

```tsx
// ✅ 推荐配置：设置 body 的高度和 overflow
<Card 
  style={{ minHeight: 500 }} 
  styles={{ 
    body: { 
      padding: 0, 
      height: 500, 
      display: 'flex', 
      flexDirection: 'column',
      overflow: 'hidden'  // 防止双滚动条
    } 
  }}
>
  <PlateEditor />
</Card>
```

### 3. 工具栏吸顶

**问题**: 工具栏吸顶需要正确的滚动容器。

```tsx
// ❌ 错误：外层容器有 overflow: auto，会创建新的滚动上下文
<div style={{ overflow: 'auto', height: 500 }}>
  <PlateEditor toolbarSticky={true} />
</div>

// ✅ 正确：外层容器使用 overflow: hidden
<div style={{ overflow: 'hidden', height: 500 }}>
  <PlateEditor toolbarSticky={true} />
</div>
```

**独立工具栏场景**:

```tsx
// 使用 StandaloneToolbar 时，需要自己管理滚动容器
import { StandaloneToolbar, PlateEditor } from 'kc-plate-editor';

// 滚动容器
<div className="scroll-container" style={{ overflow: 'auto', height: 500 }}>
  {/* 工具栏使用 CSS sticky */}
  <div style={{ position: 'sticky', top: 0, zIndex: 50 }}>
    <StandaloneToolbar config={config} editor={editor} sticky={false} />
  </div>
  
  <PlateEditor showToolbar={false} />
</div>
```

### 4. 快捷键保存

**v0.5.5 新增**: 支持 `Ctrl+S` / `Cmd+S` 快捷键保存。

```tsx
<PlateEditor
  // 方式1: 使用 onSave 回调处理保存逻辑
  onSave={(content) => {
    console.log('快捷键保存:', content);
    // 调用 API 保存到服务器
    saveToServer(content);
  }}
  
  // 方式2: 启用自动保存到 localStorage
  autoSave={true}
  autoSaveKey="my-editor-content"
  autoSaveInterval={5000}  // 5秒自动保存一次
/>
```

**清理本地存储**:

```tsx
// 发布或退出时清理临时数据
const handlePublish = async () => {
  await publishDocument();
  editorRef.current?.clearLocalStorage();
};

const handleExit = () => {
  editorRef.current?.clearLocalStorage();
  navigate('/');
};
```

### 5. 目录跳转

**问题**: 目录点击跳转不到正确位置。

**原因**: 编辑器使用了嵌套滚动容器，`scrollIntoView` 可能无法正确工作。

**v0.5.5 已修复**: 目录跳转现在会自动查找正确的滚动容器。

```tsx
// 如果仍有问题，确保编辑器容器结构正确
<div style={{ height: 500, overflow: 'hidden' }}>
  <PlateEditor 
    showToc={true}
    tocPosition="right"
    tocWidth={240}
  />
</div>
```

### 6. 表单集成

**在 Ant Design Form 中使用**:

```tsx
import { Form, Input, Button } from 'antd';
import { PlateEditor, ConfigProvider, EditorErrorBoundary } from 'kc-plate-editor';

function ArticleForm() {
  const editorRef = useRef<PlateEditorRef>(null);
  
  const handleSubmit = async () => {
    const content = editorRef.current?.getContent();
    const markdown = editorRef.current?.getMarkdown();
    const html = await editorRef.current?.getHtml();
    
    // 提交到服务器
    await submitArticle({ content, markdown, html });
  };

  return (
    <Form onFinish={handleSubmit}>
      <Form.Item label="标题" name="title">
        <Input />
      </Form.Item>
      
      <Form.Item label="内容">
        {/* 添加边框样式 */}
        <div style={{ border: '1px solid #d9d9d9', borderRadius: 8, overflow: 'hidden' }}>
          <ConfigProvider config={{ locale: 'zh-CN' }}>
            <EditorErrorBoundary>
              <PlateEditor
                ref={editorRef}
                toolbarConfig={BASIC_TOOLBAR}
                toolbarSticky={false}  // 表单中不需要吸顶
                showSettings={false}
              />
            </EditorErrorBoundary>
          </ConfigProvider>
        </div>
      </Form.Item>
      
      <Button type="primary" htmlType="submit">提交</Button>
    </Form>
  );
}
```

### 7. 全屏编辑模式

**实现全屏沉浸式编辑**:

```tsx
function FullScreenEditor() {
  return (
    <div style={{ 
      height: '100vh', 
      display: 'flex', 
      flexDirection: 'column',
      background: '#fff',
    }}>
      {/* 顶部导航栏 */}
      <div style={{ 
        padding: '8px 16px', 
        borderBottom: '1px solid #e8e8e8',
        flexShrink: 0,
      }}>
        <span>文档标题</span>
        <Button onClick={handleSave}>保存</Button>
      </div>
      
      {/* 编辑器区域 - flex: 1 填充剩余空间 */}
      <div style={{ flex: 1, overflow: 'hidden' }}>
        <PlateEditor
          toolbarConfig={FULL_TOOLBAR}
          toolbarSticky={true}
          toolbarAlign="center"
          contentMaxWidth={900}
          showToc={true}
          tocPosition="right"
          autoFocus
        />
      </div>
    </div>
  );
}
```

### 8. 错误边界

**始终使用 EditorErrorBoundary 包裹编辑器**:

```tsx
import { EditorErrorBoundary, PlateEditor } from 'kc-plate-editor';

// ✅ 推荐：使用错误边界防止编辑器崩溃影响整个页面
<EditorErrorBoundary>
  <PlateEditor />
</EditorErrorBoundary>
```

### 9. 性能优化

**避免不必要的重渲染**:

```tsx
// ❌ 错误：每次渲染都创建新的配置对象
<PlateEditor
  toolbarConfig={{ ...FULL_TOOLBAR, history: false }}
/>

// ✅ 正确：使用 useMemo 缓存配置
const toolbarConfig = useMemo(() => ({
  ...FULL_TOOLBAR,
  history: false,
}), []);

<PlateEditor toolbarConfig={toolbarConfig} />
```

**预设切换时重新挂载编辑器**:

```tsx
// 使用 key 强制重新挂载，确保工具栏配置正确应用
<PlateEditor
  key={`editor-${activePreset}`}
  toolbarConfig={getToolbarConfig()}
/>
```

### 10. 常见问题排查

| 问题 | 可能原因 | 解决方案 |
|------|----------|----------|
| 列表没有项目符号 | 全局 CSS 重置了 list-style | 移除或修改全局样式 |
| 双滚动条 | 容器和编辑器都有 overflow | 外层使用 `overflow: hidden` |
| 工具栏不吸顶 | 滚动容器不正确 | 检查父容器的 overflow 设置 |
| 目录跳转失效 | 嵌套滚动容器 | 升级到 v0.5.5 |
| 快捷键无效 | 焦点不在编辑器 | 确保编辑器已聚焦 |
| 内容丢失 | 未保存就离开 | 使用 autoSave 或 onSave |

---

## 工具栏配置

### 使用预设

```tsx
import {
  PlateEditor,
  FULL_TOOLBAR,      // 完整工具栏
  BASIC_TOOLBAR,     // 基础工具栏
  MINIMAL_TOOLBAR,   // 极简工具栏
  DOCUMENT_TOOLBAR,  // 文档工具栏
} from 'kc-plate-editor';

<PlateEditor toolbarConfig={FULL_TOOLBAR} />
<PlateEditor toolbarConfig={BASIC_TOOLBAR} />
<PlateEditor toolbarConfig={MINIMAL_TOOLBAR} />
```

### 自定义配置

```tsx
const customToolbar: ToolbarConfig = {
  history: true,
  textFormat: ['bold', 'italic', 'underline', 'strikethrough', 'code'],
  color: ['textColor', 'backgroundColor', 'highlight'],
  align: ['left', 'center', 'right', 'justify'],
  list: ['bulleted', 'numbered', 'todo'],
  insert: ['link', 'image', 'table', 'emoji'],
  typography: ['fontSize', 'lineHeight'],
  blockOps: ['indent', 'outdent'],
  advanced: ['export', 'import'],
};

<PlateEditor toolbarConfig={customToolbar} />
```

### 工具栏样式

```tsx
<PlateEditor
  toolbarAlign="center"
  toolbarFullWidth={true}
  toolbarClassName="custom-class"
  toolbarStyle={{ padding: '8px' }}
/>
```

---

## 内容操作

### 获取/设置内容

```tsx
// 获取 JSON 内容
const value = editorRef.current?.getContent();

// 设置 JSON 内容
editorRef.current?.setContent([
  { type: 'h1', children: [{ text: '标题' }] },
  { type: 'p', children: [{ text: '段落内容' }] },
]);

// 重置内容
editorRef.current?.resetContent();
```

### Markdown 转换

```tsx
// 获取 Markdown
const markdown = editorRef.current?.getMarkdown();

// 设置 Markdown
editorRef.current?.setMarkdown('# 标题\n\n这是段落内容');

// 插入 Markdown
editorRef.current?.insertMarkdown('**粗体文本**');
```

### HTML 转换

```tsx
// 获取 HTML
const html = await editorRef.current?.getHtml();

// 设置 HTML
editorRef.current?.setHtml('<h1>标题</h1><p>段落内容</p>');
```

### 查找替换

```tsx
// 查找文本
const matches = editorRef.current?.find('搜索文本', {
  caseSensitive: false,
});
console.log(`找到 ${matches.length} 处匹配`);

// 替换文本
const count = editorRef.current?.replace('旧文本', '新文本', {
  caseSensitive: false,
  replaceAll: true,
});
console.log(`已替换 ${count} 处`);
```

---

## 导出功能

```tsx
// 导出为 HTML 文件
await editorRef.current?.exportAsHtml('document.html');

// 导出为 Markdown 文件
await editorRef.current?.exportAsMarkdown('document.md');

// 导出为 PDF 文件（支持多页）
await editorRef.current?.exportAsPdf('document.pdf');

// 导出为图片（高清 2x）
await editorRef.current?.exportAsImage('document.png');
```

---

## 高级功能

### 字数统计

```tsx
const wordCount = editorRef.current?.getWordCount();
console.log({
  words: wordCount.words,
  charactersWithSpaces: wordCount.charactersWithSpaces,
  paragraphs: wordCount.paragraphs,
  lines: wordCount.lines,
});
```

### 文档大纲

```tsx
const outline = editorRef.current?.getOutline();
// 返回: [{ id, depth, title, path, type }]
```

### 目录功能

```tsx
<PlateEditor
  showToc={true}
  tocPosition="left"
  tocWidth="240px"
/>
```

### 编辑器模式

```tsx
<PlateEditor
  mode="edit"  // 'edit' | 'view' | 'suggestion'
/>
```

---

## API 参考

### PlateEditorRef 方法

#### 内容管理

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `getContent()` | 获取编辑器内容 | `Value` |
| `setContent(value)` | 设置编辑器内容 | `void` |
| `resetContent()` | 重置为初始内容 | `void` |
| `getText()` | 获取纯文本内容 | `string` |
| `isEmpty()` | 检查编辑器是否为空 | `boolean` |

#### Markdown/HTML 转换

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `getMarkdown()` | 获取 Markdown | `string` |
| `setMarkdown(md)` | 设置 Markdown | `void` |
| `insertMarkdown(md)` | 插入 Markdown | `void` |
| `getHtml()` | 获取 HTML | `Promise<string>` |
| `setHtml(html)` | 设置 HTML | `void` |

#### 查找替换

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `find(text, options)` | 查找文本 | `MatchPosition[]` |
| `replace(search, replace, options)` | 替换文本 | `number` |

#### 文档分析

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `getWordCount()` | 获取字数统计 | `WordCountResult` |
| `getOutline()` | 获取文档大纲 | `OutlineItem[]` |

#### 导出功能

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `exportAsHtml(filename)` | 导出 HTML | `Promise<void>` |
| `exportAsMarkdown(filename)` | 导出 Markdown | `Promise<void>` |
| `exportAsPdf(filename)` | 导出 PDF | `Promise<void>` |
| `exportAsImage(filename)` | 导出图片 | `Promise<void>` |

#### 插入操作

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `insertText(text)` | 插入文本 | `void` |
| `insertLink(url, text)` | 插入链接 | `void` |
| `insertImage(url)` | 插入图片 | `void` |

#### 格式控制

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `toggleMark(mark)` | 切换格式 | `void` |
| `undo()` | 撤销 | `void` |
| `redo()` | 重做 | `void` |

#### 编辑器控制

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `focus()` | 聚焦编辑器 | `void` |
| `blur()` | 失焦编辑器 | `void` |
| `getEditor()` | 获取底层编辑器实例 | `TPlateEditor` |

#### 修改状态

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `isModified()` | 检查内容是否已修改 | `boolean` |
| `resetModifiedState()` | 重置修改状态 | `void` |

#### 工具栏/目录控制

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `setToolbarVisible(visible)` | 设置工具栏可见性 | `void` |
| `setTocVisible(visible)` | 设置目录可见性 | `void` |

#### 本地存储 (v0.5.5+)

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `saveToLocalStorage()` | 手动保存到 localStorage | `void` |
| `clearLocalStorage()` | 清理 localStorage 临时数据 | `void` |

#### 自动保存

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `getAutoSaveState()` | 获取自动保存状态 | `AutoSaveState` |
| `saveNow()` | 立即触发保存 | `void` |
| `restoreAutoSave()` | 恢复自动保存的内容 | `Value \| null` |
| `clearAutoSave()` | 清除自动保存数据 | `void` |

### PlateEditorProps

#### 基础属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `value` | `Value` | - | 编辑器内容 |
| `onChange` | `(value: Value) => void` | - | 内容变化回调 |
| `mode` | `'edit' \| 'view' \| 'suggestion'` | `'edit'` | 编辑器模式 |
| `disabled` | `boolean` | `false` | 是否禁用 |
| `readOnly` | `boolean` | `false` | 是否只读 |
| `autoFocus` | `boolean` | `false` | 是否自动聚焦 |
| `className` | `string` | - | 自定义类名 |

#### 工具栏属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `toolbarConfig` | `ToolbarConfig` | `FULL_TOOLBAR` | 工具栏配置 |
| `showToolbar` | `boolean` | `true` | 是否显示工具栏 |
| `toolbarSticky` | `boolean` | `true` | 工具栏是否吸顶 |
| `toolbarAlign` | `'left' \| 'center' \| 'right'` | `'left'` | 工具栏对齐 |
| `toolbarFullWidth` | `boolean` | `false` | 工具栏是否全宽 |
| `toolbarClassName` | `string` | - | 工具栏类名 |
| `toolbarStyle` | `CSSProperties` | - | 工具栏样式 |
| `customToolbarButtons` | `CustomToolbarButtons` | - | 自定义工具栏按钮 |

#### 目录属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `showToc` | `boolean` | `false` | 是否显示目录 |
| `tocPosition` | `'left' \| 'right' \| 'top' \| 'custom'` | `'left'` | 目录位置 |
| `tocWidth` | `string \| number` | `'320px'` | 目录宽度 |
| `tocClassName` | `string` | - | 目录类名 |
| `tocStyle` | `CSSProperties` | - | 目录样式 |
| `renderToc` | `(toc: ReactNode) => ReactNode` | - | 自定义目录渲染 |

#### 保存属性 (v0.5.5+)

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `onSave` | `(content: Value) => void` | - | 快捷键保存回调 |
| `autoSave` | `boolean` | `false` | 是否启用自动保存 |
| `autoSaveKey` | `string` | - | localStorage 存储键名 |
| `autoSaveInterval` | `number` | `30000` | 自动保存间隔（毫秒） |
| `onAutoSave` | `(content: Value) => void` | - | 自动保存回调 |

#### 其他属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `showSettings` | `boolean` | `true` | 是否显示设置按钮 |
| `contentMaxWidth` | `string \| number` | - | 内容区域最大宽度 |
| `onModifiedChange` | `(isModified: boolean) => void` | - | 修改状态变化回调 |

---

## 开发指南

### 本地开发

```bash
# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

# 类型检查
pnpm typecheck

# 代码检查
pnpm lint

# 构建
pnpm build
```

### 项目结构

```
src/
├── components/       # UI 组件
│   ├── editor/       # 编辑器核心
│   └── ui/           # 通用 UI
├── hooks/            # React Hooks
├── lib/              # 工具库
│   ├── word-count.ts # 字数统计
│   ├── find-replace.ts # 查找替换
│   ├── toc.ts        # 目录提取
│   ├── outline.ts    # 大纲获取
│   ├── export.ts     # 导出功能
│   └── constants.ts  # 常量定义
├── pages/            # 示例页面
│   ├── Editor.tsx    # 完整编辑器
│   └── examples/     # 功能示例
└── index.ts          # SDK 入口
```

---

## 发布到 npm

### 前置条件

- 已安装 Node.js 18+ 和 pnpm
- 拥有 npm 账号并具有 `kc-plate-editor` 包的发布权限
- npm 账号已开启 2FA 时，需准备好身份验证器 App（如 Google Authenticator）

### 发布步骤

```bash
# 1. 进入项目目录
cd kc-plate-editor

# 2. 更新版本号（选择 patch/minor/major）
npm version patch   # 0.6.1 → 0.6.2
npm version minor   # 0.6.1 → 0.7.0
npm version major   # 0.6.1 → 1.0.0
# 或直接编辑 package.json 中的 version 字段

# 3. 验证代码质量（typecheck + lint + test）
pnpm run typecheck
pnpm run lint
pnpm run test

# 4. 构建产物
pnpm run build

# 5. 登录 npm 官方 registry
npm login --registry https://registry.npmjs.org/
# 按提示在浏览器完成登录，回到终端按 Enter 确认

# 6. 发布（开启 2FA 的账号需要 --otp 参数）
npm publish --registry https://registry.npmjs.org/ --otp=你的6位验证码
# 从身份验证器 App 获取 OTP 后立即执行（30秒有效期）
```

### 发布到私有 registry

如果使用 Verdaccio、Nexus 等私有 npm 仓库：

```bash
# 1. 登录私有 registry
npm login --registry http://your-private-registry:4873/

# 2. 构建
pnpm run build

# 3. 发布到私有 registry（私服通常不需要 OTP）
npm publish --registry http://your-private-registry:4873/
```

也可以在 `.npmrc` 中配置默认 registry 避免每次指定：

```ini
# .npmrc
registry=http://your-private-registry:4873/
```

### 发布后验证

```bash
# 查看已发布的版本
npm view kc-plate-editor versions --registry https://registry.npmjs.org/

# 在其他项目中安装最新版
pnpm add kc-plate-editor@latest
```

### 常见问题

| 错误 | 原因 | 解决方式 |
|------|------|----------|
| `E403 Two-factor authentication required` | npm 账号开启了 2FA | 添加 `--otp=123456` 参数 |
| `E401 Unauthorized` | 未登录或 token 过期 | 重新执行 `npm login` |
| `E403 Forbidden` | 无发布权限 | 检查 npm 账号对该包的权限 |
| `EPUBLISHCONFLICT` | 版本号已存在 | 更新 `package.json` 中的版本号 |

---

## 更多资源

- **在线示例** - 运行 `pnpm dev` 后访问 http://localhost:3001
- **详细文档** - 查看 `docs/` 目录
- **变更日志** - 查看 `CHANGELOG.md`
- **Plate.js 文档** - https://platejs.org

---

## 技术栈

- React 19
- Plate.js 52
- TypeScript 5.9
- Tailwind CSS 4
- Vite 7

---

> **注意**: 本项目为私有项目，仅供内部使用，未经授权不得分发。
