嵌入

一行脚本即可把编辑器放进你的网站或应用。Plus 许可密钥可去除水印。

安装

把下面的代码贴进页面即可。单个 82KB 文件、零依赖运行。

<div id="typdit-editor"></div>
<script src="https://typdit.com/embed/v1.js"></script>
<script>
  const editor = Typdit.create(document.getElementById('typdit-editor'), {
    placeholder: 'Write here',
    licenseKey: 'YOUR_KEY',
    onChange: (snapshot) => console.log(snapshot),
  });
</script>

直接从编辑器页面带走

不必手写安装代码。在编辑器页面把模式和主题调成你想要的样子,往下滚动,就能看到照抄这些设置的安装代码。与默认值相同的选项不会写出来,所以一眼就能看清哪里做了特别设置。

点击连文档一起复制,屏幕上的内容会作为 snapshot 选项一并带上。适合打开模板看中了骨架、想连骨架一起带走的时候。

全部选项

全部通过 Typdit.create(element, options) 的第二个参数传入。所有字段均可省略,默认值见表。

选项说明
snapshot初始文档(JSON 快照, 见下方格式一节)。省略则从空文档开始
theme: 'light' | 'dark'主题(默认 light)
mode: 'notion'Notion 风格预设: 隐藏工具栏并开启块手柄(见下文)。默认 classic
handles: true单独开关块手柄(优先于 mode 预设)
writingMode: 'paper'写作模式。可取 plain、focus、book、paper、essay、script、techdoc,或传入预设对象。排版、计量、目录、脚注与剧本流程会一起改变
writingMode: { id, extends }在内置模式之上只改几项,就成了自己的模式。不写 extends 则从什么都没打开的底座开始
writingLabels替换模式 UI 文案(计量名称、目录、脚注、目标)。可只写一部分,其余用英文默认值
writingSlashLabels模式专属斜杠条目的文案(脚注、剧本块)
handleLabels替换块手柄文字(可只传部分键)
toolbar: false隐藏顶部工具栏(默认显示)
menus: false关闭斜杠命令与选中气泡菜单(默认开启)
markdown: false关闭 # 标题、- 列表等 Markdown 快捷输入(默认开启)
typography: false关闭弯引号、省略号自动替换(默认开启)
placeholder空文档中显示的提示文字
autofocus: true创建后立即聚焦编辑器(默认关闭)
toolbarLabels替换工具栏文字(可只传部分键, 见下文)
licenseKeyPlus 许可密钥, 域名校验通过后去除水印
onChange(snapshot)内容每次变化时回调最新快照
onRequestImage()图片上传钩子。resolve {src, alt?} 即插入, null 为取消。未提供时工具栏回退为 URL 输入
onRequestFile()文件附件钩子, 返回 {src, name?, size?}。仅在提供此钩子时才渲染文件按钮
onRequestVideo()视频上传钩子, 返回 {src}。提供后视频链接输入行会多一个上传按钮(链接输入始终可用)

Notion 风格模式

mode: 'notion' 会隐藏工具栏并开启块手柄。鼠标悬停在块上时, 左侧会出现添加(+)和菜单(⋮⋮)按钮; 菜单可上下移动、复制、删除块或转换块类型。排版仍可通过斜杠命令和选中气泡菜单完成。

没有工具栏时, 图片、视频、文件插入项也会从斜杠菜单中移除(其输入界面属于工具栏)。若既要手柄又要附件, 可同时传 toolbar: true, 手柄与工具栏可以共存。

Typdit.create(el, {
  mode: 'notion',
  handleLabels: { moveUp: '위로 이동', moveDown: '아래로 이동', delete: '삭제' },
});

实例方法

用 Typdit.create() 返回的句柄读写文档, 存储完全由宿主站点负责。

方法说明
getSnapshot()以 JSON 快照返回当前文档
setSnapshot(json)用快照整体替换文档(加载)
getText()返回无格式纯文本(用于搜索索引、字数统计)
focus()将焦点移入编辑器
destroy()移除编辑器并解除监听, SPA 卸载组件时调用
versionSDK 版本字符串(当前 1.0.0)

快照格式

快照是可以原样存储、原样传回的扁平 JSON。text 的每一行与 blocks 的每一项一一对应, marks 是基于 text 的偏移区间。加载时会跳过未知的块和标记类型, 因此新版本写出的快照在旧版本中也能打开。

{
  "version": 1,
  "text": "제목\n첫 문단입니다.",
  "blocks": [
    { "type": "heading", "attrs": { "level": 1 } },
    { "type": "paragraph" }
  ],
  "marks": [
    { "type": "bold", "from": 3, "to": 7 }
  ]
}

接上自动保存

onChange 每次击键都会触发。常见做法是加防抖, 停止输入后只向服务器发送一次。

let timer;
const editor = Typdit.create(el, {
  onChange: (snapshot) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fetch('/api/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(snapshot),
      });
    }, 800);
  },
});

接入图片与文件上传

编辑器本身不上传文件。钩子负责选择与上传并返回 URL, 插入和渲染由编辑器完成。不提供钩子时图片仍可通过 URL 输入使用, 文件按钮仅在提供 onRequestFile 时出现。

Typdit.create(el, {
  onRequestImage: async () => {
    const file = await pickFile('image/*');   // 파일 선택 UI는 호스트 구현
    if (!file) return null;                   // null = 취소
    const src = await uploadToMyServer(file); // 업로드도 호스트 구현
    return { src, alt: file.name };
  },
  onRequestFile: async () => {
    const file = await pickFile('*/*');
    if (!file) return null;
    const src = await uploadToMyServer(file);
    return { src, name: file.name, size: file.size };
  },
  onRequestVideo: async () => {
    const file = await pickFile('video/*');
    return file ? { src: await uploadToMyServer(file) } : null;
  },
});

修改工具栏文字

默认文字为英文。在 toolbarLabels 中只传要修改的键, 其余保持默认。全部键: text, quote, code, divider, table, image, video, file, upload, font, fontDefault, color, highlight, none, alignLeft, alignCenter, alignRight, imageUrl, videoUrl, link, linkApply, linkRemove, undo, redo。

Typdit.create(el, {
  toolbarLabels: {
    text: '본문', quote: '인용', code: '코드', divider: '구분선',
    image: '이미지', video: '영상', link: '링크',
    undo: '되돌리기', redo: '다시 실행',
  },
});

在 React 中使用

用 ref 拿到容器, 挂载时创建, 卸载时调用 destroy()。

import { useEffect, useRef } from 'react';

function TypditEditor({ onChange }) {
  const ref = useRef(null);
  useEffect(() => {
    const editor = window.Typdit.create(ref.current, { onChange });
    return () => editor.destroy();
  }, []);
  return <div ref={ref} />;
}

在 Vue 中使用

在 onMounted 中创建, 在 onBeforeUnmount 中调用 destroy()。

<template><div ref="host"></div></template>

<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
const host = ref(null);
let editor;
onMounted(() => { editor = window.Typdit.create(host.value, {}); });
onBeforeUnmount(() => editor?.destroy());
</script>

在 SSR 框架(如 Next.js)中使用

脚本只在浏览器中运行。服务端渲染时没有 window, 请在仅客户端的组件里加载脚本后再创建。

'use client';
import { useEffect, useRef } from 'react';

export default function Editor() {
  const ref = useRef(null);
  useEffect(() => {
    let editor;
    const s = document.createElement('script');
    s.src = 'https://typdit.com/embed/v1.js';
    s.onload = () => { editor = window.Typdit.create(ref.current, {}); };
    document.head.appendChild(s);
    return () => editor?.destroy();
  }, []);
  return <div ref={ref} />;
}

一页多个编辑器

每次调用 Typdit.create() 都会创建独立实例, 一页放多少个都可以。样式只在首次创建时注入一个 style 标签, 每个实例通过各自句柄的 destroy() 单独移除。

许可与水印

  • 没有密钥时编辑器功能完整,底部会有小水印。
  • Plus 订户在编辑器页的嵌入卡片按域名签发密钥。密钥在注册域名及其子域名生效(写成 *.example.com 含义相同)。
  • 密钥有效期与签发时的订阅期一致,续订后请重新签发。
  • 校验失败或网络不通都不会禁用编辑器, 只是水印保留, 也不会抛错。

网络与 CSP

  • 文档内容不会被发送到任何地方。编辑器不为内容发起网络请求, 存储完全由宿主站点完成。
  • 唯一的请求是设置 licenseKey 时的一次校验(只读, 目标 firestore.googleapis.com)。
  • 使用 CSP 的站点在 script-src 放行 typdit.com, 若使用密钥再在 connect-src 放行 firestore.googleapis.com。正文中插入的图片和视频另算: 需在 img-src、frame-src 放行其来源(YouTube 嵌入用 youtube-nocookie.com)。
  • 样式通过一个 style 标签注入, 类名以 td- 为前缀作用域隔离, 不会与宿主 CSS 混淆。

版本策略

/embed/v1.js 是锁定主版本的 URL。v1 URL 内保持向后兼容, 破坏兼容的变更会以新 URL(v2)发布。运行时可通过 Typdit.version 查看已加载版本。

下一篇: AI 工具 →