前々回 および 前回から引き続いて 今回は、 lexical の Editor で変更があったら index.html にそれを反映する 方法を調べます。
Editor で変更があったらコンソールにその内容を出すように変更してみます。
src/Editor.js の変更差分。
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerUpdateListener(({editorState}) => {
+ editor.update(()=>{
+ const newMarkdownValue = $convertToMarkdownString(TRANSFORMERS);
+ console.log(newMarkdownValue);
+ });
+
onChange(editorState);
});
}, [editor, onChange]);
要するに OnChangePlugin 内でエディタの内容に変更があったらコールバックされる部分で、 現在の editorState を マークダウンテキストに変換する処理を入れています。
editor.update(()=>{
const newMarkdownValue = $convertToMarkdownString(TRANSFORMERS);
console.log(newMarkdownValue);
});
editor.update(()=>{}) の関数内では $なんとか スペシャル関数が使えるとかなんとか。 このページに説明があります。
// Inside the `editor.update` you can use special $ prefixed helper functions.
よくわからないのだが、とりあえずこれで内容に変更がはいるたびに コンソールに変更後の内容を書き出すことはできるようになりました。
あとはこれを dist/index.html で把握できるようにする必要があります。
変更が発生したらそれを受け取ることができるように、 リスナー(コールバック)関数を用意して、追加します。
コールバックするための関数は、 onTextChange という名前にします。
最初に src/index.js に onTextChange 関数を定義して、それを Editor へ渡します。
onTextChange 内では、変更された内容を受け取ったら(コールバックされてきたら)、 mytextarea の id の textarea へそれを反映します。
const onTextChange = (newMarkdownValue)=>{
document.getElementById('mytextarea').value = newMarkdownValue;
};
...
root.render(<Editor initMarkdownValue={initMarkdownValue} onTextChange={onTextChange} />);
src/Editor.js ではこの onTextChange 関数を受け取って OnChangePlugin に渡します。
function Editor( { initMarkdownValue='', onTextChange=(f)=>f } ) {
...
return (
<LexicalComposer initialConfig={initialConfig}>
...
<OnChangePlugin onChange={onChange} onTextChange={onTextChange}/>
</LexicalComposer>
);
}
OnChangePlugin で onTextChange 関数を受け取って 変更が発生したときに、 コンソールに出す代わりに onTextChange(newMarkdownValue) としてコールバックします。
function OnChangePlugin({ onChange, onTextChange }) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerUpdateListener(({editorState}) => {
editor.update(()=>{
const newMarkdownValue = $convertToMarkdownString(TRANSFORMERS);
onTextChange(newMarkdownValue);
//console.log(newMarkdownValue);
});
onChange(editorState);
});
}, [editor, onChange]);
}
さらに textarea 要素を display:none; にしていたものを display:block; にして、表示を復活させておきます。
この状態で実行すると、次のように変更がそのまま 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:block;">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 onTextChange = (newMarkdownValue)=>{
document.getElementById('mytextarea').value = newMarkdownValue;
};
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<Editor initMarkdownValue={initMarkdownValue} onTextChange={onTextChange} />);
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, onTextChange }) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerUpdateListener(({editorState}) => {
editor.update(()=>{
const newMarkdownValue = $convertToMarkdownString(TRANSFORMERS);
onTextChange(newMarkdownValue);
//console.log(newMarkdownValue);
});
onChange(editorState);
});
}, [editor, onChange]);
}
function Editor( { initMarkdownValue='', onTextChange=(f)=>f } ) {
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} onTextChange={onTextChange}/>
</LexicalComposer>
);
}
export default Editor;
以上です。
次回は、 エディタの初期値を与える部分で 手抜きして既存の markdown モジュールを使っていたが、 問題が実はあったので、その修正を試みます。