嵌入

一行腳本即可把編輯器放進你的網站或應用。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 工具 →