kindy-editor 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +27 -2
  2. package/dist-lib/builder.js +4 -4
  3. package/dist-lib/{editorApp-F48A7I6z.js → editorApp-CtrnSSsP.js} +9620 -8998
  4. package/dist-lib/events.js +1 -1
  5. package/dist-lib/export.js +6 -6
  6. package/dist-lib/generate-toc.js +3 -3
  7. package/dist-lib/import.js +2 -2
  8. package/dist-lib/{kindy-editor-DnWvHijW.js → kindy-editor-CoeqFYYG.js} +100 -85
  9. package/dist-lib/kindy-editor.js +1 -1
  10. package/dist-lib/measure.js +3 -3
  11. package/dist-lib/{measureHost-Ca_gM01T.js → measureHost-mtLj644m.js} +2 -2
  12. package/dist-lib/{metrics-BFTHu_jF.js → metrics-DHgtBP-R.js} +1 -1
  13. package/dist-lib/{paintStyle-GQFOpJKx.js → paintStyle-Cjk9egel.js} +10 -9
  14. package/dist-lib/{parse-DmTwoFz6.js → parse-D3V5Abci.js} +11 -11
  15. package/dist-lib/{pipeline-BTemNJdx.js → pipeline-JfHxzjns.js} +1 -1
  16. package/dist-lib/recalc-docx.js +1 -1
  17. package/dist-lib/{recalcToc-c9kIMGbk.js → recalcToc-MRszlj8g.js} +80 -80
  18. package/dist-lib/{stylesheet-BcU1Q_JI.js → stylesheet-E5bu1xch.js} +1 -1
  19. package/dist-lib/{toc-mgkp-28Y.js → toc-DSwkNKls.js} +15 -15
  20. package/dist-lib/{webmcp-BLC_QALL.js → webmcp-Dr3n_xAO.js} +2 -2
  21. package/docs/.vitepress/cache/deps/_metadata.json +7 -7
  22. package/docs/.vitepress/config.ts +4 -0
  23. package/docs/.vitepress/dist/404.html +2 -2
  24. package/docs/.vitepress/dist/assets/{app.DYJtoZwO.js → app.rXBVdtfQ.js} +1 -1
  25. package/docs/.vitepress/dist/assets/chunks/framework.D9_ZvfG7.js +19 -0
  26. package/docs/.vitepress/dist/assets/chunks/{theme.BTGY7hKU.js → theme.CFP3sGN8.js} +1 -1
  27. package/docs/.vitepress/dist/assets/fonts.md.W87hfQua.js +75 -0
  28. package/docs/.vitepress/dist/assets/fonts.md.W87hfQua.lean.js +1 -0
  29. package/docs/.vitepress/dist/assets/{http-event-sink.md.BViKRVhd.js → http-event-sink.md.D7mTerle.js} +1 -1
  30. package/docs/.vitepress/dist/assets/{http-event-sink.md.BViKRVhd.lean.js → http-event-sink.md.D7mTerle.lean.js} +1 -1
  31. package/docs/.vitepress/dist/assets/{index.md.Bc6V9_E3.js → index.md.DN1JXJhG.js} +1 -1
  32. package/docs/.vitepress/dist/assets/{index.md.Bc6V9_E3.lean.js → index.md.DN1JXJhG.lean.js} +1 -1
  33. package/docs/.vitepress/dist/assets/{public-events.md.CMTD1Y84.js → public-events.md.Rj44CdBr.js} +1 -1
  34. package/docs/.vitepress/dist/assets/{public-events.md.CMTD1Y84.lean.js → public-events.md.Rj44CdBr.lean.js} +1 -1
  35. package/docs/.vitepress/dist/assets/{review-comments.md.19jCC2Pi.js → review-comments.md.466PcTcC.js} +1 -1
  36. package/docs/.vitepress/dist/assets/{review-comments.md.19jCC2Pi.lean.js → review-comments.md.466PcTcC.lean.js} +1 -1
  37. package/docs/.vitepress/dist/assets/table-editing.md.Bso_sgTE.js +28 -0
  38. package/docs/.vitepress/dist/assets/table-editing.md.Bso_sgTE.lean.js +1 -0
  39. package/docs/.vitepress/dist/fonts.html +99 -0
  40. package/docs/.vitepress/dist/hashmap.json +1 -1
  41. package/docs/.vitepress/dist/http-event-sink.html +6 -6
  42. package/docs/.vitepress/dist/index.html +6 -6
  43. package/docs/.vitepress/dist/public-events.html +7 -7
  44. package/docs/.vitepress/dist/review-comments.html +6 -6
  45. package/docs/.vitepress/dist/table-editing.html +52 -0
  46. package/docs/fonts.md +139 -0
  47. package/docs/table-editing.md +102 -0
  48. package/package.json +4 -1
  49. package/types/kindy-editor.d.ts +65 -1
  50. package/docs/.vitepress/dist/assets/chunks/framework.DCJ5T8RY.js +0 -19
@@ -0,0 +1,28 @@
1
+ import{_ as s,o as a,c as n,ae as t}from"./chunks/framework.D9_ZvfG7.js";const d=JSON.parse('{"title":"Chỉnh sửa bảng","description":"","frontmatter":{},"headers":[],"relativePath":"table-editing.md","filePath":"table-editing.md"}'),e={name:"table-editing.md"};function l(h,i,p,k,o,r){return a(),n("div",null,[...i[0]||(i[0]=[t(`<h1 id="chinh-sua-bang" tabindex="-1">Chỉnh sửa bảng <a class="header-anchor" href="#chinh-sua-bang" aria-label="Permalink to &quot;Chỉnh sửa bảng&quot;">​</a></h1><p>Kindy Editor xử lý bảng bằng mô hình semantic, không suy đoán thao tác từ vị trí con trỏ. Một target luôn gồm:</p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>TablePath (body/header/footer + ancestor table/cell IDs)</span></span>
2
+ <span class="line"><span> └─ TableSelection (cell | row | column | table)</span></span>
3
+ <span class="line"><span> └─ TableTargetResolver</span></span>
4
+ <span class="line"><span> └─ TableGridMutationService</span></span>
5
+ <span class="line"><span> └─ validateTableGrid trước và sau mutation</span></span></code></pre></div><p>Vì vậy thao tác chèn/xóa hàng hoặc cột, merge/unmerge và undo đều đi qua cùng một đường xử lý. <code>rowSpan</code>/<code>colSpan</code>, ID, content, format ô, row properties và <code>colFractions</code> của phần còn sống được giữ lại.</p><h2 id="thao-tac-tren-giao-dien" tabindex="-1">Thao tác trên giao diện <a class="header-anchor" href="#thao-tac-tren-giao-dien" aria-label="Permalink to &quot;Thao tác trên giao diện&quot;">​</a></h2><ul><li>Kéo qua nhiều ô để tạo cell selection hình chữ nhật.</li><li>Bấm dải mỏng phía trái bảng để chọn cả hàng.</li><li>Bấm dải mỏng phía trên bảng để chọn cả cột.</li><li>Bấm góc trên-trái của bảng để chọn cả bảng.</li><li>Dùng tab <strong>Bảng</strong> hoặc menu chuột phải để chèn/xóa, merge/unmerge.</li><li>Nút không hợp lệ sẽ bị disable. Ví dụ, <strong>Merge</strong> bị tắt khi chỉ có một ô; <strong>Unmerge</strong> bị tắt khi ô không có span.</li></ul><p>Mọi structural command là một transaction duy nhất, nên Undo/Redo không để lại bảng ở trạng thái trung gian.</p><h2 id="api-tich-hop" tabindex="-1">API tích hợp <a class="header-anchor" href="#api-tich-hop" aria-label="Permalink to &quot;API tích hợp&quot;">​</a></h2><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { TablePath, TableSelection } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;kindy-editor&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
6
+ <span class="line"></span>
7
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> path</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> TablePath</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
8
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> story: { kind: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;body&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
9
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ancestors: [],</span></span>
10
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> tableId: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;table-42&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
11
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span>
12
+ <span class="line"></span>
13
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> selection</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> TableSelection</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
14
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> kind: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;row&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> table: path,</span></span>
16
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> from: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
17
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> to: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
18
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span>
19
+ <span class="line"></span>
20
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> editor.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">setTableSelection</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(selection);</span></span>
21
+ <span class="line"></span>
22
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (editor.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">canExecuteTableAction</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;deleteRow&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)) {</span></span>
23
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> editor.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">executeTableAction</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;deleteRow&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
24
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Các action công khai:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> TableAction</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span></span>
25
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;insertRowAbove&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;insertRowBelow&quot;</span></span>
26
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;insertColumnLeft&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;insertColumnRight&quot;</span></span>
27
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;deleteRow&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;deleteColumn&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;deleteTable&quot;</span></span>
28
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;mergeCells&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;unmergeCell&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre></div><p><code>getTableSelection()</code> trả selection semantic hiện tại. <code>setTableSelection(null)</code> xóa selection bảng.</p><h2 id="invariant-va-du-lieu-loi" tabindex="-1">Invariant và dữ liệu lỗi <a class="header-anchor" href="#invariant-va-du-lieu-loi" aria-label="Permalink to &quot;Invariant và dữ liệu lỗi&quot;">​</a></h2><p><code>validateTableGrid()</code> phát hiện các lỗi mà renderer tolerant trước đây có thể che khuất:</p><ul><li><code>rowSpan</code>/<code>colSpan</code> không hợp lệ;</li><li>span vượt biên;</li><li>hai owner cell chồng cùng grid slot;</li><li>grid có ô trống không owner;</li><li>cell ID trùng;</li><li>số <code>colFractions</code> không khớp số cột.</li></ul><p>Structural action không chạy trên bảng malformed. Điều này ngăn một thao tác UI làm hỏng thêm dữ liệu import. Importer nên đưa lỗi vào compatibility report và yêu cầu repair rõ ràng thay vì tự sửa âm thầm.</p><h2 id="gioi-han-hien-tai" tabindex="-1">Giới hạn hiện tại <a class="header-anchor" href="#gioi-han-hien-tai" aria-label="Permalink to &quot;Giới hạn hiện tại&quot;">​</a></h2><p><code>TablePath</code> đã resolve được nested table. Tuy nhiên shared operation log hiện vẫn định địa chỉ structural op top-level bằng <code>tableId</code>, nên structural action trên nested table đang bị disable an toàn. Text và render của nested table không bị thay đổi. Bước tiếp theo là path-based operation để bật mutation nested table mà không sửa nhầm outer table.</p><h2 id="kiem-thu" tabindex="-1">Kiểm thử <a class="header-anchor" href="#kiem-thu" aria-label="Permalink to &quot;Kiểm thử&quot;">​</a></h2><p>Regression hiện bao phủ:</p><ul><li>insert row cắt ngang <code>rowSpan</code>;</li><li>xóa owner row nhưng giữ content/ID ở hàng còn sống;</li><li>xóa cột cắt qua <code>colSpan</code>;</li><li>merge/unmerge 2D;</li><li>semantic row selection và nested <code>TablePath</code>;</li><li>hit-test dải chọn table/row/column;</li><li>250 structural mutations hỗn hợp có seed cố định, validate invariant sau từng bước.</li></ul>`,21)])])}const g=s(e,[["render",l]]);export{d as __pageData,g as default};
@@ -0,0 +1 @@
1
+ import{_ as s,o as a,c as n,ae as t}from"./chunks/framework.D9_ZvfG7.js";const d=JSON.parse('{"title":"Chỉnh sửa bảng","description":"","frontmatter":{},"headers":[],"relativePath":"table-editing.md","filePath":"table-editing.md"}'),e={name:"table-editing.md"};function l(h,i,p,k,o,r){return a(),n("div",null,[...i[0]||(i[0]=[t("",21)])])}const g=s(e,[["render",l]]);export{d as __pageData,g as default};
@@ -0,0 +1,99 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US" dir="ltr">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Font tích hợp, font riêng và CDN | Kindy Editor</title>
7
+ <meta name="description" content="Canvas-based Word Document Editor Documentation">
8
+ <meta name="generator" content="VitePress v1.6.4">
9
+ <link rel="preload stylesheet" href="/assets/style.D6g4bpRC.css" as="style">
10
+ <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
+
12
+ <script type="module" src="/assets/app.rXBVdtfQ.js"></script>
13
+ <link rel="preload" href="/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
14
+ <link rel="modulepreload" href="/assets/chunks/theme.CFP3sGN8.js">
15
+ <link rel="modulepreload" href="/assets/chunks/framework.D9_ZvfG7.js">
16
+ <link rel="modulepreload" href="/assets/fonts.md.W87hfQua.lean.js">
17
+ <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
18
+ <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
19
+ </head>
20
+ <body>
21
+ <div id="app"><div class="Layout" data-v-c4daae71><!--[--><!--]--><!--[--><span tabindex="-1" data-v-17ad389b></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-17ad389b>Skip to content</a><!--]--><!----><header class="VPNav" data-v-c4daae71 data-v-a46e73f0><div class="VPNavBar" data-v-a46e73f0 data-v-bb7529ee><div class="wrapper" data-v-bb7529ee><div class="container" data-v-bb7529ee><div class="title" data-v-bb7529ee><div class="VPNavBarTitle has-sidebar" data-v-bb7529ee data-v-5622a072><a class="title" href="/" data-v-5622a072><!--[--><!--]--><!----><span data-v-5622a072>Kindy Editor</span><!--[--><!--]--></a></div></div><div class="content" data-v-bb7529ee><div class="content-body" data-v-bb7529ee><!--[--><!--]--><div class="VPNavBarSearch search" data-v-bb7529ee><!----></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-bb7529ee data-v-5bacfe89><span id="main-nav-aria-label" class="visually-hidden" data-v-5bacfe89> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/fonts.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Fonts & CDN</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/table-editing.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Table editing</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/public-events.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Public Events</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/http-event-sink.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>HTTP Event Sink</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-bb7529ee data-v-123fcb2f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-123fcb2f data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-bb7529ee data-v-08b35e6f data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-bb7529ee data-v-0c845e5d data-v-40e55da0><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-40e55da0><span class="vpi-more-horizontal icon" data-v-40e55da0></span></button><div class="menu" data-v-40e55da0><div class="VPMenu" data-v-40e55da0 data-v-39eca401><!----><!--[--><!--[--><!----><div class="group" data-v-0c845e5d><div class="item appearance" data-v-0c845e5d><p class="label" data-v-0c845e5d>Appearance</p><div class="appearance-action" data-v-0c845e5d><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-0c845e5d data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div></div></div><div class="group" data-v-0c845e5d><div class="item social-links" data-v-0c845e5d><div class="VPSocialLinks social-links-list" data-v-0c845e5d data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-bb7529ee data-v-670493dd><span class="container" data-v-670493dd><span class="top" data-v-670493dd></span><span class="middle" data-v-670493dd></span><span class="bottom" data-v-670493dd></span></span></button></div></div></div></div><div class="divider" data-v-bb7529ee><div class="divider-line" data-v-bb7529ee></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-c4daae71 data-v-9d8129cc><div class="container" data-v-9d8129cc><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-9d8129cc><span class="vpi-align-left menu-icon" data-v-9d8129cc></span><span class="menu-text" data-v-9d8129cc>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-9d8129cc data-v-d61422fe><button data-v-d61422fe>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-c4daae71 data-v-12a40e3d><div class="curtain" data-v-12a40e3d></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-12a40e3d><span class="visually-hidden" id="sidebar-aria-label" data-v-12a40e3d> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-ec086cd5><section class="VPSidebarItem level-0 has-active" data-v-ec086cd5 data-v-2e0476bc><div class="item" role="button" tabindex="0" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><h2 class="text" data-v-2e0476bc>Integration & Events</h2><!----></div><div class="items" data-v-2e0476bc><!--[--><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Overview</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/fonts.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Fonts & CDN</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/table-editing.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Table editing</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/public-events.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Public Events API</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/http-event-sink.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>HTTP Event Sink</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-c4daae71 data-v-d2aef184><div class="VPDoc has-sidebar has-aside" data-v-d2aef184 data-v-ef3a4dda><!--[--><!--]--><div class="container" data-v-ef3a4dda><div class="aside" data-v-ef3a4dda><div class="aside-curtain" data-v-ef3a4dda></div><div class="aside-container" data-v-ef3a4dda><div class="aside-content" data-v-ef3a4dda><div class="VPDocAside" data-v-ef3a4dda data-v-79cae1a0><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-79cae1a0 data-v-dfbd9fb8><div class="content" data-v-dfbd9fb8><div class="outline-marker" data-v-dfbd9fb8></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-dfbd9fb8>On this page</div><ul class="VPDocOutlineItem root" data-v-dfbd9fb8 data-v-da421f78><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-79cae1a0></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-ef3a4dda><div class="content-container" data-v-ef3a4dda><!--[--><!--]--><main class="main" data-v-ef3a4dda><div style="position:relative;" class="vp-doc _fonts" data-v-ef3a4dda><div><h1 id="font-tich-hop-font-rieng-va-cdn" tabindex="-1">Font tích hợp, font riêng và CDN <a class="header-anchor" href="#font-tich-hop-font-rieng-va-cdn" aria-label="Permalink to &quot;Font tích hợp, font riêng và CDN&quot;">​</a></h1><p>Kindy Editor dùng cùng một font source cho Canvas layout và export. Vì vậy font không chỉ cần hiển thị được trong CSS: exporter còn phải đọc được binary font để đo glyph và nhúng vào PDF. Font ngoài hiện hỗ trợ <strong>TTF/OTF</strong>; WOFF/WOFF2 chưa dùng được cho export PDF.</p><h2 id="khai-bao-nhieu-font-truc-tiep" tabindex="-1">Khai báo nhiều font trực tiếp <a class="header-anchor" href="#khai-bao-nhieu-font-truc-tiep" aria-label="Permalink to &quot;Khai báo nhiều font trực tiếp&quot;">​</a></h2><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { KindyEditor, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> FontsConfig } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;kindy-editor&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
22
+ <span class="line"></span>
23
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> fonts</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> FontsConfig</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
24
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Các URL tương đối bên dưới được ghép với thư mục CDN có version.</span></span>
25
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> baseUrl: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;https://cdn.example.vn/kindy-fonts/2026-08/&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
26
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> fonts: [</span></span>
27
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
28
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> family: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Inter&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
29
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> label: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Inter&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
30
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> faces: {</span></span>
31
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> regular: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;inter/Inter-Regular.ttf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
32
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> bold: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;inter/Inter-Bold.ttf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
33
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> italic: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;inter/Inter-Italic.ttf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
34
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> boldItalic: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;inter/Inter-BoldItalic.ttf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
35
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
36
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> sizing: { ascent: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.969</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, descent: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.241</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
37
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
38
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
39
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> family: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Noto Serif&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
40
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> faces: { regular: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;noto-serif/NotoSerif-Regular.otf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
41
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> sizing: { ascent: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1.069</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, descent: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.293</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
42
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
43
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
44
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span>
45
+ <span class="line"></span>
46
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> editor</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> KindyEditor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
47
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> container: document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">querySelector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;#editor&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
48
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> locale: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;vi&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
49
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> fonts,</span></span>
50
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p><code>regular</code> là bắt buộc. Face bị thiếu sẽ dùng lại Regular, bảo đảm preview và export không tự tạo faux-bold/faux-italic khác nhau. <code>sizing</code> là ascent/descent chia cho units-per-em của font; nên lấy từ <code>hhea</code>/<code>OS/2</code> của chính file font.</p><h2 id="dung-font-manifest-tren-cdn" tabindex="-1">Dùng font manifest trên CDN <a class="header-anchor" href="#dung-font-manifest-tren-cdn" aria-label="Permalink to &quot;Dùng font manifest trên CDN&quot;">​</a></h2><p>Ứng dụng có thể đưa hàng loạt family vào một catalog có version:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
51
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;schemaVersion&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;1.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
52
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;baseUrl&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;./files/&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
53
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;fonts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span></span>
54
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
55
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;family&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Inter&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
56
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;faces&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
57
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;regular&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Inter-Regular.ttf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
58
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;bold&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Inter-Bold.ttf&quot;</span></span>
59
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
60
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;sizing&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;ascent&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.969</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;descent&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.241</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
61
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
62
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
63
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;family&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Noto Serif&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
64
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;faces&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;regular&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;NotoSerif-Regular.otf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
65
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;sizing&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;ascent&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1.069</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;descent&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.293</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
66
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
67
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ]</span></span>
68
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Sau đó chỉ cần truyền URL catalog:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> KindyEditor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
69
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> container,</span></span>
70
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> fonts: {</span></span>
71
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> manifests: [</span></span>
72
+ <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;https://cdn.example.vn/kindy-fonts/2026-08/manifest.json&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
73
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
74
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> url: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;https://static.example.vn/brand-fonts/manifest.json&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
75
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> baseUrl: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;https://static.example.vn/brand-fonts/files/&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
76
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
77
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
78
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Khai báo trực tiếp luôn ưu tiên hơn manifest nếu trùng family.</span></span>
79
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> fonts: [companyFont],</span></span>
80
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
81
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p>Catalog lỗi, timeout hoặc sai schema sẽ bị bỏ qua riêng lẻ; editor vẫn mở bằng font tích hợp sẵn và các font còn lại.</p><h2 id="cdn-co-xac-thuc-hoac-signed-url" tabindex="-1">CDN có xác thực hoặc signed URL <a class="header-anchor" href="#cdn-co-xac-thuc-hoac-signed-url" aria-label="Permalink to &quot;CDN có xác thực hoặc signed URL&quot;">​</a></h2><p><code>loader</code> được dùng chung khi tải manifest, khi hiển thị Canvas và khi chuẩn bị font cho export. Binary đã tải được truyền vào worker; token và hàm loader không đi vào worker.</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> KindyEditor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
82
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> container,</span></span>
83
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> fonts: {</span></span>
84
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> manifests: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;https://assets.example.vn/fonts/manifest.json&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
85
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> loader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">request</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">signal</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
86
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> response</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(request.url, {</span></span>
87
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> signal,</span></span>
88
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> headers: { Authorization: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">`Bearer ${</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> getAssetToken</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">()</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
89
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> credentials: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;include&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
90
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> });</span></span>
91
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">response.ok) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">throw</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">`Font HTTP ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">response</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">status</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
92
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> response.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">arrayBuffer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span>
93
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
94
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
95
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p><code>request.kind</code> là <code>manifest</code> hoặc <code>font</code>; request font còn có <code>family</code> và <code>style</code> để ứng dụng ghi log, cấp signed URL hoặc áp dụng policy riêng.</p><h2 id="cau-hinh-cdn-khuyen-nghi" tabindex="-1">Cấu hình CDN khuyến nghị <a class="header-anchor" href="#cau-hinh-cdn-khuyen-nghi" aria-label="Permalink to &quot;Cấu hình CDN khuyến nghị&quot;">​</a></h2><ul><li>Cho phép CORS từ domain chạy editor, hoặc dùng <code>loader</code> qua transport của hệ thống.</li><li>Đặt version bất biến trong URL và trả <code>Cache-Control: public, max-age=31536000, immutable</code>.</li><li>Không ghi đè file font ở cùng URL; đổi version khi binary hoặc metrics thay đổi.</li><li>Tự host font thương mại theo đúng license. Không đưa Times New Roman lên CDN công cộng nếu tổ chức chưa có quyền phân phối.</li><li>Giữ manifest dưới 1 MB và mỗi font dưới 10 MB. Request bị giới hạn 15 giây để một CDN lỗi không treo quá trình mở editor.</li><li>Kiểm thử golden DOCX/PDF sau khi đổi font hoặc metrics vì thay đổi metrics có thể làm thay đổi điểm xuống dòng và phân trang.</li></ul><h2 id="quan-he-voi-dung-luong-bundle" tabindex="-1">Quan hệ với dung lượng bundle <a class="header-anchor" href="#quan-he-voi-dung-luong-bundle" aria-label="Permalink to &quot;Quan hệ với dung lượng bundle&quot;">​</a></h2><p>Font khai báo qua URL/manifest không được đóng vào bundle JavaScript của <code>kindy-editor</code>. Vì vậy có thể mở rộng catalog mà không làm tarball npm phình theo. Ở bước tối ưu tiếp theo, catalog lớn nên được tải theo family/style mà tài liệu thực sự sử dụng thay vì preload toàn bộ catalog.</p></div></div></main><footer class="VPDocFooter" data-v-ef3a4dda data-v-07d71ff1><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-07d71ff1><span class="visually-hidden" id="doc-footer-aria-label" data-v-07d71ff1>Pager</span><div class="pager" data-v-07d71ff1><a class="VPLink link pager-link prev" href="/" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Previous page</span><span class="title" data-v-07d71ff1>Overview</span><!--]--></a></div><div class="pager" data-v-07d71ff1><a class="VPLink link pager-link next" href="/table-editing.html" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Next page</span><span class="title" data-v-07d71ff1>Table editing</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
96
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"fonts.md\":\"W87hfQua\",\"http-event-sink.md\":\"D7mTerle\",\"index.md\":\"DN1JXJhG\",\"public-events.md\":\"Rj44CdBr\",\"review-comments.md\":\"466PcTcC\",\"table-editing.md\":\"Bso_sgTE\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Kindy Editor\",\"description\":\"Canvas-based Word Document Editor Documentation\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Fonts & CDN\",\"link\":\"/fonts\"},{\"text\":\"Table editing\",\"link\":\"/table-editing\"},{\"text\":\"Public Events\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}],\"sidebar\":[{\"text\":\"Integration & Events\",\"items\":[{\"text\":\"Overview\",\"link\":\"/\"},{\"text\":\"Fonts & CDN\",\"link\":\"/fonts\"},{\"text\":\"Table editing\",\"link\":\"/table-editing\"},{\"text\":\"Public Events API\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/hotienky/editor\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
97
+
98
+ </body>
99
+ </html>
@@ -1 +1 @@
1
- {"http-event-sink.md":"BViKRVhd","index.md":"Bc6V9_E3","public-events.md":"CMTD1Y84","review-comments.md":"19jCC2Pi"}
1
+ {"fonts.md":"W87hfQua","http-event-sink.md":"D7mTerle","index.md":"DN1JXJhG","public-events.md":"Rj44CdBr","review-comments.md":"466PcTcC","table-editing.md":"Bso_sgTE"}
@@ -9,16 +9,16 @@
9
9
  <link rel="preload stylesheet" href="/assets/style.D6g4bpRC.css" as="style">
10
10
  <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
11
 
12
- <script type="module" src="/assets/app.DYJtoZwO.js"></script>
12
+ <script type="module" src="/assets/app.rXBVdtfQ.js"></script>
13
13
  <link rel="preload" href="/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
14
- <link rel="modulepreload" href="/assets/chunks/theme.BTGY7hKU.js">
15
- <link rel="modulepreload" href="/assets/chunks/framework.DCJ5T8RY.js">
16
- <link rel="modulepreload" href="/assets/http-event-sink.md.BViKRVhd.lean.js">
14
+ <link rel="modulepreload" href="/assets/chunks/theme.CFP3sGN8.js">
15
+ <link rel="modulepreload" href="/assets/chunks/framework.D9_ZvfG7.js">
16
+ <link rel="modulepreload" href="/assets/http-event-sink.md.D7mTerle.lean.js">
17
17
  <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
18
18
  <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
19
19
  </head>
20
20
  <body>
21
- <div id="app"><div class="Layout" data-v-c4daae71><!--[--><!--]--><!--[--><span tabindex="-1" data-v-17ad389b></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-17ad389b>Skip to content</a><!--]--><!----><header class="VPNav" data-v-c4daae71 data-v-a46e73f0><div class="VPNavBar" data-v-a46e73f0 data-v-bb7529ee><div class="wrapper" data-v-bb7529ee><div class="container" data-v-bb7529ee><div class="title" data-v-bb7529ee><div class="VPNavBarTitle has-sidebar" data-v-bb7529ee data-v-5622a072><a class="title" href="/" data-v-5622a072><!--[--><!--]--><!----><span data-v-5622a072>Kindy Editor</span><!--[--><!--]--></a></div></div><div class="content" data-v-bb7529ee><div class="content-body" data-v-bb7529ee><!--[--><!--]--><div class="VPNavBarSearch search" data-v-bb7529ee><!----></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-bb7529ee data-v-5bacfe89><span id="main-nav-aria-label" class="visually-hidden" data-v-5bacfe89> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/public-events.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Public Events</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/http-event-sink.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>HTTP Event Sink</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-bb7529ee data-v-123fcb2f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-123fcb2f data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-bb7529ee data-v-08b35e6f data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-bb7529ee data-v-0c845e5d data-v-40e55da0><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-40e55da0><span class="vpi-more-horizontal icon" data-v-40e55da0></span></button><div class="menu" data-v-40e55da0><div class="VPMenu" data-v-40e55da0 data-v-39eca401><!----><!--[--><!--[--><!----><div class="group" data-v-0c845e5d><div class="item appearance" data-v-0c845e5d><p class="label" data-v-0c845e5d>Appearance</p><div class="appearance-action" data-v-0c845e5d><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-0c845e5d data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div></div></div><div class="group" data-v-0c845e5d><div class="item social-links" data-v-0c845e5d><div class="VPSocialLinks social-links-list" data-v-0c845e5d data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-bb7529ee data-v-670493dd><span class="container" data-v-670493dd><span class="top" data-v-670493dd></span><span class="middle" data-v-670493dd></span><span class="bottom" data-v-670493dd></span></span></button></div></div></div></div><div class="divider" data-v-bb7529ee><div class="divider-line" data-v-bb7529ee></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-c4daae71 data-v-9d8129cc><div class="container" data-v-9d8129cc><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-9d8129cc><span class="vpi-align-left menu-icon" data-v-9d8129cc></span><span class="menu-text" data-v-9d8129cc>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-9d8129cc data-v-d61422fe><button data-v-d61422fe>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-c4daae71 data-v-12a40e3d><div class="curtain" data-v-12a40e3d></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-12a40e3d><span class="visually-hidden" id="sidebar-aria-label" data-v-12a40e3d> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-ec086cd5><section class="VPSidebarItem level-0 has-active" data-v-ec086cd5 data-v-2e0476bc><div class="item" role="button" tabindex="0" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><h2 class="text" data-v-2e0476bc>Integration & Events</h2><!----></div><div class="items" data-v-2e0476bc><!--[--><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Overview</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/public-events.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Public Events API</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/http-event-sink.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>HTTP Event Sink</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-c4daae71 data-v-d2aef184><div class="VPDoc has-sidebar has-aside" data-v-d2aef184 data-v-ef3a4dda><!--[--><!--]--><div class="container" data-v-ef3a4dda><div class="aside" data-v-ef3a4dda><div class="aside-curtain" data-v-ef3a4dda></div><div class="aside-container" data-v-ef3a4dda><div class="aside-content" data-v-ef3a4dda><div class="VPDocAside" data-v-ef3a4dda data-v-79cae1a0><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-79cae1a0 data-v-dfbd9fb8><div class="content" data-v-dfbd9fb8><div class="outline-marker" data-v-dfbd9fb8></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-dfbd9fb8>On this page</div><ul class="VPDocOutlineItem root" data-v-dfbd9fb8 data-v-da421f78><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-79cae1a0></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-ef3a4dda><div class="content-container" data-v-ef3a4dda><!--[--><!--]--><main class="main" data-v-ef3a4dda><div style="position:relative;" class="vp-doc _http-event-sink" data-v-ef3a4dda><div><h1 id="http-event-sink" tabindex="-1">HTTP event sink <a class="header-anchor" href="#http-event-sink" aria-label="Permalink to &quot;HTTP event sink&quot;">​</a></h1><p><code>createHttpEventSink</code> is an optional transport adapter. It batches envelopes, retries transient failures with exponential backoff, and sends JSON to a host-owned endpoint. It contains no Kindy backend URL or authentication policy.</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { KindyEditor } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;kindy-editor&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
21
+ <div id="app"><div class="Layout" data-v-c4daae71><!--[--><!--]--><!--[--><span tabindex="-1" data-v-17ad389b></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-17ad389b>Skip to content</a><!--]--><!----><header class="VPNav" data-v-c4daae71 data-v-a46e73f0><div class="VPNavBar" data-v-a46e73f0 data-v-bb7529ee><div class="wrapper" data-v-bb7529ee><div class="container" data-v-bb7529ee><div class="title" data-v-bb7529ee><div class="VPNavBarTitle has-sidebar" data-v-bb7529ee data-v-5622a072><a class="title" href="/" data-v-5622a072><!--[--><!--]--><!----><span data-v-5622a072>Kindy Editor</span><!--[--><!--]--></a></div></div><div class="content" data-v-bb7529ee><div class="content-body" data-v-bb7529ee><!--[--><!--]--><div class="VPNavBarSearch search" data-v-bb7529ee><!----></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-bb7529ee data-v-5bacfe89><span id="main-nav-aria-label" class="visually-hidden" data-v-5bacfe89> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/fonts.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Fonts & CDN</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/table-editing.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Table editing</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/public-events.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Public Events</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/http-event-sink.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>HTTP Event Sink</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-bb7529ee data-v-123fcb2f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-123fcb2f data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-bb7529ee data-v-08b35e6f data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-bb7529ee data-v-0c845e5d data-v-40e55da0><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-40e55da0><span class="vpi-more-horizontal icon" data-v-40e55da0></span></button><div class="menu" data-v-40e55da0><div class="VPMenu" data-v-40e55da0 data-v-39eca401><!----><!--[--><!--[--><!----><div class="group" data-v-0c845e5d><div class="item appearance" data-v-0c845e5d><p class="label" data-v-0c845e5d>Appearance</p><div class="appearance-action" data-v-0c845e5d><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-0c845e5d data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div></div></div><div class="group" data-v-0c845e5d><div class="item social-links" data-v-0c845e5d><div class="VPSocialLinks social-links-list" data-v-0c845e5d data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-bb7529ee data-v-670493dd><span class="container" data-v-670493dd><span class="top" data-v-670493dd></span><span class="middle" data-v-670493dd></span><span class="bottom" data-v-670493dd></span></span></button></div></div></div></div><div class="divider" data-v-bb7529ee><div class="divider-line" data-v-bb7529ee></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-c4daae71 data-v-9d8129cc><div class="container" data-v-9d8129cc><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-9d8129cc><span class="vpi-align-left menu-icon" data-v-9d8129cc></span><span class="menu-text" data-v-9d8129cc>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-9d8129cc data-v-d61422fe><button data-v-d61422fe>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-c4daae71 data-v-12a40e3d><div class="curtain" data-v-12a40e3d></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-12a40e3d><span class="visually-hidden" id="sidebar-aria-label" data-v-12a40e3d> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-ec086cd5><section class="VPSidebarItem level-0 has-active" data-v-ec086cd5 data-v-2e0476bc><div class="item" role="button" tabindex="0" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><h2 class="text" data-v-2e0476bc>Integration & Events</h2><!----></div><div class="items" data-v-2e0476bc><!--[--><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Overview</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/fonts.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Fonts & CDN</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/table-editing.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Table editing</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/public-events.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>Public Events API</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-2e0476bc><div class="item" data-v-2e0476bc><div class="indicator" data-v-2e0476bc></div><a class="VPLink link link" href="/http-event-sink.html" data-v-2e0476bc><!--[--><p class="text" data-v-2e0476bc>HTTP Event Sink</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-c4daae71 data-v-d2aef184><div class="VPDoc has-sidebar has-aside" data-v-d2aef184 data-v-ef3a4dda><!--[--><!--]--><div class="container" data-v-ef3a4dda><div class="aside" data-v-ef3a4dda><div class="aside-curtain" data-v-ef3a4dda></div><div class="aside-container" data-v-ef3a4dda><div class="aside-content" data-v-ef3a4dda><div class="VPDocAside" data-v-ef3a4dda data-v-79cae1a0><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-79cae1a0 data-v-dfbd9fb8><div class="content" data-v-dfbd9fb8><div class="outline-marker" data-v-dfbd9fb8></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-dfbd9fb8>On this page</div><ul class="VPDocOutlineItem root" data-v-dfbd9fb8 data-v-da421f78><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-79cae1a0></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-ef3a4dda><div class="content-container" data-v-ef3a4dda><!--[--><!--]--><main class="main" data-v-ef3a4dda><div style="position:relative;" class="vp-doc _http-event-sink" data-v-ef3a4dda><div><h1 id="http-event-sink" tabindex="-1">HTTP event sink <a class="header-anchor" href="#http-event-sink" aria-label="Permalink to &quot;HTTP event sink&quot;">​</a></h1><p><code>createHttpEventSink</code> is an optional transport adapter. It batches envelopes, retries transient failures with exponential backoff, and sends JSON to a host-owned endpoint. It contains no Kindy backend URL or authentication policy.</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { KindyEditor } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;kindy-editor&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
22
22
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { createHttpEventSink } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;kindy-editor/events&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
23
23
  <span class="line"></span>
24
24
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> sink</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> createHttpEventSink</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
@@ -44,7 +44,7 @@
44
44
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;schemaVersion&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;1.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
45
45
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;events&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [{ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;schemaVersion&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;1.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;id&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;...&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">&quot;type&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;...&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }]</span></span>
46
46
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Delivery is at-least-once, so the receiver must deduplicate by event <code>id</code>. Recommended endpoint behavior:</p><ol><li>Authenticate the request and derive the real tenant/user from the session.</li><li>Validate every envelope against <code>kindy-editor/events/schema</code>.</li><li>Verify the user can access <code>document.id</code>; never trust browser <code>actor</code> for auth.</li><li>Insert events and an outbox row in one database transaction using a unique constraint on <code>(tenant_id, event_id)</code>.</li><li>Return 2xx only after durable acceptance; process webhooks/analytics from the outbox asynchronously.</li></ol><p>The in-memory adapter is intentionally small. Applications that require delivery across browser crashes should persist failed batches in IndexedDB or send critical events through their normal document-save API instead.</p></div></div></main><footer class="VPDocFooter" data-v-ef3a4dda data-v-07d71ff1><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-07d71ff1><span class="visually-hidden" id="doc-footer-aria-label" data-v-07d71ff1>Pager</span><div class="pager" data-v-07d71ff1><a class="VPLink link pager-link prev" href="/public-events.html" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Previous page</span><span class="title" data-v-07d71ff1>Public Events API</span><!--]--></a></div><div class="pager" data-v-07d71ff1><!----></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
47
- <script>window.__VP_HASH_MAP__=JSON.parse("{\"http-event-sink.md\":\"BViKRVhd\",\"index.md\":\"Bc6V9_E3\",\"public-events.md\":\"CMTD1Y84\",\"review-comments.md\":\"19jCC2Pi\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Kindy Editor\",\"description\":\"Canvas-based Word Document Editor Documentation\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Public Events\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}],\"sidebar\":[{\"text\":\"Integration & Events\",\"items\":[{\"text\":\"Overview\",\"link\":\"/\"},{\"text\":\"Public Events API\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/hotienky/editor\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
47
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"fonts.md\":\"W87hfQua\",\"http-event-sink.md\":\"D7mTerle\",\"index.md\":\"DN1JXJhG\",\"public-events.md\":\"Rj44CdBr\",\"review-comments.md\":\"466PcTcC\",\"table-editing.md\":\"Bso_sgTE\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Kindy Editor\",\"description\":\"Canvas-based Word Document Editor Documentation\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Fonts & CDN\",\"link\":\"/fonts\"},{\"text\":\"Table editing\",\"link\":\"/table-editing\"},{\"text\":\"Public Events\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}],\"sidebar\":[{\"text\":\"Integration & Events\",\"items\":[{\"text\":\"Overview\",\"link\":\"/\"},{\"text\":\"Fonts & CDN\",\"link\":\"/fonts\"},{\"text\":\"Table editing\",\"link\":\"/table-editing\"},{\"text\":\"Public Events API\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/hotienky/editor\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
48
48
 
49
49
  </body>
50
50
  </html>
@@ -9,17 +9,17 @@
9
9
  <link rel="preload stylesheet" href="/assets/style.D6g4bpRC.css" as="style">
10
10
  <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
11
 
12
- <script type="module" src="/assets/app.DYJtoZwO.js"></script>
12
+ <script type="module" src="/assets/app.rXBVdtfQ.js"></script>
13
13
  <link rel="preload" href="/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
14
- <link rel="modulepreload" href="/assets/chunks/theme.BTGY7hKU.js">
15
- <link rel="modulepreload" href="/assets/chunks/framework.DCJ5T8RY.js">
16
- <link rel="modulepreload" href="/assets/index.md.Bc6V9_E3.lean.js">
14
+ <link rel="modulepreload" href="/assets/chunks/theme.CFP3sGN8.js">
15
+ <link rel="modulepreload" href="/assets/chunks/framework.D9_ZvfG7.js">
16
+ <link rel="modulepreload" href="/assets/index.md.DN1JXJhG.lean.js">
17
17
  <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
18
18
  <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
19
19
  </head>
20
20
  <body>
21
- <div id="app"><div class="Layout" data-v-c4daae71><!--[--><!--]--><!--[--><span tabindex="-1" data-v-17ad389b></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-17ad389b>Skip to content</a><!--]--><!----><header class="VPNav" data-v-c4daae71 data-v-a46e73f0><div class="VPNavBar" data-v-a46e73f0 data-v-bb7529ee><div class="wrapper" data-v-bb7529ee><div class="container" data-v-bb7529ee><div class="title" data-v-bb7529ee><div class="VPNavBarTitle" data-v-bb7529ee data-v-5622a072><a class="title" href="/" data-v-5622a072><!--[--><!--]--><!----><span data-v-5622a072>Kindy Editor</span><!--[--><!--]--></a></div></div><div class="content" data-v-bb7529ee><div class="content-body" data-v-bb7529ee><!--[--><!--]--><div class="VPNavBarSearch search" data-v-bb7529ee><!----></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-bb7529ee data-v-5bacfe89><span id="main-nav-aria-label" class="visually-hidden" data-v-5bacfe89> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/public-events.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Public Events</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/http-event-sink.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>HTTP Event Sink</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-bb7529ee data-v-123fcb2f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-123fcb2f data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-bb7529ee data-v-08b35e6f data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-bb7529ee data-v-0c845e5d data-v-40e55da0><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-40e55da0><span class="vpi-more-horizontal icon" data-v-40e55da0></span></button><div class="menu" data-v-40e55da0><div class="VPMenu" data-v-40e55da0 data-v-39eca401><!----><!--[--><!--[--><!----><div class="group" data-v-0c845e5d><div class="item appearance" data-v-0c845e5d><p class="label" data-v-0c845e5d>Appearance</p><div class="appearance-action" data-v-0c845e5d><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-0c845e5d data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div></div></div><div class="group" data-v-0c845e5d><div class="item social-links" data-v-0c845e5d><div class="VPSocialLinks social-links-list" data-v-0c845e5d data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-bb7529ee data-v-670493dd><span class="container" data-v-670493dd><span class="top" data-v-670493dd></span><span class="middle" data-v-670493dd></span><span class="bottom" data-v-670493dd></span></span></button></div></div></div></div><div class="divider" data-v-bb7529ee><div class="divider-line" data-v-bb7529ee></div></div></div><!----></header><!----><!----><div class="VPContent is-home" id="VPContent" data-v-c4daae71 data-v-d2aef184><div class="VPHome" data-v-d2aef184 data-v-8b59c434><!--[--><!--]--><div class="VPHero VPHomeHero" data-v-8b59c434 data-v-d266baba><div class="container" data-v-d266baba><div class="main" data-v-d266baba><!--[--><!--]--><!--[--><h1 class="heading" data-v-d266baba><span class="name clip" data-v-d266baba>Kindy Editor</span><span class="text" data-v-d266baba>Documentation</span></h1><p class="tagline" data-v-d266baba>Trình soạn thảo tài liệu Word canvas chất lượng cao, hỗ trợ full layout engine và external event streaming.</p><!--]--><!--[--><!--]--><div class="actions" data-v-d266baba><!--[--><div class="action" data-v-d266baba><a class="VPButton medium brand" href="/public-events.html" data-v-d266baba data-v-b20c43c2>Public Events API</a></div><div class="action" data-v-d266baba><a class="VPButton medium alt" href="/review-comments.html" data-v-d266baba data-v-b20c43c2>Review Comments &amp; Mentions</a></div><!--]--></div><!--[--><!--]--></div><!----></div></div><!--[--><!--]--><!--[--><!--]--><div class="VPFeatures VPHomeFeatures" data-v-8b59c434 data-v-1da4ff3d><div class="container" data-v-1da4ff3d><div class="items" data-v-1da4ff3d><!--[--><div class="grid-3 item" data-v-1da4ff3d><div class="VPLink no-icon VPFeature" data-v-1da4ff3d data-v-95001937><!--[--><article class="box" data-v-95001937><!----><h2 class="title" data-v-95001937>📄 Canvas-based DOCX Engine</h2><p class="details" data-v-95001937>Render và xử lý layout văn bản Word chính xác trên Canvas với tốc độ cao.</p><!----></article><!--]--></div></div><div class="grid-3 item" data-v-1da4ff3d><div class="VPLink no-icon VPFeature" data-v-1da4ff3d data-v-95001937><!--[--><article class="box" data-v-95001937><!----><h2 class="title" data-v-95001937>⚡ Real-time Event Stream</h2><p class="details" data-v-95001937>Hệ thống event versioned mạnh mẽ cho autosave, audit log, workflow và tracking.</p><!----></article><!--]--></div></div><div class="grid-3 item" data-v-1da4ff3d><div class="VPLink no-icon VPFeature" data-v-1da4ff3d data-v-95001937><!--[--><article class="box" data-v-95001937><!----><h2 class="title" data-v-95001937>🔌 Dễ dàng tích hợp</h2><p class="details" data-v-95001937>Hỗ trợ TypeScript definitions, host-owned @mention picker, capability callbacks và notification events.</p><!----></article><!--]--></div></div><!--]--></div></div></div><!--[--><!--]--><div class="vp-doc container" style="" data-v-8b59c434 data-v-2498cc33><!--[--><div style="position:relative;" data-v-8b59c434><div></div></div><!--]--></div></div></div><!----><!--[--><!--]--></div></div>
22
- <script>window.__VP_HASH_MAP__=JSON.parse("{\"http-event-sink.md\":\"BViKRVhd\",\"index.md\":\"Bc6V9_E3\",\"public-events.md\":\"CMTD1Y84\",\"review-comments.md\":\"19jCC2Pi\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Kindy Editor\",\"description\":\"Canvas-based Word Document Editor Documentation\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Public Events\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}],\"sidebar\":[{\"text\":\"Integration & Events\",\"items\":[{\"text\":\"Overview\",\"link\":\"/\"},{\"text\":\"Public Events API\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/hotienky/editor\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
21
+ <div id="app"><div class="Layout" data-v-c4daae71><!--[--><!--]--><!--[--><span tabindex="-1" data-v-17ad389b></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-17ad389b>Skip to content</a><!--]--><!----><header class="VPNav" data-v-c4daae71 data-v-a46e73f0><div class="VPNavBar" data-v-a46e73f0 data-v-bb7529ee><div class="wrapper" data-v-bb7529ee><div class="container" data-v-bb7529ee><div class="title" data-v-bb7529ee><div class="VPNavBarTitle" data-v-bb7529ee data-v-5622a072><a class="title" href="/" data-v-5622a072><!--[--><!--]--><!----><span data-v-5622a072>Kindy Editor</span><!--[--><!--]--></a></div></div><div class="content" data-v-bb7529ee><div class="content-body" data-v-bb7529ee><!--[--><!--]--><div class="VPNavBarSearch search" data-v-bb7529ee><!----></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-bb7529ee data-v-5bacfe89><span id="main-nav-aria-label" class="visually-hidden" data-v-5bacfe89> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/fonts.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Fonts & CDN</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/table-editing.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Table editing</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/public-events.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Public Events</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/http-event-sink.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>HTTP Event Sink</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-bb7529ee data-v-123fcb2f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-123fcb2f data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-bb7529ee data-v-08b35e6f data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-bb7529ee data-v-0c845e5d data-v-40e55da0><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-40e55da0><span class="vpi-more-horizontal icon" data-v-40e55da0></span></button><div class="menu" data-v-40e55da0><div class="VPMenu" data-v-40e55da0 data-v-39eca401><!----><!--[--><!--[--><!----><div class="group" data-v-0c845e5d><div class="item appearance" data-v-0c845e5d><p class="label" data-v-0c845e5d>Appearance</p><div class="appearance-action" data-v-0c845e5d><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-0c845e5d data-v-fa1daf57 data-v-7012e5dd><span class="check" data-v-7012e5dd><span class="icon" data-v-7012e5dd><!--[--><span class="vpi-sun sun" data-v-fa1daf57></span><span class="vpi-moon moon" data-v-fa1daf57></span><!--]--></span></span></button></div></div></div><div class="group" data-v-0c845e5d><div class="item social-links" data-v-0c845e5d><div class="VPSocialLinks social-links-list" data-v-0c845e5d data-v-f2234a39><!--[--><a class="VPSocialLink no-icon" href="https://github.com/hotienky/editor" aria-label="github" target="_blank" rel="noopener" data-v-f2234a39 data-v-fbf39218><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-bb7529ee data-v-670493dd><span class="container" data-v-670493dd><span class="top" data-v-670493dd></span><span class="middle" data-v-670493dd></span><span class="bottom" data-v-670493dd></span></span></button></div></div></div></div><div class="divider" data-v-bb7529ee><div class="divider-line" data-v-bb7529ee></div></div></div><!----></header><!----><!----><div class="VPContent is-home" id="VPContent" data-v-c4daae71 data-v-d2aef184><div class="VPHome" data-v-d2aef184 data-v-8b59c434><!--[--><!--]--><div class="VPHero VPHomeHero" data-v-8b59c434 data-v-d266baba><div class="container" data-v-d266baba><div class="main" data-v-d266baba><!--[--><!--]--><!--[--><h1 class="heading" data-v-d266baba><span class="name clip" data-v-d266baba>Kindy Editor</span><span class="text" data-v-d266baba>Documentation</span></h1><p class="tagline" data-v-d266baba>Trình soạn thảo tài liệu Word canvas chất lượng cao, hỗ trợ full layout engine và external event streaming.</p><!--]--><!--[--><!--]--><div class="actions" data-v-d266baba><!--[--><div class="action" data-v-d266baba><a class="VPButton medium brand" href="/public-events.html" data-v-d266baba data-v-b20c43c2>Public Events API</a></div><div class="action" data-v-d266baba><a class="VPButton medium alt" href="/review-comments.html" data-v-d266baba data-v-b20c43c2>Review Comments &amp; Mentions</a></div><!--]--></div><!--[--><!--]--></div><!----></div></div><!--[--><!--]--><!--[--><!--]--><div class="VPFeatures VPHomeFeatures" data-v-8b59c434 data-v-1da4ff3d><div class="container" data-v-1da4ff3d><div class="items" data-v-1da4ff3d><!--[--><div class="grid-3 item" data-v-1da4ff3d><div class="VPLink no-icon VPFeature" data-v-1da4ff3d data-v-95001937><!--[--><article class="box" data-v-95001937><!----><h2 class="title" data-v-95001937>📄 Canvas-based DOCX Engine</h2><p class="details" data-v-95001937>Render và xử lý layout văn bản Word chính xác trên Canvas với tốc độ cao.</p><!----></article><!--]--></div></div><div class="grid-3 item" data-v-1da4ff3d><div class="VPLink no-icon VPFeature" data-v-1da4ff3d data-v-95001937><!--[--><article class="box" data-v-95001937><!----><h2 class="title" data-v-95001937>⚡ Real-time Event Stream</h2><p class="details" data-v-95001937>Hệ thống event versioned mạnh mẽ cho autosave, audit log, workflow và tracking.</p><!----></article><!--]--></div></div><div class="grid-3 item" data-v-1da4ff3d><div class="VPLink no-icon VPFeature" data-v-1da4ff3d data-v-95001937><!--[--><article class="box" data-v-95001937><!----><h2 class="title" data-v-95001937>🔌 Dễ dàng tích hợp</h2><p class="details" data-v-95001937>Hỗ trợ TypeScript definitions, host-owned @mention picker, capability callbacks và notification events.</p><!----></article><!--]--></div></div><!--]--></div></div></div><!--[--><!--]--><div class="vp-doc container" style="" data-v-8b59c434 data-v-2498cc33><!--[--><div style="position:relative;" data-v-8b59c434><div></div></div><!--]--></div></div></div><!----><!--[--><!--]--></div></div>
22
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"fonts.md\":\"W87hfQua\",\"http-event-sink.md\":\"D7mTerle\",\"index.md\":\"DN1JXJhG\",\"public-events.md\":\"Rj44CdBr\",\"review-comments.md\":\"466PcTcC\",\"table-editing.md\":\"Bso_sgTE\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Kindy Editor\",\"description\":\"Canvas-based Word Document Editor Documentation\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Fonts & CDN\",\"link\":\"/fonts\"},{\"text\":\"Table editing\",\"link\":\"/table-editing\"},{\"text\":\"Public Events\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}],\"sidebar\":[{\"text\":\"Integration & Events\",\"items\":[{\"text\":\"Overview\",\"link\":\"/\"},{\"text\":\"Fonts & CDN\",\"link\":\"/fonts\"},{\"text\":\"Table editing\",\"link\":\"/table-editing\"},{\"text\":\"Public Events API\",\"link\":\"/public-events\"},{\"text\":\"HTTP Event Sink\",\"link\":\"/http-event-sink\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/hotienky/editor\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
23
23
 
24
24
  </body>
25
25
  </html>