Skip to content

Migration guide: v1 → v2 ​

on-codemerge 2.x rewrites the editor around a virtual JSON document (on-codemerge/kernel), an SDK surface (on-codemerge/sdk), and plugins that never own DOM.

See this migration guide for the v1 → v2 delta.

Quick map ​

v1v2
HTMLEditorEditor
new ToolbarPlugin() classToolbarPlugin() factory (mark buttons only)
Plugin owns toolbar DOMCore ToolbarPanel via ctx.toolbar.add / defineMenu
PopupManager / createElement in pluginsctx.popup / ctx.menu / ViewSpec + portals
HTML as source of truthJSON: getJSON / setJSON (HTML is a boundary)
plugins/FooPlugin/style.css CDNon-codemerge/index.css + public.css (no per-plugin CSS export)
JestVitest (+ Playwright / untestutils for e2e)

Product surfaces (v2) ​

Eight Editor entries share kernel + SDK — pick by document shape in Editors:

PackageRole
on-codemergeWYSIWYG prose
on-codemerge/jsonJSON Tree + Raw
on-codemerge/markdownDual-pane Markdown
on-codemerge/codeSource buffer
on-codemerge/formsForm studio
on-codemerge/chartsChart studio
on-codemerge/calendarCalendar studio
on-codemerge/tablesGrid JSON studio

Scoped packages also published: @codemerge/kernel, @codemerge/view, @codemerge/mermaid (Mermaid subset — Mermaid), @codemerge/sdk, @codemerge/hunspell, @codemerge/collaboration-server.

Install ​

bash
pnpm add on-codemerge
# workspace packages are re-exported from the main package
ts
import 'on-codemerge/index.css';
import 'on-codemerge/public.css';
import { Editor, createDefaultPlugins, insertText } from 'on-codemerge';
// Individual plugins: import { TablePlugin } from 'on-codemerge/plugins'

const editor = new Editor(host, { plugins: createDefaultPlugins() });
editor.run(insertText('Hello'));

Use createCorePlugins() for a lean bar, or pass an explicit factory list.

Plugins ​

ts
import { definePlugin, core, insertAtomAfter } from 'on-codemerge/sdk';
// or from 'on-codemerge'

export function MyPlugin() {
  return definePlugin({
    name: 'my-plugin',
    commands: {
      insertThing: insertAtomAfter('myAtom', {}),
    },
    setup(ctx) {
      // Top-level bar
      ctx.toolbar.add({
        id: 'my-bar',
        label: 'My',
        group: 'format',
        order: 20,
        onClick: () => ctx.editor.command('insertThing'),
      });

      // Or into a named overflow menu (Insert / Review / Tools)
      ctx.toolbar.add({
        id: 'my-insert',
        label: 'Thing',
        menu: 'insert',
        order: 80,
        onClick: () => ctx.editor.command('insertThing'),
      });
    },
  });
}

Rules:

  1. Import UI/helpers from on-codemerge/sdk (or on-codemerge) — not on-codemerge/kernel from a plugin.
  2. No document.createElement / .innerHTML = outside SDK foreign(...) / widgets.
  3. Overlays go through portals (popup / menu / notify) — see Plugin guide.

Toolbar chrome vs ToolbarPlugin ​

  • Chrome (the sticky bar) is always created by Editor → SDK ToolbarPanel.
  • ToolbarPlugin() only registers Bold / Italic / Underline / Strike.
  • Overflow menus come from Editor toolbar.menus (omit → Insert / Review / Tools; { menus: [] } → flat bar). Plugins place items with menu: '…' / PluginToolbarOpts; missing menu id → bar button.
  • Separators come from toolbar group boundaries (no ToolbarDividerPlugin).
  • There is no ToolbarMenusPlugin / flattenOverflowMenus — removed in favor of declarative toolbar.menus.

Persistence ​

ts
// Prefer JSON
const json = editor.getJSON();
editor.setJSON(json);

// HTML when integrating with legacy CMS
const html = editor.getHTML();
editor.setHTML(html);

Removed / renamed APIs ​

  • editor.showToolbar / hideToolbar / addToolbarTool — gone; use editor.toolbar.add.
  • editor.remove(plugin) / getPlugins() — not on Editor; dispose via plugin scope / recreate editor.
  • createToolbarButton — deleted; use ctx.toolbar.add.
  • Per-plugin CSS package exports — use bundled index.css / public.css.

Testing ​

Unit: Vitest (pnpm test:unit). Browser: untestutils + Playwright (pnpm test:e2e).

Further reading ​

Released under the MIT License.