Skip to content

Documentation for SoEditor 1.3.0

React form example

SoEditor 1.3.0
BUILT FOR YOUR CMS

Make room for your content

An article, a product story, or your next announcement. Familiar tools for the work you do every day.

WYSIWYGHTML SourceCMS

Loads on demand · Content stays in this page

Install and run

The /cms components are released in 1.3.0. The existing useSoEditorWorkspace entry remains compatible.

sh
pnpm add @soeditor/editor@1.3.0 @soeditor/react@1.3.0 react react-dom

Select “Start editing” to run the published packages. Download complete example sources, extract, run pnpm install and pnpm dev, then open react.html.

Try it

  1. Edit the article and watch bound HTML update below it.
  2. Select “Replace HTML” to apply external state, then toggle readonly to stop editing.
  3. “Submit form” reads canonical textarea HTML. “Reset form” restores the initial value for the current mount.
  4. “HTML Source” loads Source on demand. “Recreate demo” mounts a new instance with the current value. Closing the demo removes the entire iframe.

Complete example source

ts
import { createElement as h, StrictMode, useRef, useState } from 'react';
import type { FormEvent } from 'react';
import { createRoot } from 'react-dom/client';
import { SoEditor } from '@soeditor/react/cms';
import type { ClassicEditor } from '@soeditor/editor/cms';
import { createEditor, initial, label, options } from './shared.js';

function ArticleForm() {
    const [html, setHtml] = useState(initial);
    const [readonly, setReadonly] = useState(false);
    const [version, setVersion] = useState(0);
    const [output, setOutput] = useState('');
    const [status, setStatus] = useState(label('正在加载', 'Loading'));
    const editor = useRef<ClassicEditor | undefined>(undefined);
    const button = (zh: string, en: string, onClick: () => void) =>
        h('button', { type: 'button', onClick }, label(zh, en));
    return h(
        'form',
        {
            onSubmit: (event: FormEvent<HTMLFormElement>) => {
                event.preventDefault();
                setOutput(
                    String(new FormData(event.currentTarget).get('body')),
                );
            },
        },
        h(
            'p',
            { className: 'notice' },
            label(
                'React 组件预览:内容仅保存在本页。',
                'React component preview: content stays in this page.',
            ),
        ),
        h(
            'div',
            { className: 'controls' },
            button('替换 HTML', 'Replace HTML', () =>
                setHtml('<h2>Updated article</h2><p>External value</p>'),
            ),
            button('切换只读', 'Toggle readonly', () => setReadonly(!readonly)),
            button('读取 HTML', 'Read HTML', () =>
                setOutput(editor.current?.getData() ?? ''),
            ),
            button('HTML Source', 'HTML Source', () => {
                void editor.current?.setWorkspaceView('source');
            }),
            button('重建示例', 'Recreate demo', () => {
                editor.current = undefined;
                setVersion(version + 1);
            }),
            h('button', { type: 'submit' }, label('提交表单', 'Submit form')),
            h('button', { type: 'reset' }, label('重置表单', 'Reset form')),
        ),
        h('p', { role: 'status' }, status),
        h(SoEditor, {
            key: version,
            name: 'body',
            value: html,
            onChange: setHtml,
            readonly,
            options,
            createEditor,
            onReady: (instance) => {
                editor.current = instance;
                setStatus(label('就绪', 'Ready'));
                document.body.dataset.ready = 'true';
            },
            onError: (error) => setStatus(String(error)),
        }),
        h('output', { 'data-bound-value': '' }, html),
        h('pre', null, output),
    );
}
const host = document.getElementById('app');
if (!host) throw new Error('Missing app host');
const root = createRoot(host);
root.render(h(StrictMode, null, h(ArticleForm)));
window.addEventListener('pagehide', () => root.unmount(), { once: true });
ts
import '@soeditor/editor/cms/styles.css';
import '../style.css';
import type { CreateClassicEditorOptions } from '@soeditor/editor/cms';

export const locale =
    new URLSearchParams(location.search).get('lang') === 'en' ? 'en' : 'zh-CN';
document.documentElement.lang = locale;
export const label = (zh: string, en: string) => (locale === 'en' ? en : zh);
export const initial = '<h2>CMS article</h2><p>Edit this content.</p>';
export const options: CreateClassicEditorOptions = {
    locale,
    ariaLabel: label('文章正文', 'Article HTML'),
    editingModes: ['wysiwyg', 'source'],
    minHeight: 240,
};
export const createEditor = async (
    host: HTMLElement,
    configuration: CreateClassicEditorOptions,
) => {
    const cms = await import('@soeditor/editor/cms/optional');
    return cms.createClassicEditor(host, configuration);
};
html
<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="robots" content="noindex, nofollow" />
        <title>SoEditor react demo</title>
    </head>
    <body>
        <div id="app"></div>
        <script type="module" src="./frameworks/react.ts"></script>
    </body>
</html>

Integration guide

HTML editing for website CMS · MIT