@docen/editor

中文文档
0.7.0发布于 2026/09/07MITTypeScriptES Module近 30 天下载 2582

Assembly layer for docen editors — bundles a Fluent UI host (ribbon, panes, status bar) with the @docen/docx engine into turnkey web components, with Office.js-style add-ins and Office-style pagination

原始使用文档
文档目录8 节
  1. Features
  2. Installation
  3. Quick Start
  4. API
  5. Web component: <docen-document>
  6. Internationalization
  7. UI Bootstrap
  8. License

@docen/editor

npm version npm downloads npm license

Assembly layer for docen editors — bundles a Fluent UI host with the @docen/docx Tiptap engine into turnkey web components like <docen-document>, and owns the LeaferJS canvas stage that renders the paginated pages.

Features

  • 🧩 Turnkey <docen-document> — One custom element bundles the Fluent UI host (title bar, ribbon, document area, status bar, panes, find/replace) with the @docen/docx engine
  • 🎨 Canvas rendering — Pages render on a LeaferJS canvas (no contenteditable, no DOM text); a viewless Tiptap model drives editing through a textarea bridge
  • 📄 Office-style pagination — Fixed-height pages with Word's stacking rules (docGrid pitch, table band split, widow/orphan) from @docen/layout; layout re-runs on every edit
  • 🖌️ Fluent UI surfaces — Ribbon (buttons, split/toggle buttons, combobox, galleries, color picker), workspace, task/navigation/format panes, context menu
  • 🌐 i18n — Built-in Chinese (zh-CN) and English (en); add more via registerTranslation / localizationInfo. Switch live from the status bar (cycles every registered locale) or the Options dialog
  • 🌓 Light/dark theme — Fluent design tokens drive the chrome; switch via the theme attribute
  • 🔄 DOCX round-trip — Open/save .docx through the underlying @docen/docx engine
  • 🔌 Add-ins — Plug in ribbon tabs, task panes, and commands without touching host internals

Installation

# Install with pnpm
$ pnpm add @docen/editor

# Install with npm
$ npm install @docen/editor

Quick Start

<docen-document> is a self-contained custom element — register the components, apply a theme, and drop it in.

<docen-document id="doc" user="Demo Macro" filename="Welcome.docx"></docen-document>

<script type="module">
  import { registerComponents, applyTheme } from "@docen/editor";

  registerComponents(); // register all custom elements
  applyTheme("light"); // "light" | "dark"
</script>

Open and save DOCX imperatively:

const doc = document.querySelector<DocenDocument>("#doc")!;

await doc.openDOCX(file); // File | ArrayBuffer | Uint8Array
const output = await doc.saveDOCX(); // → Uint8Array

API

Web component: <docen-document>

A turnkey WYSIWYG document editor. The title bar (brand, auto-save, save/undo/redo, filename menu), ribbon, document area, status bar (page/word count, language indicator, zoom), and panes are all built in.

Attributes

Configuration attributes split by reactivity:

  • Reactive — change at runtime and the component re-renders: editable, filename, user, avatar, section-properties, styles, addins, theme.
  • Once — read only on connect (initial value); runtime control goes through methods: content, navigation-pane, properties-pane, zoom, show-marks.

The chrome (title bar, ribbon, status bar, panes) is always shown — extend it via add-ins rather than toggling attributes.

AttributeDefaultDescription
user—Display name shown in the header
avatar—Avatar image URL (omitted → initial-letter avatar)
filename"Document"Document name shown in the header and save dialog default
content—Initial document as Tiptap JSON (once on connect)
editabletruefalse makes the surface read-only (reactive)
section-properties—JSON section page setup (size, margins, orientation); reactive
styles—JSON named styles; reactive
addins—JSON array of external add-ins (ribbon/task-pane data); see Add-ins
themelight"light" | "dark"; drives the Fluent theme
navigation-pane—true opens the navigation (left) pane on connect (once)
properties-pane—true opens the properties (right) pane on connect (once)
zoom100Initial zoom percent (once); runtime via setZoom
show-marksfalsetrue shows page/section-break markers (once); runtime setShowMarks
lang—BCP-47 UI locale ("zh-CN" / "en" / …); per-instance, reactive

Unwired ribbon commands (skeleton buttons) render visually but are greyed out (disabled) — the ribbon keeps its full Office shape without dead clicks.

Methods

class DocenDocument extends HTMLElement {
  // Open — single entry point auto-detects docx/md from the extension.
  open(file: File): Promise<void>;
  // Format-specific loaders (use when the format is known up front, e.g. a
  // server-fetched docx buffer with no filename).
  openDOCX(input: File | ArrayBuffer | Uint8Array): Promise<void>;
  openMarkdown(input: File | string): Promise<void>;
  saveDOCX(): Promise<Uint8Array>;
  saveMarkdown(): string;

  // Runtime model — flat Tiptap JSON (doc > block+). Pages are a rendering
  // projection (@docen/layout paginates per edit), never stored in the model.
  // For Tiptap's own getText / setContent / chain, use getEditor().
  getJSON(): JSONContent;
  setJSON(json: JSONContent): void;

  // The underlying @docen/docx Tiptap Editor — the full Tiptap API surface.
  getEditor(): Editor | undefined;
  repaginate(): void;

  // Task-pane visibility (Office.addin.showAsTaskpane / hide equivalent).
  // `id` is "navigation" | "properties"; flips fire docen:taskpane-visibility-change.
  showTaskpane(id: TaskPaneId): void;
  hideTaskpane(id: TaskPaneId): void;
  getTaskpaneState(id: TaskPaneId): boolean;

  // Zoom (Office.Document.zoom.set equivalent; clamped 10–500).
  setZoom(pct: number): void;
  getZoom(): number;

  // Editing/formatting marks (page + section-break markers).
  setShowMarks(on: boolean): void;
  getShowMarks(): boolean;

  // Add-in registry — register/unregister ribbon + command contributions.
  addAddin(addin: DocenAddin): void;
  removeAddin(id: string): void;

  // Office.context.displayLanguage equivalent — read-only current UI locale.
  readonly displayLanguage: string;
}

Events

All events bubble and compose out of the shadow DOM — listen on the host element. docen:save / :save-as / :open / :print are cancelable: call preventDefault() to take over the action (otherwise the built-in behavior runs). docen:save-as carries { format } ("docx" | "markdown") — which Save-As variant the user picked. (docen:open is format-agnostic: the host auto-detects docx/md from the chosen file's extension, so it carries no detail.)

EventWhenDetail
docen:readyEditor mounted and ready—
docen:changeDocument content changed (autosave driver){ dirty }
docen:saveSave button — preventDefault() to take over—
docen:save-asSave As menu — preventDefault() to take over{ format }
docen:openOpen menu — preventDefault() to take over—
docen:newNew menu — host-only (no built-in action)—
docen:printPrint menu — preventDefault() to take over—
docen:taskpane-visibility-changeA task pane opened/closed (method or pane ✕){ id, visibilityMode }
docen:zoom-changeZoom changed (button / slider / setZoom){ zoom }
docen:marks-changeFormatting marks toggled{ showMarks }
docen:lang-changeLocale changed (status-bar cycle / Options OK){ lang }

Slots

The properties (right) pane body is slot-driven. The default fallback is the built-in <docen-format-pane> (empty state); slot a custom element to take over the pane — e.g. show image, table, or paragraph properties depending on the current selection.

SlotDefaultDescription
properties<docen-format-pane>Right pane body. Slot a component to own the properties UI.
<docen-document>
  <image-properties slot="properties"></image-properties>
</docen-document>

Selection-aware switching is the consumer's responsibility — the Office.js model leaves task-pane content + navigation to the add-in. Track the selection (e.g. on docen:change) and swap the slotted component (v-if / conditional render). The built-in <docen-format-pane groups='[{title,fields:[…]}]'> is a declarative radio / number / color renderer you can reuse as the slotted content when declarative fields are enough.

Configuration

The component works out-of-box. Collaborative actions (save, open, print) hand off to the host via cancelable events; the UI is extended via add-ins.

<!-- Read-only document -->
<docen-document editable="false"></docen-document>
const doc = document.querySelector<DocenDocument>("#doc")!;

// Take over save (skip the built-in picker → route to your storage)
doc.addEventListener("docen:save", (event) => {
  event.preventDefault();
  saveToStorage(doc.getJSON());
});

// Autosave on change
doc.addEventListener("docen:change", () => scheduleAutosave());

Add-ins

Plug in ribbon tabs and commands without touching host internals — declaratively (JSON attribute, data-only — functions can't cross the attribute boundary) or imperatively (full add-in object via addAddin). Task-pane contributions are reserved for a follow-up (the navigation/format panes are built-in today), so only ribbon tabs flow through the addins attribute right now.

<!-- Declarative: a "Citations" ribbon tab -->
<docen-document
  addins='[{"id":"citations","ribbon":[{"tab":"citations","label":"Citations","groups":[{"id":"tools","label":"Tools","controls":[{"type":"button","id":"cite","label":"Cite","event":"bold"}]}]}]}]'
></docen-document>
// Imperative: full add-in (commands / pane-render allowed here)
doc.addAddin({
  id: "citations",
  name: "Citations",
  ribbon: [
    {
      tab: "citations",
      label: "Citations",
      groups: [
        {
          id: "tools",
          label: "Tools",
          controls: [/* … */],
        },
      ],
    },
  ],
});

Ribbon control event names route to the engine's native Tiptap commands (editor.chain().focus().<event>(value).run()), so a built-in name like bold works directly. Override a command by contributing a Tiptap extension whose addCommands redefines the same name.

Internationalization

The host ships with English (en, default) and Chinese (zh-CN). Every label runs through a single t(key) lookup, and the locale resolves per-instance from <docen-document lang> (forwarded to the internal workspace) — set the attribute and the ribbon, header, status bar, and Options dialog re-localize live, with no dependency on <html lang>. The status-bar language pill cycles every registered locale; the Options dialog renders a <select> of the same list.

Add a locale by registering its translation table — the Options dropdown and the status-bar cycle pick it up with no further wiring:

import { registerTranslation } from "@docen/editor";

registerTranslation({
  languageTag: "fr",
  $name: "Français",
  translations: { "ribbon.tab.home": "Accueil" /* … */ },
});

Re-registering a tag merges (later wins on key conflicts), so an add-in can extend a built-in locale with its own keys without clobbering the base. The Office.js manifest shape is supported too — pass localizationInfo on an add-in and the host registers it on addAddin:

doc.addAddin({
  id: "about",
  localizationInfo: {
    defaultLanguageTag: "en",
    additionalLanguages: [{ languageTag: "zh-CN", translations: { "about.tab": "关于" } }],
  },
  // …ribbon, commands…
});

availableLanguages() lists every registered tag (for custom pickers); the read-only displayLanguage getter mirrors Office.context.displayLanguage.

UI Bootstrap

import { registerComponents, applyTheme } from "@docen/editor";

registerComponents(); // registers <docen-document>
applyTheme("light"); // "light" | "dark"

License

版本与发布记录

@docen/editor 共发布 17 个版本,首次发布于 2026/06/23;过去 12 个月发布了 17 个版本。

版本发布日期距今
0.7.0latest2026/09/072 个月前
0.6.22026/09/022 个月前
0.6.12026/08/312 个月前
0.6.02026/08/312 个月前
0.5.52026/08/063 个月前
0.5.42026/08/063 个月前

关于 @docen/editor 的常见问题

@docen/editor 是什么?

Assembly layer for docen editors — bundles a Fluent UI host (ribbon, panes, status bar) with the @docen/docx engine into turnkey web components, with Office.js-style add-ins and Office-style pagination

如何安装 @docen/editor?

在项目目录运行 npm install @docen/editor,或使用 pnpm add / yarn add / bun add。

@docen/editor 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@docen/editor 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@docen/editor 可以商用吗?

@docen/editor 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@docen/editor 的下载量有多少?

根据 npm 官方下载统计,@docen/editor 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,582 次,日均约 86 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@docen/editor 还在维护吗?

最新版本 0.7.0 发布于 2026/09/07(2 个月前);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/06/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。