安裝
把下面的程式碼貼進頁面即可。單一 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 | 替換工具列文字(可只傳部分鍵, 見下文) |
licenseKey | Plus 授權金鑰, 網域驗證通過後去除浮水印 |
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 卸載元件時調用 |
version | SDK 版本字串(當前 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 查看已載入版本。