首页 / 快速上手

快速上手

从安装到跑起来只要几分钟。本页覆盖三种接入方式、常用配置、上传(整包 + 分片)、Markdown 导入、代码块高亮,以及保存与数据回填。

它是什么

KEditor 是一个纯前端的微信公众号风格富文本编辑器。它不依赖任何后端,也不依赖任何运行时库:图片与视频走上传(整包 + 分片),代码块可选 20 种语言并语法高亮,Markdown 文件/文本可一键导入转成可编辑 HTML,随时能切到 HTML 源码模式查看或改写产物。

样式随 JS 注入,不需要额外引入 CSS。Vue 是可选 peerDependency —— 只用核心 API 的项目不会被强制安装 Vue。

安装

用包管理器,或者直接用 CDN。两条路都不需要构建步骤。

npm / pnpm / yarn
npm install kyle-editor

# 使用 Vue 组件的项目,再把 Vue 显式装上
npm install kyle-editor vue@^3
CDN(无需安装)
<!-- unpkg / jsdelivr 的默认入口就是压缩版,下面两种写法等价 -->
<script src="https://unpkg.com/kyle-editor"></script>
<script src="https://unpkg.com/kyle-editor/dist/keditor.umd.min.js"></script>
产物用途体积
keditor.umd.min.jsCDN / <script> 默认入口192 KB / gzip 61 KB
keditor.umd.js同上但未压缩,便于断点调试300 KB / gzip 78 KB
keditor.es.jsESM,打包器走这个288 KB
keditor.cjsCommonJS288 KB
keditor-vue.*.jsVue 组件 / 插件,外部化 vue 与本包各约 4 KB

两份 UMD 产物由同一入口分别构建,全局变量都是 window.KEditor,API 完全一致 —— 调试时换未压缩版不会改变行为。

快速开始

准备一个挂载点,然后创建实例。下面三种方式任选其一。

方式一:<script> 引入(零构建)

index.html
<div id="editor"></div>
<script src="https://unpkg.com/kyle-editor/dist/keditor.umd.min.js"></script>
<script>
  const editor = KEditor.create('#editor', {
    placeholder: '开始撰写内容,支持拖拽 / 粘贴图片与视频…',
    // 高度默认随内容自动撑开;想要固定高度 + 内部滚动就写上 height: 460
  })

  editor.on('change', ({ html }) => console.log(html))
</script>

本地开发时把 src 换成相对路径 dist/keditor.umd.min.js 即可。

方式二:ESM / 打包器

Vite / Webpack / Rollup
import KEditor from 'kyle-editor'

const editor = KEditor.create('#editor')  // 高度默认自适应内容

方式三:Vue 3 安装集成

main.js
import { createApp } from 'vue'
import KEditorVue from 'kyle-editor/vue'

createApp(App).use(KEditorVue).mount('#app')
组件用法
<template>
  <k-editor v-model="form.content" :options="options" :disabled="saving" @upload="onUpload" />
</template>

<script setup>
import { ref } from 'vue'

const form = ref({ content: '' })
const saving = ref(false)

const options = {
  // 不写 height 就是自适应内容高度;写了则固定高度、内容超出时编辑区内部滚动
  height: 420,
  // 无后端时保持默认 'mock';上线时换成下面两行即可
  uploader: '/api/upload',              // 普通上传:整个文件一次请求
  chunkUploader: '/api/chunk-upload',   // 分片上传:大文件自动切片(≥ 5MB)
  upload: { mode: 'auto', chunkSize: 5 * 1024 * 1024, concurrency: 4 },
}

const onUpload = (payload) => console.log(payload.stage, payload.percent)
</script>

kyle-editor/vue 的默认导出就是组件本身(自带 install),所以 app.use(KEditorVue) 后模板里用全局标签 <k-editor>,与直接 import KEditorVue 当组件用 <KEditorVue v-model="html" /> 两种写法等价。只想显式拿插件时用 import { KEditorPlugin }。

常用配置

下面这些是 90% 的场景会用到的。完整清单见 API 参考 · 配置项。

value
初始 HTML;留空时取挂载节点的 innerHTML
placeholder
空内容占位文案,默认 '开始撰写内容…'
height
内容区高度。不写 / 0 / 'auto' = 随内容自动撑开;填 px 才固定高度并内部滚动
primaryColor
主题色,默认 '#07c160'(CSS 变量 --ke-primary)
toolbar
工具栏项数组,false 隐藏;分隔符 '|',右对齐标记 ':tail'
readOnly
只读模式;运行时用 editor.setReadOnly(bool)
wordCount
底部字数 / 词数 / 图片视频统计,默认 true
maxLength
超出后提示(软限制,不截断输入)
historyLimit
撤销栈深度,默认 60
一份常见的完整配置
const editor = KEditor.create('#editor', {
  value: '<p>初始内容</p>',
  placeholder: '开始撰写内容…',
  primaryColor: '#07c160',
  height: 0,                 // 自适应内容高度
  paperWidth: 677,           // 公众号正文版心
  wordCount: true,

  uploader: '/api/upload',
  chunkUploader: '/api/chunk-upload',

  code: { defaultLanguage: 'javascript' },
  markdown: { insertMode: 'replace' },

  toolbar: [
    'undo', 'redo', '|',
    'heading', 'fontsize', '|',
    'bold', 'italic', 'underline', 'strike', '|',
    'foreColor', 'backColor', '|',
    'alignLeft', 'alignCenter', 'alignRight', '|',
    'ul', 'ol', 'quote', '|',
    'link', 'image', 'video', 'table', 'code', 'hr', 'clear',
    ':tail', 'markdown', 'source', 'fullscreen',
  ],

  onUpload: ({ stage, percent }) => console.log(stage, percent),
})

preview 不在默认工具栏里(默认尾栏是「导入MD / 源码 / 全屏」),但预览能力没变:用 editor.openPreview(),或者把 'preview' 写回 toolbar。

上传:整包与分片

上传分两条链路,互不影响,可以只配一条,也可以都配。

链路适用行为
普通上传
uploader
小文件(图片为主) 整个文件一次请求发完,返回 url 即成功
分片上传
chunkUploader
大文件(视频为主) 切片 → 并发 → 重试,只需一个接口,响应里 url 非空即成功
两条链路各自配置
KEditor.create('#editor', {
  uploader: '/api/upload',                        // 普通上传(整个文件一次请求)
  chunkUploader: '/api/chunk-upload',             // 分片上传(切片并发)
  upload: {
    mode: 'auto',                                 // 'auto' 按体积自动选(默认)| 'normal' | 'chunk'
    chunkThreshold: 5 * 1024 * 1024,              // auto:达到该体积才走分片
    chunkSize: 1024 * 1024,                       // 分片大小
    concurrency: 4,                               // 并发分片数
    retries: 2,                                   // 单片失败重试次数
  },
})

你配了什么,就是什么行为

你配了什么实际行为
两条都配mode: 'auto':小于 chunkThreshold 走普通,否则走分片
只配 uploader全部走普通上传(分片不启用,不会静默用 mock)
只配 chunkUploader全部走分片上传(mode 默认变 'chunk')
两条都不配默认两条都是 'mock':小文件普通、大文件分片(开箱演示)

只配一条链路时,另一条不会被默认 mock 顶上 —— 否则真实后端只接了一半,另一半会静默产生 blob: 假地址。

接口约定

普通上传:整包 POST(multipart/form-data),请求字段 file(可用 fieldName 改)、filename、fileSize;响应 { "url": "https://cdn/a.png" },也支持 { code: 0, data: { url } }(用 parse 解开)。没返回 url 视为失败,占位块转错误态并提供「重试 / 移除」。

分片上传:每个分片都 POST 到 chunkUploader 这一个地址。请求字段 file(分片二进制)、identifier(同一文件恒定的指纹)、index(从 0 起)、total、filename、fileSize、chunkSize。只要某个响应里返回非空 url,就代表这个文件上传成功,编辑器立刻用它替换占位块,剩余分片不再上传 —— 服务端秒传、或收齐最后一片时返回都可以。

需要鉴权 / 自定义字段名 / 自定义取地址字段
KEditor.create('#editor', {
  chunkUploader: {
    type: 'http',
    url: '/api/chunk-upload',
    fieldName: 'chunk',                                         // 默认 'file'
    headers: () => ({ Authorization: `Bearer ${getToken()}` }), // 传函数则每次请求实时取值
    withCredentials: true,
    parse: (json) => json.data,                                 // 默认 json.data ?? json
    pickUrl: (json) => json?.payload?.filePath || '',           // 默认认 url / fileUrl / src
    mode: 'formdata',                                           // 默认 formdata;可换 'raw'
  },
})

其他写法(两条链路通用)

适配器怎么写都可以
uploader: '/api/upload'                                   // 字符串地址
uploader: { type: 'http', url: '/api/upload' }            // 显式写法
uploader: KEditor.httpAdapter({ url: '/api/upload' })     // 自己造适配器
uploader: KEditor.simpleAdapter(async (file) => myUpload(file))  // 函数包装
uploader: async (file) => (await myUpload(file)).url      // 直接给函数也可以
uploader: 'mock'                                          // 零后端,生成 blob: 地址
uploader: KEditor.mockAdapter({ speed: 8 })               // mock 可调速度 / 延迟 / 失败率

分片上传现在是单接口:httpAdapter 不再接受 initUrl / chunkUrl / completeUrl / abortUrl,配了会直接报错提示迁移。需要自定义 init / complete 流程时,自己实现适配器传给 chunkUploader。

进度、速度、取消、重试的完整适配器协议与后端实现建议,见仓库 docs/upload-adapter.md。

导入 Markdown

尾栏的 导入MD 按钮打开导入面板:选文件、拖文件,或直接粘文本,实时预览转换结果与结构统计,再选落点导入。把 .md / .markdown / .txt 文件拖进正文也能直接转换。

导入配置
KEditor.create('#editor', {
  markdown: {
    insertMode: 'replace',     // 默认落点:replace 替换全文 / append 追加文末 / cursor 插入光标
    maxSize: 5 * 1024 * 1024,  // 单文件上限(字节),0 = 不限制
    gfm: true,                 // 表格 / 删除线 / 任务列表 / 裸链接
    breaks: false,             // true = 段落内单换行也当硬换行
    linkTarget: false,         // true 或 '_blank' = 外链自动加 target + rel
    frontMatter: true,         // 丢弃开头的 YAML front matter
  },
})
运行时导入(source 支持 文本 / File / { name, content })
await editor.importMarkdown(mdText)                   // 按 options.markdown.insertMode 落点
await editor.importMarkdown(file, { mode: 'append' }) // 临时指定落点
await editor.setMarkdown(mdText)                      // 替换全文
await editor.insertMarkdown(mdText)                   // 插入光标处
await editor.appendMarkdown(mdText)                   // 追加到文末

editor.markdownOptions()   // { insertMode: 'replace', maxSize: 5242880, auto: true }

editor.on('markdownimport', ({ mode, name, summary }) => {
  console.log(mode, name, summary)
})

导入返回值是 { ok, html, summary, mode, chars, name };只读模式返回 { ok: false, reason: 'readonly' },超限或读文件失败返回 { ok: false, reason: 'error' }。

支持的语法

类别说明
标题# ~ ###### 与 setext(=== / ---)
列表有序 / 无序 / 嵌套 / 松散段落 / 任务列表(- [x] → ☑)
表格GFM 表格,列对齐写入 style="text-align:…"
代码块围栅与缩进,语言保留并高亮(data-k-lang + class="language-x"),未知语言退化纯文本
行内粗体 / 斜体 / 删除线 / 行内代码 / 转义 / 硬换行 / 引用式与自动链接 / 图片
其他引用(含嵌套与懒续行)、分隔线、YAML front matter 丢弃、内联 HTML 与 HTML 块

导入是 GFM 子集:不支持脚注、目录(TOC)、数学公式与自定义容器(callout),这些语法会按普通段落渲染。导入是单次快照,不会与源 .md 保持双向同步。

文件编码先按严格 UTF-8 解码,失败回退 gb18030 / gbk / big5。相对路径图片(![](./img/a.png))只保留路径文本,不会自动上传。

代码块与高亮

点工具栏「代码块」就是一个语言面板,20 种内置语言,支持按名称或别名搜索(js / mysql / yml)。光标已在某个代码块里时,同一个面板变成「代码块语言」,选一项即改写当前块并重新着色。

配置与运行时 API
KEditor.create('#editor', {
  code: {
    languages: ['js', 'python', 'go', 'sql'], // 只留几种(主名或别名都认);不写 = 全部 20 种
    defaultLanguage: 'python',                // 新建代码块默认语言
    highlight: true,                          // false = 只保留纯文本,语言标记照写
  },
})

editor.insertCode('const a = 1', 'js')   // 插入代码块(语言可省,用 defaultLanguage)
editor.setCodeLanguage('sql')            // 改写光标所在块的语言
editor.getCodeLanguage()                 // 不在块内返回 ''
editor.codeLanguages()                   // [{ value, label, hint }, …]
editor.getState().inCode                 // 光标是否在代码块内

语言同时写在 <pre data-k-lang="javascript"> 与 <code class="language-javascript"> 上;外部 HTML 只带 class="language-xxx" 也能识别并补上标记。

高亮只是编辑/预览时的显示效果:getHTML()、源码模式、复制出去的内容都是不带 token <span> 的干净代码,再次导入会自动重新高亮。单块超过 60 KB 退化为纯文本,避免长文卡顿。

版心与高度

高度:默认自适应

不配置 height 时,内容区随内容自动撑开(整页滚动,工具栏尽量吸附视口顶部);配置了才固定高度,内容超出时在编辑区内部滚动。

高度控制
KEditor.create('#editor', {})                  // 自适应:默认行为
KEditor.create('#editor', { height: 460 })     // 固定 460px,内部滚动
KEditor.create('#editor', { minHeight: 200 })  // 只控最小高度

// 运行时切换
editor.setHeight(600)      // 固定 600px
editor.setHeight(0)        // 回到自适应(也接受 'auto')
editor.heightOptions()     // { height: 0, auto: true }

版心:宽屏不浪费

宽屏下如果版心固定 677px,编辑器两侧会出现大片空白。默认采用 fluid 版心:版心不小于 paperWidth,宽屏时随容器生长,两侧留白恒定为 paperGap。

版心配置
KEditor.create('#editor', {
  paperWidth: 677,     // 版心基准宽(公众号);fluid 模式下它是最小宽度
  paperMode: 'fluid',  // 'fluid'(默认)| 'fixed'(恒定 paperWidth + 64)
  paperGap: 24,        // fluid 模式下单侧留白
  paperMaxWidth: 0,    // fluid 模式下的版心上限,0 = 不限
})

// 运行时调整(侧栏折叠、布局变化时)
editor.setPaper({ mode: 'fixed' })
editor.setPaper({ gap: 40, maxWidth: 1000 })
editor.paperOptions()  // { width, mode, gap, maxWidth }
需求配置
永远像公众号那样居中一栏paperMode: 'fixed'
宽屏也想多写点字默认 fluid(不限上限)
宽屏但可读性优先fluid + paperMaxWidth: 1000

自定义按钮

不想改源码就往工具栏加业务按钮。典型场景:插入公司模板、关联栏目、跳转后台页面。

加一个下拉选择按钮
const editor = KEditor.create('#editor', {
  customButtons: [
    // ① 数据从接口读取
    {
      name: 'column',
      text: '栏目',
      title: '插入栏目链接',
      searchable: true,         // 面板内搜索
      url: '/api/columns',      // 内部 fetch,自动识别 items/list/data/rows/records
      link: true,               // 用每项自带的地址(url / href / link 字段)
      insert: 'link',
      position: { after: 'link' },   // 放到「链接」之后
    },
    // ② 数据静态配置
    {
      name: 'tpl',
      text: '模板',
      options: [
        { value: 'opening', label: '开场白', desc: '问候 + 本期导读', group: '通用' },
        { value: 'ending',  label: '结束语', desc: '引导关注与转发', group: '通用' },
      ],
      link: { url: 'https://mp.weixin.qq.com/s/tpl-{value}', text: '【{label}】', target: '_blank' },
    },
  ],
})

editor.on('customselect', ({ name, item, link, action }) => {
  console.log(name, item.label, '→', link?.url, '(插入方式:' + action + ')')
})

position 可以把它放到工具栏任意位置:'start' 最前、'tail' 右侧吸附尾栏(与「导入MD / 源码 / 全屏」同区)、数字索引、或者 { before } / { after } 锚点。锚点找不到会自动回退,不报错、不丢按钮。

运行时也能增删移位:addButton / moveButton / removeButton / setButtonOptions / getButtonValue / setButtonValue。完整字段说明见 API 参考 · 自定义工具栏按钮。

保存与回填

取内容用 getHTML(),回填用 value 或 setContent()。默认取到的是干净产物:编辑器内部属性与未完成占位块都被剔除,也没有代码高亮的 token <span>。

一个最小的自动保存
const editor = KEditor.create('#editor', {
  value: await fetchDraft(),          // 回填:初始 HTML
  // 高度自适应,不用管
})

let timer = null
editor.on('change', () => {
  clearTimeout(timer)                 // change 已内置 160ms 防抖,这里再做一次幂等
  timer = setTimeout(async () => {
    await fetch('/api/draft', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ html: editor.getHTML() }),
    })
  }, 1200)
})

editor.on('save', () => saveNow())    // ⌘/Ctrl + S 触发
方法说明
getHTML()取干净 HTML(默认剔除内部属性与未完成占位块)
getHTML({ clean: 'preview' })保留代码高亮,用于预览渲染
getHTML({ clean: false })原始 HTML,仅调试用
getText() / getJSON()纯文本 / {html, text, counts}
setContent(html, { sanitize?, silent? })覆盖内容
counts(){chars, words, images, videos, uploading}

默认上传适配器产生的 blob: 地址仅当前会话有效,正式环境请配置真实上传接口。入库前建议再跑一次 sanitizeHtml。

安全

所有进入编辑器的外部 HTML(value、setContent、粘贴)都会经过白名单净化:

  • 标签与属性双白名单,未知标签直接丢弃
  • on* 事件属性一律剔除
  • javascript: / vbscript: / file: / 非法 data: 协议拦截
  • style 声明逐条过滤(丢弃 expression、behavior 等)
  • Markdown 转换产物同样再过一遍白名单,<script> 会被清掉或转义

getHTML({ clean: false }) 会返回含编辑器内部属性的原始 HTML,请仅在调试时使用。

下一步