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.
- package/README.md +27 -2
- package/dist-lib/builder.js +4 -4
- package/dist-lib/{editorApp-F48A7I6z.js → editorApp-CtrnSSsP.js} +9620 -8998
- package/dist-lib/events.js +1 -1
- package/dist-lib/export.js +6 -6
- package/dist-lib/generate-toc.js +3 -3
- package/dist-lib/import.js +2 -2
- package/dist-lib/{kindy-editor-DnWvHijW.js → kindy-editor-CoeqFYYG.js} +100 -85
- package/dist-lib/kindy-editor.js +1 -1
- package/dist-lib/measure.js +3 -3
- package/dist-lib/{measureHost-Ca_gM01T.js → measureHost-mtLj644m.js} +2 -2
- package/dist-lib/{metrics-BFTHu_jF.js → metrics-DHgtBP-R.js} +1 -1
- package/dist-lib/{paintStyle-GQFOpJKx.js → paintStyle-Cjk9egel.js} +10 -9
- package/dist-lib/{parse-DmTwoFz6.js → parse-D3V5Abci.js} +11 -11
- package/dist-lib/{pipeline-BTemNJdx.js → pipeline-JfHxzjns.js} +1 -1
- package/dist-lib/recalc-docx.js +1 -1
- package/dist-lib/{recalcToc-c9kIMGbk.js → recalcToc-MRszlj8g.js} +80 -80
- package/dist-lib/{stylesheet-BcU1Q_JI.js → stylesheet-E5bu1xch.js} +1 -1
- package/dist-lib/{toc-mgkp-28Y.js → toc-DSwkNKls.js} +15 -15
- package/dist-lib/{webmcp-BLC_QALL.js → webmcp-Dr3n_xAO.js} +2 -2
- package/docs/.vitepress/cache/deps/_metadata.json +7 -7
- package/docs/.vitepress/config.ts +4 -0
- package/docs/.vitepress/dist/404.html +2 -2
- package/docs/.vitepress/dist/assets/{app.DYJtoZwO.js → app.rXBVdtfQ.js} +1 -1
- package/docs/.vitepress/dist/assets/chunks/framework.D9_ZvfG7.js +19 -0
- package/docs/.vitepress/dist/assets/chunks/{theme.BTGY7hKU.js → theme.CFP3sGN8.js} +1 -1
- package/docs/.vitepress/dist/assets/fonts.md.W87hfQua.js +75 -0
- package/docs/.vitepress/dist/assets/fonts.md.W87hfQua.lean.js +1 -0
- package/docs/.vitepress/dist/assets/{http-event-sink.md.BViKRVhd.js → http-event-sink.md.D7mTerle.js} +1 -1
- package/docs/.vitepress/dist/assets/{http-event-sink.md.BViKRVhd.lean.js → http-event-sink.md.D7mTerle.lean.js} +1 -1
- package/docs/.vitepress/dist/assets/{index.md.Bc6V9_E3.js → index.md.DN1JXJhG.js} +1 -1
- package/docs/.vitepress/dist/assets/{index.md.Bc6V9_E3.lean.js → index.md.DN1JXJhG.lean.js} +1 -1
- package/docs/.vitepress/dist/assets/{public-events.md.CMTD1Y84.js → public-events.md.Rj44CdBr.js} +1 -1
- package/docs/.vitepress/dist/assets/{public-events.md.CMTD1Y84.lean.js → public-events.md.Rj44CdBr.lean.js} +1 -1
- package/docs/.vitepress/dist/assets/{review-comments.md.19jCC2Pi.js → review-comments.md.466PcTcC.js} +1 -1
- package/docs/.vitepress/dist/assets/{review-comments.md.19jCC2Pi.lean.js → review-comments.md.466PcTcC.lean.js} +1 -1
- package/docs/.vitepress/dist/assets/table-editing.md.Bso_sgTE.js +28 -0
- package/docs/.vitepress/dist/assets/table-editing.md.Bso_sgTE.lean.js +1 -0
- package/docs/.vitepress/dist/fonts.html +99 -0
- package/docs/.vitepress/dist/hashmap.json +1 -1
- package/docs/.vitepress/dist/http-event-sink.html +6 -6
- package/docs/.vitepress/dist/index.html +6 -6
- package/docs/.vitepress/dist/public-events.html +7 -7
- package/docs/.vitepress/dist/review-comments.html +6 -6
- package/docs/.vitepress/dist/table-editing.html +52 -0
- package/docs/fonts.md +139 -0
- package/docs/table-editing.md +102 -0
- package/package.json +4 -1
- package/types/kindy-editor.d.ts +65 -1
- package/docs/.vitepress/dist/assets/chunks/framework.DCJ5T8RY.js +0 -19
|
@@ -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.
|
|
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.
|
|
15
|
-
<link rel="modulepreload" href="/assets/chunks/framework.
|
|
16
|
-
<link rel="modulepreload" href="/assets/public-events.md.
|
|
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/public-events.md.Rj44CdBr.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 active" 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="/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 _public-events" data-v-ef3a4dda><div><h1 id="public-events-and-external-integrations" tabindex="-1">Public events and external integrations <a class="header-anchor" href="#public-events-and-external-integrations" aria-label="Permalink to "Public events and external integrations""></a></h1><p>Kindy Editor exposes a versioned event stream for host applications that need autosave, audit, workflow, analytics, notifications, or backend integration. The existing <code>editor.on("presence", ...)</code> API remains supported; new integrations should use <code>editor.events</code> because it provides typed namespaced events and a stable envelope.</p><h2 id="quick-start" tabindex="-1">Quick start <a class="header-anchor" href="#quick-start" aria-label="Permalink to "Quick start""></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;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "kindy-editor"</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 active" 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 _public-events" data-v-ef3a4dda><div><h1 id="public-events-and-external-integrations" tabindex="-1">Public events and external integrations <a class="header-anchor" href="#public-events-and-external-integrations" aria-label="Permalink to "Public events and external integrations""></a></h1><p>Kindy Editor exposes a versioned event stream for host applications that need autosave, audit, workflow, analytics, notifications, or backend integration. The existing <code>editor.on("presence", ...)</code> API remains supported; new integrations should use <code>editor.events</code> because it provides typed namespaced events and a stable envelope.</p><h2 id="quick-start" tabindex="-1">Quick start <a class="header-anchor" href="#quick-start" aria-label="Permalink to "Quick start""></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;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "kindy-editor"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
|
|
22
22
|
<span class="line"></span>
|
|
23
23
|
<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>
|
|
24
24
|
<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;">"#editor"</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>
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</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;">event, actor: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> };</span></span>
|
|
57
57
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
58
58
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
59
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p><code>operations</code> adds forward model operations. <code>full</code> also adds selections and the canonical <code>Change</code> when available. A host should use the least detailed setting it needs, redact user data before transport, and avoid sending document content to general-purpose analytics services.</p><h2 id="event-catalog" tabindex="-1">Event catalog <a class="header-anchor" href="#event-catalog" aria-label="Permalink to "Event catalog""></a></h2><table tabindex="0"><thead><tr><th>Domain</th><th>Events</th></tr></thead><tbody><tr><td>Editor</td><td><code>editor.ready</code>, <code>editor.destroyed</code>, <code>editor.mode.changed</code>, <code>editor.error</code></td></tr><tr><td>Open/import</td><td><code>document.open.*</code>, <code>document.import.*</code></td></tr><tr><td>Export</td><td><code>document.export.*</code></td></tr><tr><td>Changes</td><td><code>document.change.applied</code>, <code>document.change.committed</code>, <code>document.change.rejected</code>, <code>document.remoteChange.applied</code></td></tr><tr><td>Review</td><td><code>review.operation.applied</code>, <code>review.suggestion.*</code>, <code>review.thread.*</code>, <code>review.comment.*</code></td></tr><tr><td>Collaboration</td><td><code>collaboration.connecting</code>, <code>collaboration.connected</code>, <code>collaboration.disconnected</code>, <code>collaboration.user.*</code>, <code>collaboration.presence.changed</code></td></tr><tr><td>Other</td><td><code>document.shared</code>, <code>selection.changed</code>, <code>custom</code></td></tr></tbody></table><p>Review emits both <code>review.operation.applied</code> (complete audit payload) and a derived semantic event. A root comment emits both <code>review.thread.created</code> and <code>review.comment.added</code>; replies set <code>isReply: true</code>. Comment events include stable thread/comment/author ids and structured mention user ids. An edit additionally includes <code>newlyMentionedUserIds</code>, and tombstoning emits <code>review.comment.deleted</code>. See <a href="./review-comments.html">Review comments and @mentions</a> for the integration and notification flow.</p><h2 id="what-each-layer-owns" tabindex="-1">What each layer owns <a class="header-anchor" href="#what-each-layer-owns" aria-label="Permalink to "What each layer owns""></a></h2><p>The editor library owns content changes, review operations, editor state, import/export, and live collaboration/presence events. The embedding application should emit its own domain events for rename/archive/delete, permissions, approval workflow, AI job lifecycle, and attachment upload. The backend owns authenticated identity, canonical sequence, authorization, durable audit, and an outbox for downstream delivery.</p><p>Do not treat a browser event sink as the only audit log. It is observability and integration plumbing; the backend change/review log remains the source of truth.</p><h2 id="compatibility" tabindex="-1">Compatibility <a class="header-anchor" href="#compatibility" aria-label="Permalink to "Compatibility""></a></h2><p>Event names and required envelope fields are stable for schema major version 1. New optional data fields and new event names may be added in minor releases. Consumers should ignore unknown fields/events. A breaking rename or semantic change requires a new schema major version.</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>
|
|
60
|
-
<script>window.__VP_HASH_MAP__=JSON.parse("{\"http-event-sink.md\":\"
|
|
59
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p><code>operations</code> adds forward model operations. <code>full</code> also adds selections and the canonical <code>Change</code> when available. A host should use the least detailed setting it needs, redact user data before transport, and avoid sending document content to general-purpose analytics services.</p><h2 id="event-catalog" tabindex="-1">Event catalog <a class="header-anchor" href="#event-catalog" aria-label="Permalink to "Event catalog""></a></h2><table tabindex="0"><thead><tr><th>Domain</th><th>Events</th></tr></thead><tbody><tr><td>Editor</td><td><code>editor.ready</code>, <code>editor.destroyed</code>, <code>editor.mode.changed</code>, <code>editor.error</code></td></tr><tr><td>Open/import</td><td><code>document.open.*</code>, <code>document.import.*</code></td></tr><tr><td>Export</td><td><code>document.export.*</code></td></tr><tr><td>Changes</td><td><code>document.change.applied</code>, <code>document.change.committed</code>, <code>document.change.rejected</code>, <code>document.remoteChange.applied</code></td></tr><tr><td>Review</td><td><code>review.operation.applied</code>, <code>review.suggestion.*</code>, <code>review.thread.*</code>, <code>review.comment.*</code></td></tr><tr><td>Collaboration</td><td><code>collaboration.connecting</code>, <code>collaboration.connected</code>, <code>collaboration.disconnected</code>, <code>collaboration.user.*</code>, <code>collaboration.presence.changed</code></td></tr><tr><td>Other</td><td><code>document.shared</code>, <code>selection.changed</code>, <code>custom</code></td></tr></tbody></table><p>Review emits both <code>review.operation.applied</code> (complete audit payload) and a derived semantic event. A root comment emits both <code>review.thread.created</code> and <code>review.comment.added</code>; replies set <code>isReply: true</code>. Comment events include stable thread/comment/author ids and structured mention user ids. An edit additionally includes <code>newlyMentionedUserIds</code>, and tombstoning emits <code>review.comment.deleted</code>. See <a href="./review-comments.html">Review comments and @mentions</a> for the integration and notification flow.</p><h2 id="what-each-layer-owns" tabindex="-1">What each layer owns <a class="header-anchor" href="#what-each-layer-owns" aria-label="Permalink to "What each layer owns""></a></h2><p>The editor library owns content changes, review operations, editor state, import/export, and live collaboration/presence events. The embedding application should emit its own domain events for rename/archive/delete, permissions, approval workflow, AI job lifecycle, and attachment upload. The backend owns authenticated identity, canonical sequence, authorization, durable audit, and an outbox for downstream delivery.</p><p>Do not treat a browser event sink as the only audit log. It is observability and integration plumbing; the backend change/review log remains the source of truth.</p><h2 id="compatibility" tabindex="-1">Compatibility <a class="header-anchor" href="#compatibility" aria-label="Permalink to "Compatibility""></a></h2><p>Event names and required envelope fields are stable for schema major version 1. New optional data fields and new event names may be added in minor releases. Consumers should ignore unknown fields/events. A breaking rename or semantic change requires a new schema major version.</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="/table-editing.html" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Previous page</span><span class="title" data-v-07d71ff1>Table editing</span><!--]--></a></div><div class="pager" data-v-07d71ff1><a class="VPLink link pager-link next" href="/http-event-sink.html" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Next page</span><span class="title" data-v-07d71ff1>HTTP Event Sink</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
60
|
+
<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>
|
|
61
61
|
|
|
62
62
|
</body>
|
|
63
63
|
</html>
|
|
@@ -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.
|
|
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.
|
|
15
|
-
<link rel="modulepreload" href="/assets/chunks/framework.
|
|
16
|
-
<link rel="modulepreload" href="/assets/review-comments.md.
|
|
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/review-comments.md.466PcTcC.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" 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" 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 _review-comments" data-v-ef3a4dda><div><h1 id="review-comments-discussions-and-mentions" tabindex="-1">Review comments, discussions, and @mentions <a class="header-anchor" href="#review-comments-discussions-and-mentions" aria-label="Permalink to "Review comments, discussions, and @mentions""></a></h1><p>Comments are anchored discussions that are separate from tracked Suggestions. Users can create and reply in <code>edit</code> and <code>suggest</code> mode; <code>view</code> mode is read-only. An anchor may be a text range or a collapsed caret. Rectangular table-cell selections are not supported yet.</p><h2 id="user-flow" tabindex="-1">User flow <a class="header-anchor" href="#user-flow" aria-label="Permalink to "User flow""></a></h2><p>A user can start a discussion from the <strong>+ Comment</strong> button, the floating comment action beside a text selection, or <code>Ctrl+Alt+M</code> / <code>Cmd+Option+M</code>. Submitting the root comment opens the inline thread beside the document and focuses the same thread in Review > Comments.</p><p>Open threads have a clickable count marker on the document, an always-available reply composer, and actions allowed by the host: Edit, Delete, Resolve, and Reopen. Delete creates a <code>Comment deleted</code> tombstone so replies and collaboration history remain intact. Resolved markers are hidden from the document and remain available through the Review panel's Open, Resolved, and All filters.</p><p>The public methods mirror the UI:</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;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> handle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><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;">whenReady</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" 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" 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 _review-comments" data-v-ef3a4dda><div><h1 id="review-comments-discussions-and-mentions" tabindex="-1">Review comments, discussions, and @mentions <a class="header-anchor" href="#review-comments-discussions-and-mentions" aria-label="Permalink to "Review comments, discussions, and @mentions""></a></h1><p>Comments are anchored discussions that are separate from tracked Suggestions. Users can create and reply in <code>edit</code> and <code>suggest</code> mode; <code>view</code> mode is read-only. An anchor may be a text range or a collapsed caret. Rectangular table-cell selections are not supported yet.</p><h2 id="user-flow" tabindex="-1">User flow <a class="header-anchor" href="#user-flow" aria-label="Permalink to "User flow""></a></h2><p>A user can start a discussion from the <strong>+ Comment</strong> button, the floating comment action beside a text selection, or <code>Ctrl+Alt+M</code> / <code>Cmd+Option+M</code>. Submitting the root comment opens the inline thread beside the document and focuses the same thread in Review > Comments.</p><p>Open threads have a clickable count marker on the document, an always-available reply composer, and actions allowed by the host: Edit, Delete, Resolve, and Reopen. Delete creates a <code>Comment deleted</code> tombstone so replies and collaboration history remain intact. Resolved markers are hidden from the document and remain available through the Review panel's Open, Resolved, and All filters.</p><p>The public methods mirror the UI:</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;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> handle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><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;">whenReady</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span>
|
|
22
22
|
<span class="line"></span>
|
|
23
23
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">handle.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">startComment</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span>
|
|
24
24
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">handle.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">openCommentThread</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(threadId);</span></span>
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> });</span></span>
|
|
89
89
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
90
90
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p>For durable notification delivery, consume the authoritative backend review log or transactional outbox. Deduplicate mention notifications with <code>documentId + commentId + mentionedUserId</code>; use a separate stable key for reply notifications. Never trust the browser-supplied actor or use client events as the only audit record.</p><p>Relevant events are:</p><ul><li><code>review.thread.created</code></li><li><code>review.comment.added</code></li><li><code>review.comment.edited</code></li><li><code>review.comment.deleted</code></li><li><code>review.thread.status.changed</code></li></ul><p>See <a href="./public-events.html">Public events</a> for the envelope, privacy controls, and compatibility rules.</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><!----></div><div class="pager" data-v-07d71ff1><a class="VPLink link pager-link next" href="/" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Next page</span><span class="title" data-v-07d71ff1>Overview</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
91
|
-
<script>window.__VP_HASH_MAP__=JSON.parse("{\"http-event-sink.md\":\"
|
|
91
|
+
<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>
|
|
92
92
|
|
|
93
93
|
</body>
|
|
94
94
|
</html>
|
|
@@ -0,0 +1,52 @@
|
|
|
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>Chỉnh sửa bảng | 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/table-editing.md.Bso_sgTE.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" href="/fonts.html" tabindex="0" data-v-5bacfe89 data-v-8dbce4d0><!--[--><span data-v-8dbce4d0>Fonts & CDN</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink active" 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 _table-editing" data-v-ef3a4dda><div><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 "Chỉnh sửa bảng""></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>
|
|
22
|
+
<span class="line"><span> └─ TableSelection (cell | row | column | table)</span></span>
|
|
23
|
+
<span class="line"><span> └─ TableTargetResolver</span></span>
|
|
24
|
+
<span class="line"><span> └─ TableGridMutationService</span></span>
|
|
25
|
+
<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 "Thao tác trên giao diện""></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 "API tích hợp""></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;"> "kindy-editor"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
|
|
26
|
+
<span class="line"></span>
|
|
27
|
+
<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>
|
|
28
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> story: { kind: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"body"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ancestors: [],</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> tableId: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"table-42"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
31
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span>
|
|
32
|
+
<span class="line"></span>
|
|
33
|
+
<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>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> kind: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"row"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> table: path,</span></span>
|
|
36
|
+
<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>
|
|
37
|
+
<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>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span>
|
|
39
|
+
<span class="line"></span>
|
|
40
|
+
<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>
|
|
41
|
+
<span class="line"></span>
|
|
42
|
+
<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;">"deleteRow"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)) {</span></span>
|
|
43
|
+
<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;">"deleteRow"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
|
|
44
|
+
<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>
|
|
45
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "insertRowAbove"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "insertRowBelow"</span></span>
|
|
46
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "insertColumnLeft"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "insertColumnRight"</span></span>
|
|
47
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "deleteRow"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "deleteColumn"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "deleteTable"</span></span>
|
|
48
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "mergeCells"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "unmergeCell"</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 "Invariant và dữ liệu lỗi""></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 "Giới hạn hiện tại""></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 "Kiểm thử""></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></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="/fonts.html" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Previous page</span><span class="title" data-v-07d71ff1>Fonts & CDN</span><!--]--></a></div><div class="pager" data-v-07d71ff1><a class="VPLink link pager-link next" href="/public-events.html" data-v-07d71ff1><!--[--><span class="desc" data-v-07d71ff1>Next page</span><span class="title" data-v-07d71ff1>Public Events API</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
49
|
+
<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>
|
|
50
|
+
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
package/docs/fonts.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Font tích hợp, font riêng và CDN
|
|
2
|
+
|
|
3
|
+
Kindy Editor dùng cùng một font source cho Canvas layout và export. Vì vậy font
|
|
4
|
+
không chỉ cần hiển thị được trong CSS: exporter còn phải đọc được binary font để
|
|
5
|
+
đo glyph và nhúng vào PDF. Font ngoài hiện hỗ trợ **TTF/OTF**; WOFF/WOFF2 chưa dùng
|
|
6
|
+
được cho export PDF.
|
|
7
|
+
|
|
8
|
+
## Khai báo nhiều font trực tiếp
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { KindyEditor, type FontsConfig } from "kindy-editor";
|
|
12
|
+
|
|
13
|
+
const fonts: FontsConfig = {
|
|
14
|
+
// Các URL tương đối bên dưới được ghép với thư mục CDN có version.
|
|
15
|
+
baseUrl: "https://cdn.example.vn/kindy-fonts/2026-08/",
|
|
16
|
+
fonts: [
|
|
17
|
+
{
|
|
18
|
+
family: "Inter",
|
|
19
|
+
label: "Inter",
|
|
20
|
+
faces: {
|
|
21
|
+
regular: "inter/Inter-Regular.ttf",
|
|
22
|
+
bold: "inter/Inter-Bold.ttf",
|
|
23
|
+
italic: "inter/Inter-Italic.ttf",
|
|
24
|
+
boldItalic: "inter/Inter-BoldItalic.ttf",
|
|
25
|
+
},
|
|
26
|
+
sizing: { ascent: 0.969, descent: 0.241 },
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
family: "Noto Serif",
|
|
30
|
+
faces: { regular: "noto-serif/NotoSerif-Regular.otf" },
|
|
31
|
+
sizing: { ascent: 1.069, descent: 0.293 },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const editor = new KindyEditor({
|
|
37
|
+
container: document.querySelector("#editor")!,
|
|
38
|
+
locale: "vi",
|
|
39
|
+
fonts,
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`regular` là bắt buộc. Face bị thiếu sẽ dùng lại Regular, bảo đảm preview và export
|
|
44
|
+
không tự tạo faux-bold/faux-italic khác nhau. `sizing` là ascent/descent chia cho
|
|
45
|
+
units-per-em của font; nên lấy từ `hhea`/`OS/2` của chính file font.
|
|
46
|
+
|
|
47
|
+
## Dùng font manifest trên CDN
|
|
48
|
+
|
|
49
|
+
Ứng dụng có thể đưa hàng loạt family vào một catalog có version:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"schemaVersion": "1.0",
|
|
54
|
+
"baseUrl": "./files/",
|
|
55
|
+
"fonts": [
|
|
56
|
+
{
|
|
57
|
+
"family": "Inter",
|
|
58
|
+
"faces": {
|
|
59
|
+
"regular": "Inter-Regular.ttf",
|
|
60
|
+
"bold": "Inter-Bold.ttf"
|
|
61
|
+
},
|
|
62
|
+
"sizing": { "ascent": 0.969, "descent": 0.241 }
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"family": "Noto Serif",
|
|
66
|
+
"faces": { "regular": "NotoSerif-Regular.otf" },
|
|
67
|
+
"sizing": { "ascent": 1.069, "descent": 0.293 }
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Sau đó chỉ cần truyền URL catalog:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
new KindyEditor({
|
|
77
|
+
container,
|
|
78
|
+
fonts: {
|
|
79
|
+
manifests: [
|
|
80
|
+
"https://cdn.example.vn/kindy-fonts/2026-08/manifest.json",
|
|
81
|
+
{
|
|
82
|
+
url: "https://static.example.vn/brand-fonts/manifest.json",
|
|
83
|
+
baseUrl: "https://static.example.vn/brand-fonts/files/",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
// Khai báo trực tiếp luôn ưu tiên hơn manifest nếu trùng family.
|
|
87
|
+
fonts: [companyFont],
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Catalog lỗi, timeout hoặc sai schema sẽ bị bỏ qua riêng lẻ; editor vẫn mở bằng
|
|
93
|
+
font tích hợp sẵn và các font còn lại.
|
|
94
|
+
|
|
95
|
+
## CDN có xác thực hoặc signed URL
|
|
96
|
+
|
|
97
|
+
`loader` được dùng chung khi tải manifest, khi hiển thị Canvas và khi chuẩn bị font
|
|
98
|
+
cho export. Binary đã tải được truyền vào worker; token và hàm loader không đi vào
|
|
99
|
+
worker.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
new KindyEditor({
|
|
103
|
+
container,
|
|
104
|
+
fonts: {
|
|
105
|
+
manifests: ["https://assets.example.vn/fonts/manifest.json"],
|
|
106
|
+
loader: async (request, signal) => {
|
|
107
|
+
const response = await fetch(request.url, {
|
|
108
|
+
signal,
|
|
109
|
+
headers: { Authorization: `Bearer ${await getAssetToken()}` },
|
|
110
|
+
credentials: "include",
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) throw new Error(`Font HTTP ${response.status}`);
|
|
113
|
+
return response.arrayBuffer();
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`request.kind` là `manifest` hoặc `font`; request font còn có `family` và `style`
|
|
120
|
+
để ứng dụng ghi log, cấp signed URL hoặc áp dụng policy riêng.
|
|
121
|
+
|
|
122
|
+
## Cấu hình CDN khuyến nghị
|
|
123
|
+
|
|
124
|
+
- Cho phép CORS từ domain chạy editor, hoặc dùng `loader` qua transport của hệ thống.
|
|
125
|
+
- Đặt version bất biến trong URL và trả `Cache-Control: public, max-age=31536000, immutable`.
|
|
126
|
+
- Không ghi đè file font ở cùng URL; đổi version khi binary hoặc metrics thay đổi.
|
|
127
|
+
- Tự host font thương mại theo đúng license. Không đưa Times New Roman lên CDN công
|
|
128
|
+
cộng nếu tổ chức chưa có quyền phân phối.
|
|
129
|
+
- 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
|
|
130
|
+
CDN lỗi không treo quá trình mở editor.
|
|
131
|
+
- Kiểm thử golden DOCX/PDF sau khi đổi font hoặc metrics vì thay đổi metrics có thể
|
|
132
|
+
làm thay đổi điểm xuống dòng và phân trang.
|
|
133
|
+
|
|
134
|
+
## Quan hệ với dung lượng bundle
|
|
135
|
+
|
|
136
|
+
Font khai báo qua URL/manifest không được đóng vào bundle JavaScript của
|
|
137
|
+
`kindy-editor`. Vì vậy có thể mở rộng catalog mà không làm tarball npm phình theo.
|
|
138
|
+
Ở 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
|
|
139
|
+
sự sử dụng thay vì preload toàn bộ catalog.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Chỉnh sửa bảng
|
|
2
|
+
|
|
3
|
+
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:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
TablePath (body/header/footer + ancestor table/cell IDs)
|
|
7
|
+
└─ TableSelection (cell | row | column | table)
|
|
8
|
+
└─ TableTargetResolver
|
|
9
|
+
└─ TableGridMutationService
|
|
10
|
+
└─ validateTableGrid trước và sau mutation
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
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ý. `rowSpan`/`colSpan`, ID, content, format ô, row properties và `colFractions` của phần còn sống được giữ lại.
|
|
14
|
+
|
|
15
|
+
## Thao tác trên giao diện
|
|
16
|
+
|
|
17
|
+
- Kéo qua nhiều ô để tạo cell selection hình chữ nhật.
|
|
18
|
+
- Di chuột vào một ô để hiện rail thao tác nhanh ngoài mép bảng, căn giữa theo hàng đang trỏ: **chèn hàng phía dưới**, **chèn cột bên phải** và nút **…** mở toàn bộ thao tác bảng. Rail là DOM overlay kích thước màn hình, không che nội dung và không bị phóng/co theo zoom của trang canvas.
|
|
19
|
+
- Bấm dải mỏng phía trái bảng để chọn cả hàng.
|
|
20
|
+
- Bấm dải mỏng phía trên bảng để chọn cả cột.
|
|
21
|
+
- Bấm góc trên-trái của bảng để chọn cả bảng.
|
|
22
|
+
- Chuột phải tại cả phần chữ, khoảng đệm hoặc ô trống đều nhận đúng ô đích và mở menu bảng. Dùng tab **Bảng**, thanh hover hoặc menu chuột phải để chèn/xóa, merge/unmerge.
|
|
23
|
+
- Nút không hợp lệ sẽ bị disable. Ví dụ, **Merge** bị tắt khi chỉ có một ô; **Unmerge** bị tắt khi ô không có span.
|
|
24
|
+
|
|
25
|
+
`Delete`/`Backspace` khi đang chọn nội dung trong ô chỉ xóa **nội dung ô**. Đây là thao tác nhập liệu thông thường, không xóa cấu trúc bảng. Muốn xóa cấu trúc, chọn ô/hàng/cột/bảng rồi dùng đúng lệnh **Xóa hàng**, **Xóa cột** hoặc **Xóa bảng** trong tab **Bảng** hay menu chuột phải.
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
## API tích hợp
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import type { TablePath, TableSelection } from "kindy-editor";
|
|
33
|
+
|
|
34
|
+
const path: TablePath = {
|
|
35
|
+
story: { kind: "body" },
|
|
36
|
+
ancestors: [],
|
|
37
|
+
tableId: "table-42",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const selection: TableSelection = {
|
|
41
|
+
kind: "row",
|
|
42
|
+
table: path,
|
|
43
|
+
from: 1,
|
|
44
|
+
to: 2,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await editor.setTableSelection(selection);
|
|
48
|
+
|
|
49
|
+
if (editor.canExecuteTableAction("deleteRow")) {
|
|
50
|
+
await editor.executeTableAction("deleteRow");
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Các action công khai:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
type TableAction =
|
|
58
|
+
| "insertRowAbove" | "insertRowBelow"
|
|
59
|
+
| "insertColumnLeft" | "insertColumnRight"
|
|
60
|
+
| "deleteRow" | "deleteColumn" | "deleteTable"
|
|
61
|
+
| "mergeCells" | "unmergeCell";
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`getTableSelection()` trả selection semantic hiện tại. `setTableSelection(null)` xóa selection bảng.
|
|
65
|
+
|
|
66
|
+
## Invariant và dữ liệu lỗi
|
|
67
|
+
|
|
68
|
+
`validateTableGrid()` phát hiện các lỗi mà renderer tolerant trước đây có thể che khuất:
|
|
69
|
+
|
|
70
|
+
- `rowSpan`/`colSpan` không hợp lệ;
|
|
71
|
+
- span vượt biên;
|
|
72
|
+
- hai owner cell chồng cùng grid slot;
|
|
73
|
+
- grid có ô trống không owner;
|
|
74
|
+
- cell ID trùng;
|
|
75
|
+
- số `colFractions` không khớp số cột.
|
|
76
|
+
|
|
77
|
+
Các bảng DOCX cũ chỉ bị lệch metadata cột (`colFractions`) hoặc thiếu grid slot do importer trước đây sẽ được sửa an toàn ngay trước structural mutation. Cơ chế repair chỉ:
|
|
78
|
+
|
|
79
|
+
- giữ nguyên cell, ID, content, span, style và row properties hiện có;
|
|
80
|
+
- chuẩn hóa độ rộng cột;
|
|
81
|
+
- bổ sung cell rỗng tại grid slot thực sự không có owner;
|
|
82
|
+
- validate lại toàn bộ grid trước khi commit transaction.
|
|
83
|
+
|
|
84
|
+
Các lỗi có nguy cơ mất dữ liệu như span vượt biên, owner cell chồng nhau, ID trùng hoặc span không hợp lệ vẫn bị từ chối. Khi đó nút structural action bị disable thay vì tự sửa âm thầm.
|
|
85
|
+
|
|
86
|
+
## Giới hạn hiện tại
|
|
87
|
+
|
|
88
|
+
`TablePath` đã resolve được nested table. Tuy nhiên shared operation log hiện vẫn định địa chỉ structural op top-level bằng `tableId`, 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.
|
|
89
|
+
|
|
90
|
+
## Kiểm thử
|
|
91
|
+
|
|
92
|
+
Regression hiện bao phủ:
|
|
93
|
+
|
|
94
|
+
- insert row cắt ngang `rowSpan`;
|
|
95
|
+
- xóa owner row nhưng giữ content/ID ở hàng còn sống;
|
|
96
|
+
- xóa cột cắt qua `colSpan`;
|
|
97
|
+
- merge/unmerge 2D;
|
|
98
|
+
- semantic row selection và nested `TablePath`;
|
|
99
|
+
- hit-test dải chọn table/row/column;
|
|
100
|
+
- import bảng từ DOCX thật rồi thêm/xóa hàng, cột và cả bảng;
|
|
101
|
+
- repair bảng legacy bị lệch metadata/ragged row mà không mất content;
|
|
102
|
+
- 250 structural mutations hỗn hợp có seed cố định, validate invariant sau từng bước.
|