前回から引き続いて lexical について調べていきます。 今回は、エディタの初期テキストを index.html で指定する方法です。
次のように textarea の値として設定してある内容を lexical のエディタの初期テキストを指定することにします。
dist/index.html
<textarea id="mytextarea">Hello, World!</textarea>
lexical のテキストエディタ部分の初期値の設定は initalConfig の editorState の値として設定すればよいようです。
src/index.js
const initialConfig = {
namespace: 'MyEditor',
theme,
onError,
editorState: 'Hello, World!',
};
一度これで実行してみます。( npm run build して python3 でwebサーバを起動。 )
うまくいきません。 コンソールを見てみると、次のようなメッセージが。
"Hello, World!" is not valid JSON
editorState の値には文字列を渡すのではなく、期待されている valid JSON を渡す必要があるとのこと。 valid JSON を作り出すのが簡単ではないかもしれないので、 ここは用意されている markdown から適切な JSON に変換してくれる関数を使いましょう。
このページ lexical-markdown を参考に実装していきます。
次のようにすればよいようです。
<LexicalComposer initialConfig={{
editorState: () => $convertFromMarkdownString(markdown, TRANSFORMERS)
}}>
src/index.js を次のように修正しました。 (作動確認なので、直接 markdown 部分にテキストを入れている。)
const initialConfig = {
namespace: 'MyEditor',
theme,
onError,
editorState: () => $convertFromMarkdownString('Hello, World!', TRANSFORMERS),
};
これらの関数を使うために src/index.js の先頭にインポートも忘れずに。
import {
$convertFromMarkdownString,
$convertToMarkdownString,
TRANSFORMERS,
} from '@lexical/markdown';
これで実行して結果を確認します。
うまくいきました。
あとは、dist/index.html の該当部分のテキストを取り出して、ここにセットすればよい。
先に進む前に、 この段階でコードをリファクタリングします。
方針は、 src/index.js に全部詰め込んでいたのをやめてファイル src/index.js と src/Editor.js とにわけます。
まず、 現在 src/index.js に書いている内容のほとんどを src/Editor.js に移します。 そして、 src/index.js の最後に書いていた dist/index.html の root に Editor を適用する部分のコードを src/index.js に残します。
リファクタリング後の src/index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import Editor from "./Editor";
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<Editor />);
src/Editor.js
import React from 'react';
//import {createRoot} from 'react-dom/client';
... 中略
export default Editor;
/*
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<Editor />);
*/
src/Editor.js は、 src/index.js からまるまるコピーした状態から、 import {createRoot} from 'react-dom/client'; をコメントアウト、 そして、 root 部分に Editor を適用するコードは、src/index.js に移動したので、 src/Editor.js からはその部分のコードをコメントアウトしました。
この段階でのプロジェクトのファイル構成を確認しておきます。
.
├── babel.config.json
├── dist
│ ├── editor.js
│ └── index.html
├── node_modules/...
├── package.json
├── package-lock.json
├── src
│ ├── Editor.js
│ └── index.js
└── webpack.config.js
dist/index.html の textarea に書いた値を src/Editor.js の initialConfig に反映させるために次のようにします。
dist/index.html
<textarea id="mytextarea">Hello, World!</textarea>
id="mytextarea" 指定してあるので、これを使って値を取得します。
src/index.js
const initMarkdownValue = document.getElementById('mytextarea').value;
この値を Editor に渡すために次のように書きかえます。
src/index.js
root.render(<Editor initMarkdownValue={initMarkdownValue} />);
Editor でこの値を受け取ることができるように、src/Editor.js の該当個所を次のように修正。
function Editor( { initMarkdownValue='' } ) {
const initialConfig = {
...
editorState: () => $convertFromMarkdownString(initMarkdownValue, TRANSFORMERS),
};
これで実行してみます。
うまくいきました。
textarea 要素は、値を受け渡すために使っているだけなので、 表示されない方がよいかもしれません。 必要なら次のように display:none; 指定して隠しておけばよいでしょう。
<textarea id="mytextarea" style="display:none;">Hello, World!</textarea>
完成したコードを掲載します。
dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>my text editor</title>
<style>
body {
margin:0;
padding:0;
}
div {
margin:0;
padding:0;
}
#myeditor {
margin: 30px;
border: solid 1px #333;
}
.editor-paragraph {
padding:0;
margin:0;
font-size:14pt;
}
.editor-placeholder {
position: absolute;
top: 32px;
left: 32px;
user-select: none;
pointer-events: none;
font-size:14pt;
color: #cccccc;
overflow: hidden;
}
</style>
</head>
<body>
<div id="myeditor">
<div id="root"></div>
</div>
<textarea id="mytextarea" style="display:none;">Hello, World!</textarea>
<script src="./editor.js"></script>
</body>
</html>
src/index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import Editor from "./Editor";
const initMarkdownValue = document.getElementById('mytextarea').value;
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<Editor initMarkdownValue={initMarkdownValue} />);
src/Editor.js
import React from 'react';
//import {createRoot} from 'react-dom/client';
import {$getRoot, $getSelection} from 'lexical';
import {useEffect, useState} from 'react';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
//import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
import {
$convertFromMarkdownString,
$convertToMarkdownString,
TRANSFORMERS,
} from '@lexical/markdown';
const theme = {
paragraph: 'editor-paragraph'
};
function MyCustomAutoFocusPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
// Focus the editor when the effect fires!
editor.focus();
}, [editor]);
return null;
}
function onError(error) {
console.error(error);
}
function OnChangePlugin({ onChange }) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerUpdateListener(({editorState}) => {
onChange(editorState);
});
}, [editor, onChange]);
}
function Editor( { initMarkdownValue='' } ) {
const initialConfig = {
namespace: 'MyEditor',
theme,
onError,
editorState: () => $convertFromMarkdownString(initMarkdownValue, TRANSFORMERS),
};
const [editorState, setEditorState] = useState();
function onChange(editorState) {
setEditorState(editorState);
}
return (
<LexicalComposer initialConfig={initialConfig}>
<PlainTextPlugin
contentEditable={<ContentEditable style={{ outline: 'none' }} />}
placeholder={<div className="editor-placeholder">Enter some text...</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
<MyCustomAutoFocusPlugin />
<OnChangePlugin onChange={onChange}/>
</LexicalComposer>
);
}
export default Editor;
次回は、lexical の Editor 上で変更した内容を監視して index.html に反映する方法を調査します。