[basecamp/lexxy] エディタのエッジケースとUI操作の不具合修正
修正の背景
Lexical.jsベースのリッチテキストエディタ「Lexxy」において、いくつかの小さいながらも重要な不具合が報告されていました。特に、ツールバーの状態不整合、blockquoteの解除時の予期しない動作、エディタの状態が壊れる問題、そしてキーボードショートカット使用時にundo/redoが二重に実行される問題などです。#479では、これらの問題を個別に修正しています。
主な修正内容
1. RootまたはShadowRootノードでの操作時のクラッシュ防止
エディタの選択範囲が特殊なノード(RootまたはShadowRoot)にある場合、getTopLevelElementOrThrow()を呼び出すとエラーが発生していました。これは、見出しやフォーマットを適用しようとした際にエディタが壊れる原因となっていました。
修正内容(src/editor/command_dispatcher.js):
const selection = $getSelection()
if (!$isRangeSelection(selection)) return
// RootまたはShadowRootの場合は新しいノードを挿入
if ($isRootOrShadowRoot(selection.anchor.getNode())) {
selection.insertNodes([ $createHeadingNode("h2") ])
return
}
const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow()
同様の修正が、カスタムブロック要素の挿入処理にも適用されています。
2. Blockquoteの解除処理の改善
Blockquoteを解除する際、内部のテキストノードや改行要素の処理が不適切で、予期しない結果となっていました。この修正では、以下のロジックが追加されました:
修正内容(src/editor/contents.js):
#unwrap(node) {
const children = node.getChildren()
if (children.length == 0) {
// 空の場合は段落を挿入
node.insertBefore($createParagraphNode())
} else {
children.forEach((child) => {
if ($isTextNode(child) && child.getTextContent().trim() !== "") {
// テキストノードは段落でラップ
const newParagraph = $createParagraphNode()
newParagraph.append(child)
node.insertBefore(newParagraph)
} else if (!$isLineBreakNode(child)) {
// 改行以外の要素はそのまま挿入
node.insertBefore(child)
}
})
}
node.remove()
}
これにより、blockquote内のテキストが適切に段落要素でラップされ、改行要素は除外されるようになりました。
3. エディタ初期化時の選択位置の修正
エディタに値を設定した際、選択範囲が不適切に設定されていました。
修正前:
root.append(...this.#parseHtmlIntoLexicalNodes(html))
root.select()
修正後:
root.append(...this.#parseHtmlIntoLexicalNodes(html))
root.selectEnd()
selectEnd()を使用することで、コンテンツの末尾にカーソルが配置されるようになりました。また、空のコンテンツ設定時に空の<p><br></p>が挿入されなくなり、テストの期待値も更新されています。
4. Undo/Redoの二重実行の防止
ツールバーのundo/redoボタンにdata-hotkey属性が設定されていたため、キーボードショートカット(Cmd+Z/Ctrl+Zなど)を使用すると、ブラウザのネイティブ動作とLexicalのコマンドが両方トリガーされ、二重に実行されていました。
修正内容(src/elements/toolbar.js):
<button class="lexxy-editor__toolbar-button" type="button"
name="undo" data-command="undo" title="Undo">
<!-- data-hotkey属性を削除 -->
</button>
<button class="lexxy-editor__toolbar-button" type="button"
name="redo" data-command="redo" title="Redo">
<!-- data-hotkey属性を削除 -->
</button>
キーボードショートカットの処理はLexicalのコマンドシステムに任せることで、この問題を解決しています。
5. スタイリングの微調整
Blockquote内の最後の段落の下マージンを削除し、より適切な視覚的表現を実現しています。
blockquote {
border-left: 4px solid var(--lexxy-content-border-color);
font-style: italic;
margin: var(--lexxy-content-margin) 0;
padding: 0.5lh 2ch;
p:last-child {
margin-block-end: 0;
}
}
影響範囲
これらの修正により、以下のシナリオでの動作が改善されました:
- 空のエディタや特殊な選択状態での見出し・フォーマット適用
- Blockquoteの解除操作
- エディタへの初期値設定
- Undo/Redoのキーボードショートカット使用時の動作