Host config
Document IO is Persistence. This page covers editor construct control through @codemerge/integrate: plugin packs, media upload endpoints, locale, toolbar, and per-plugin server settings.
What you can control
| Area | HostOptions fields | Notes |
|---|---|---|
| Document value | value, format | html / markdown / text |
| Chrome | chrome | bar | page |
| i18n | locale, fallbackLocale, messages | forwarded to Editor |
| Theme | colorScheme | host | system |
| Toolbar menus | toolbar | { menus: [] } → flat bar |
| History | history | maxDepth, mergeWindowMs |
| Diagnostics | diagnostics | onMeasure timing hooks |
| Plugin pack | pack | core (default) | default | none |
| Image upload | image | UploadConfig → ImagePlugin |
| File upload | fileUpload | only with pack: 'default' (or your own plugins) |
| Explicit plugins | plugins | wins over pack / image / fileUpload |
| Extra plugins | pluginsAppend | e.g. CollaborationPlugin, SpellChecker |
Helpers: createEditorHost, createHostPlugins from @codemerge/integrate.
Same fields work on:
createEditorHost(el, opts)- React / Vue / … adapter props (
host-optionson Vue) <ocm-editor>.configure(opts)/.optionsbindPersistence(plainEl, { load, save, pack, image, … })when integrate mounts the host
Upload (image / files)
Packs use the same UploadConfig as the plugin docs (endpoints, headers, useEmulation, …).
If you set endpoints.upload, integrate defaults useEmulation: false (demo in-memory store stays off). Override with useEmulation: true only for offline demos.
import { createEditorHost } from '@codemerge/integrate';
createEditorHost(el, {
pack: 'default', // includes FileUploadPlugin; 'core' has ImagePlugin only
image: {
endpoints: { upload: '/api/media', list: '/api/media', delete: '/api/media' },
headers: () => ({
'X-CSRF-TOKEN':
document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '',
}),
maxFileSize: 10 * 1024 * 1024,
},
fileUpload: {
endpoints: {
upload: '/api/files',
download: '/api/files',
list: '/api/files',
delete: '/api/files',
},
},
});
headers as a function is resolved once at mount (handy for CSRF).
Without integrate sugar, same thing:
import { Editor, createDefaultPlugins } from 'on-codemerge';
new Editor(el, {
plugins: createDefaultPlugins({
image: { endpoints: { upload: '/api/media' }, useEmulation: false },
fileUpload: { endpoints: { upload: '/api/files' }, useEmulation: false },
}),
});
Per-plugin server settings
Upload is the common case, so it has first-class image / fileUpload on packs. Everything else (collab WS, spell dictionaries, custom AI endpoints, …) goes through plugin constructors:
| Need | How |
|---|---|
| Media / file URLs + CSRF | image / fileUpload on pack |
| Collaboration | pluginsAppend: [CollaborationPlugin({ serverUrl, getToken })] |
| Spellcheck dictionaries | pluginsAppend: [SpellCheckerPlugin({ dictionaries: … })] |
| Replace / reorder pack | plugins: […] (full list; pack ignored) |
| Pack + one extra | pack: 'default' + pluginsAppend |
import { CollaborationPlugin, SpellCheckerPlugin } from 'on-codemerge/plugins';
import { createEditorHost } from '@codemerge/integrate';
createEditorHost(el, {
pack: 'default',
image: { endpoints: { upload: '/api/media' } },
pluginsAppend: [
CollaborationPlugin({
serverUrl: 'wss://example.com/collab',
docId: 'page-42',
getToken: () => fetchToken(),
}),
SpellCheckerPlugin({/* dictionary file URLs — see SpellChecker plugin docs */}),
],
});
Full control without packs:
import { createDefaultPlugins, CollaborationPlugin } from 'on-codemerge/plugins';
createEditorHost(el, {
plugins: [
...createDefaultPlugins({
image: { endpoints: { upload: '/api/media' } },
}),
CollaborationPlugin({ serverUrl: 'wss://…', getToken }),
],
});
React
<CodeMergeEditor
pack="default"
image={{ endpoints: { upload: '/api/media' } }}
fileUpload={{ endpoints: { upload: '/api/files' } }}
locale="ru"
value={html}
onChange={setHtml}
/>
Vue 3
Pass a bag via host-options:
<CodeMergeEditor
:value="html"
:host-options="{
pack: 'default',
image: { endpoints: { upload: '/api/media' } },
locale: 'ru',
}"
@change="onChange"
/>
Web Component
Set options before connect, or call configure():
import '@codemerge/integrate/element';
const el = document.createElement('ocm-editor');
el.configure({
pack: 'default',
image: {
endpoints: { upload: '/api/media' },
headers: () => ({ Authorization: `Bearer ${token}` }),
},
locale: 'en',
});
document.body.append(el); // or set `.options` before first connect
If the element is already in the DOM, register ready before configure() (ready can fire synchronously on remount).
Attrs still work for simple fields: value, format, chrome, locale, pack.
Persistence + upload together
import '@codemerge/integrate/element';
import { bindPersistence, restPersistence } from '@codemerge/integrate/protocol';
const el = document.querySelector('ocm-editor');
function wire() {
bindPersistence(
el,
restPersistence({
url: `/pages/${id}`,
parse: (d) => d.content,
serialize: (value) => ({ content: value }),
})
);
}
// Listen before configure — `ready` fires synchronously on remount.
el.addEventListener('ready', wire, { once: true });
el.configure({
pack: 'default',
image: { endpoints: { upload: '/api/media' } },
});
When bindPersistence mounts a plain element, pass the same host fields on the persistence options (pack, image, …).
Related
Persistence
Mount alone is not an integration. Wire your backend with bindPersistence (callbacks) or restPersistence (fetch sugar). No fixed /api/doc path and no required JSON shape.
Server + Vite
Pattern for Laravel, Slim, Express, Django, Go, … — npm peers, Vite (or any bundler), Web Component, your load/save + upload URLs.