@domternal/core

中文文档
1.8.1发布于 2026/11/08MITTypeScriptESM + CJS近 30 天下载 4913

MIT-licensed, framework-agnostic headless rich text editor engine built on ProseMirror

原始使用文档
文档目录14 节
  1. Install
  2. Quick start
  3. Choose your extensions
  4. Editable document notes
  5. Readable literal colors
  6. Read-only previews
  7. Optional print preparation
  8. Full block backgrounds
  9. Optional list labels
  10. Content and validation
  11. Server-side rendering
  12. Localization
  13. Guides
  14. License

@domternal/core

Version MIT License

The headless editor engine for Domternal, built on ProseMirror. Use it directly in JavaScript or TypeScript, or through the Angular, React, Vue and Vanilla wrappers. It provides the Editor class, composable extensions, chainable commands, document serialization and server-side helpers.

Documentation · Getting started · Examples

Install

The package declares Node.js 22 or later for tooling.

pnpm add @domternal/core

Core has no framework dependency and includes @domternal/pm. Use version 1.8.1 for all installed Domternal Free packages. Wrappers and extensions require Core >=1.8.0 <2.0.0; extensions also require PM in that range. Core 1.8.1 requires PM >=1.8.1 <2.0.0 at runtime. Import ProseMirror primitives from @domternal/pm/* to keep one shared copy.

For ready-made editor styling, also install @domternal/theme and import it from your application entry point, with class="dm-editor" on the mount or an ancestor. Core has no bundled editor theme, toolbar or framework UI.

Quick start

Add a mount element to your page:

<div id="editor"></div>

Then create the editor in your browser entry point:

import { Editor, StarterKit } from '@domternal/core';

const editor = new Editor({
  element: document.getElementById('editor')!,
  extensions: [StarterKit],
  content: '<p>Hello <strong>world</strong>!</p>',
});

editor.on('update', () => {
  console.log(editor.getJSON());
});

StarterKit includes paragraphs, headings, lists, tasks, links, common text marks, history and editing keymaps. It does not include tables, images, Office paste cleanup or other optional extension packages.

Read or change the document through the same instance:

editor.chain().focus().toggleBold().run();
editor.commands.insertContent('<p>Another paragraph</p>');

const json = editor.getJSON();
const html = editor.getHTML();
const text = editor.getText();

Call editor.destroy() when removing the editor from your application. Framework wrappers manage the lifetime of the instances they create.

Choose your extensions

Configure or disable individual StarterKit features:

StarterKit.configure({
  heading: { levels: [1, 2, 3] },
  link: { openOnClick: false },
  codeBlock: false,
});

Pass the configured StarterKit in place of StarterKit in extensions. Headings support levels 1 to 4 by default. listIndent is off by default because enabling it also captures Tab in a paragraph immediately after a list.

For a smaller schema, replace StarterKit with the individual exports you need, such as Document, Paragraph, Text, Bold, BaseKeymap and History. Register optional packages in that same extension array:

Only imported extensions enter your JavaScript bundle. preset: 'notion' selects the Notion presentation and behavior; it does not install optional extensions. See the configuration guide.

Editable document notes

Free 1.6.0 adds opt-in DocumentNotes, which is not part of StarterKit. It installs an inline documentNoteReference atom and an editable documentNote block. Both live in ordinary editor JSON and the shared ProseMirror document, so collaboration and history operate on the same content without a separate application note store. Enable DocumentNotes on every collaborative writer before storing these nodes.

import { DocumentNotes, Editor, StarterKit, getDocumentNotes } from '@domternal/core';

const editor = new Editor({ extensions: [StarterKit, DocumentNotes] });
editor.commands.insertDocumentNote({
  noteId: 'source-note',
  kind: 'footnote',
  content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Editable note body' }] }],
});
editor.commands.insertDocumentNoteReference({ noteId: 'source-note', kind: 'footnote' });
editor.commands.focusDocumentNote({ noteId: 'source-note', kind: 'footnote' });
const notes = getDocumentNotes(editor.state.doc);

The same commands accept kind: 'endnote'. insertDocumentNote can generate an identity when noteId is omitted and accepts an optional literal label for a custom mark. Bodies and references share noteId and kind; a repeated reference points to the existing body. Insertion is one undoable transaction. Clicking a reference, pressing Enter or Space on it, or calling focusDocumentNote selects the corresponding body. The body heading links back to its first reference.

Automatic labels are derived from reference order independently for each kind; custom labels do not advance the counter. Rendering never rewrites JSON or Yjs state. Deleting a reference keeps its body visible for editing. Missing bodies and duplicate identities display an unresolved status instead of guessing or discarding content. Copying a body with its references remaps colliding identities; a reference copied alone retains its target identity.

Identities are limited to 256 UTF-16 units and custom labels to 64 nonblank units, without control characters or unpaired surrogates. The derived navigation index is bounded to 512 bodies, 512 references, 100,000 nodes, 64 levels and two million text units. Exceeding the index bound keeps the content and disables unresolved navigation. getDocumentNoteTypes(schema) recognizes the actual first-party contract, including renamed nodes that retain its attributes. It does not adopt unrelated nodes just because they use the same names.

Free 1.7.0 exposes a helper for application-owned, read-only DOM previews built with the schema serializer. annotateDocumentNotes(root, getDocumentNotes(doc), editor.i18n) derives the same labels without changing JSON. A full-document snapshot preserves numbering when only part of the document is rendered. See static note presentation.

Pro Import and Pro Export 1.6.0 integrate with this optional model for supported DOCX notes. Free provides the editable model; those DOCX/PDF adapters belong to Pro. This model is not a paginated footnote editor.

Readable literal colors

Core and theme 1.4.0 add an optional display policy for literal text colors. Enable it explicitly by adding the configured extension to your editor:

import { StarterKit, TextStyle, TextColor } from '@domternal/core';
import '@domternal/theme';

const extensions = [
  StarterKit,
  TextStyle,
  TextColor.configure({ contrast: 'auto' }),
];

The default, original, keeps authored colors. auto measures the live editor's background and adjusts an opaque literal color only when its contrast is below 4.5:1. It mixes the color toward black or white rather than replacing every color with the theme's text color. Readable colors and named color tokens stay unchanged. This also works for saved documents in a read-only editor; it is not specific to Word import.

The adjustment affects screen presentation only. JSON, HTML, clipboard HTML, export, print and shared document data retain the original color. Theme changes recalculate the presentation without editing the document. Transparent backgrounds and ancestor opacity, including checked task labels, are composited when the complete solid background is known. The policy keeps opacity unchanged; if no text color can reach 4.5:1 through that opacity, the authored color stays unchanged. Translucent text, unresolved or unsupported color spaces, image or gradient backgrounds, painted pseudo-elements and other compositing effects are left unchanged. A custom textStyle mark view of higher priority also takes precedence. This is a bounded readability aid, not an accessibility guarantee for arbitrary application CSS.

Core produces view attributes and the matching theme supplies their screen styles. Applications with their own styles can use the same narrowly scoped rule:

@media screen {
  .dm-editor .ProseMirror [data-dm-text-contrast='adjusted'] {
    color: var(--dm-readable-color) !important;
  }
}

Read-only previews

Free 1.8.0 exposes TextColorStorage.preview(root, marks) for application-owned, schema-rendered read-only previews. Obtain the storage from the configured TextColor extension and pass the preview's .ProseMirror root with its rendered literal-color spans and their current inline style.color values. It returns null when contrast is original; with auto it returns refresh() and destroy() methods. Call refresh() after attaching the preview and destroy() when removing it. The preview needs the same theme/readability CSS as the editor. The capability adds only temporary presentation attributes; it does not install editing plugins or rewrite document content.

Read the storage from editor.storage['textColor'] as TextColorStorage after registering TextColor. Use TextColorPreviewMark for each supplied span/color pair and TextColorPreview for the returned lifecycle handle. Render spans from your trusted editor schema; the capability is not an HTML sanitizer. Attach the preview under the matching theme and .dm-editor screen styles before refreshing. Keep the supplied spans and literal colors unchanged for the handle's lifetime; destroy and recreate it when replacing the rendered content.

Optional print preparation

Free 1.6.0 adds Print.configure({ prepareResources: true }) to wait for live images and fonts before command-driven printing. The default stays immediate. A bounded timeout, cancelPrintPreparation() and the optional storage.print.preparation promise let an application report or cancel waiting. The matching theme and standard Table node view also support eligible multirow print headers without changing document JSON or selection. Browser pagination and native-dialog limitations still apply. See the Print guide for configuration, resource limits and supported header structures.

Full block backgrounds

Since 1.5.0, BlockColor keeps explicit hex, rgb() and rgba() fills in its existing bgColor node attribute. A paragraph or heading with an inline background-color therefore keeps a fill across the block, including an empty paragraph. Named CSS colors in HTML become literal hex values; transparent becomes a transparent rgba() fill. Nested inline highlights remain independent. Alpha values are retained.

Palette tokens such as red and gray retain their theme meaning and take precedence over an inline background when data-bg-color is present. The palette commands still accept only their configured choices. Literal fills serialize as background-color; tokens continue to serialize as data-bg-color.

View-only tone attributes let the matching theme keep default text readable on known light or dark fills without changing the document. An unresolved translucent fill does not receive a guessed tone. Explicit text colors retain the selected TextColor contrast policy described above.

Optional list labels

Available since 1.5.0, ListLabels can be added beside StarterKit to retain a bounded custom bullet or an ordered counter with a text prefix and suffix. It is not included in StarterKit. Existing schemas and stored JSON stay unchanged when this extension is omitted.

import { ListLabels, StarterKit } from '@domternal/core';

const extensions = [StarterKit, ListLabels];
// On an orderedList node:
const attrs = {
  start: 4,
  listLabel: { kind: 'counter', format: 'decimal', prefix: 'Section ', suffix: ') ' },
};

The browser updates markers as items are edited. Markers remain native list presentation, with no duplicate marker text in clipboard text or document text. The extension installs scoped companion CSS while the editor is alive. Set nonce for the application's CSP policy, or injectCSS: false when the host provides that CSS. getListLabelStyleSheet(json) supplies the companion stylesheet for rendered HTML. HTML without it retains ordinary list fallbacks. The pure @domternal/core/list-labels entry exposes the validator, formatter, stylesheet collector and label types for workers and server code without loading the editor runtime.

For compatibility, ListLabel, readListLabel, formatListLabel and getNodeListLabel retain the original counter and text contract. The readers and formatter return null for compound and picture labels. Use ExtendedListLabel, readExtendedListLabel, parseExtendedListLabel, formatExtendedListLabel and getNodeExtendedListLabel when opting into the additional variants. All except the node reader are also available from the pure @domternal/core/list-labels entry. Existing exhaustive TypeScript switches do not need new cases.

In 1.6.0, compound labels can reference enclosing ordered items without storing a copied number such as 1.2 in the document. For example, an inner list can use { kind: 'compound', parts: [{ ancestor: 1, format: 'inherit' }, '.', { ancestor: 0, format: 'inherit' }, ') '], fallback: 'decimal' }. The marker updates after an ancestor's number or format changes, including undo and collaborative edits. A missing or unordered referenced ancestor uses the ordinary fallback. Serialized HTML is a snapshot; regenerate it after editing the source document. See the contract below for the bounded template rules.

Free 1.6.0 also supports { kind: 'image', src: pngDataUrl, text: '◆' } for a small local picture marker on a bullet list. The qualified profile accepts static 8-bit RGB/RGBA PNG up to 32×32 pixels and 4 KiB, with a required readable text fallback. It does not fetch remote marker assets or accept SVG. Companion CSS displays the picture; the existing marker remains available when that CSS is absent.

All collaborative writers must enable the same list-label schema. An older writer without the extension can remove the unknown attribute when it edits a list. See the list-label contract for supported records, diagnostics, serialization and rendering limits.

Whole HTML list items that begin with a nested list receive an empty paragraph label during parsing, so their outer numbering, checked state and later siblings stay together. This works on load, paste, drop and server-side HTML conversion without Paste Cleanup. Native partial copies keep their open slice boundaries; the original DOM and already valid list items are unchanged.

Content and validation

Use JSON for structured storage and HTML or plain text when your application needs those formats. Loading JSON normalizes unsupported attributes and refused links; onContentDiagnostic reports those repairs. Rendering an existing shared document can show a normalized value without rewriting its stored data.

Before migrating persisted or collaborative content, read the content normalization guide and release notes. URL and style checks apply to built-in content paths. Custom renderers, exporters and uploads still need application validation; custom IconSet SVG must be trusted application code. See the security guide.

onUpdate runs for accepted document changes, including changes appended by plugins. A transaction vetoed by a plugin does not run update callbacks. See the transaction contract when connecting autosave or external state.

Server-side rendering

generateHTML, generateJSON and generateText work without an editor instance. HTML serialization and parsing need a DOM. In Node.js, install the optional linkedom peer:

pnpm add linkedom

With no document option, generateHTML and generateJSON load the installed linkedom themselves: from an ES module on Node.js 22.3 or later, and from CommonJS on every supported version. Pass a document explicitly to choose the DOM implementation, or where the runtime cannot load it:

import { generateHTML, generateText, StarterKit } from '@domternal/core';
import { parseHTML } from 'linkedom';

const content = {
  type: 'doc',
  content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }],
};
const { document } = parseHTML('<!doctype html><html><body></body></html>');

const html = generateHTML(content, [StarterKit], { document });
const text = generateText(content, [StarterKit]);

generateJSON(html, extensions, { document }) parses HTML into JSON. generateText does not need a DOM. Browser calls use the browser document. See server-side utilities.

Free 1.6.0 adds createSchema(extensions) for a worker or headless conversion pipeline. The helper builds only the ProseMirror schema using the same extension configuration as the editor:

import { createSchema, StarterKit } from '@domternal/core';

const schema = createSchema([StarterKit.configure({ heading: { levels: [1, 2, 3] } })]);
const doc = schema.nodeFromJSON(savedContent);
doc.check();

This helper creates no editor view, plugins or editor lifecycle. Custom schema and storage factories must work without a browser or a live editor. Share one extension factory between the editor and worker, including custom nodes, marks and configured attributes. It does not normalize imported JSON; use the existing content normalization API when that is intended.

Localization

Custom translation catalogs use CompleteMessages<typeof definitions>. In Core 1.4.0, definitions without optionalTranslation: true remain required. A definition marked optionalTranslation: true may fall back to its English default until the application translates it, so additive messages need not break existing catalogs. Use Required<CompleteMessages<typeof definitions>> to require every current message in a maintained language pack. Runtime i18n.messages overrides remain partial. The six core.documentNotes.* messages added in 1.6.0 are optional, so existing complete Core catalogs continue to type-check and use English note labels until those translations are supplied.

Guides

License

MIT.

版本与发布记录

@domternal/core 共发布 48 个版本,首次发布于 2026/03/22;过去 12 个月发布了 48 个版本。

版本发布日期距今
1.8.1latest2026/11/085 天前
1.8.02026/10/2123 天前
1.7.02026/10/2024 天前
1.6.02026/10/1925 天前
1.5.12026/10/1628 天前
1.5.02026/10/1628 天前

关于 @domternal/core 的常见问题

@domternal/core 是什么?

MIT-licensed, framework-agnostic headless rich text editor engine built on ProseMirror

如何安装 @domternal/core?

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

@domternal/core 支持 TypeScript 吗?

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

@domternal/core 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@domternal/core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@domternal/core 可以商用吗?

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

@domternal/core 的下载量有多少?

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

@domternal/core 还在维护吗?

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