選択範囲へのURL貼り付けで複数のリンクノードを統合
Lexxyのリンク生成ロジックが改善され、複数の単語にまたがる選択範囲へURLを貼り付けた際に、単一の LinkNode が生成されるようになりました。従来は同じURLを持つ隣接リンクが複数生成される問題がありましたが、既存リンクをクリアしてから新規リンクを適用する方式に変更されています。
背景
複数の単語や既存のリンクを含む選択範囲にURLを貼り付けると、Lexicalは各テキストセグメントごとに個別の LinkNode を生成していました。#594 で報告されたこの問題により、<a href="https://example.com">link1</a><a href="https://example.com"> </a><a href="https://example.com">link2</a> のような不要な分割が発生していました。
この問題はリスト項目や他のブロック要素をまたぐ選択範囲でも同様に発生し、本来は <a href="https://example.com">link1 link2</a> のような単一リンクになるべきところが、複数のリンクノードに分割されていました。
技術的な変更
createLinkWithSelectedText メソッドに $toggleLink(null) の呼び出しが追加され、新しいURLを適用する前に既存のリンクをクリアするようになりました。
変更前:
createeLinkWithSelectedText(url) {
if (!this.hasSelectedText()) return
this.editor.update(() => {
$toggleLink(url)
})
}
変更後:
createLinkWithSelectedText(url) {
if (!this.hasSelectedText()) return
this.editor.update(() => {
$toggleLink(null)
$toggleLink(url)
})
}
$toggleLink(null) により、選択範囲内の既存リンク境界がすべて解除されます。これによりLexicalは選択範囲を「リンク境界のないフラットなテキストノード」として認識し、続く $toggleLink(url) が選択範囲全体に対して単一の LinkNode を生成します。
テストケースでは、"Hello"という単語をリンク化した後、スペースと"everyone"を追加し、全体を選択して同じURLを貼り付けるシナリオが追加されました。
test "merge adjacent links when pasting URL over multiple words" do
find_editor.send "Hello"
find_editor.select "Hello"
find_editor.paste "https://37signals.com"
find_editor.send :arrow_right
find_editor.send " everyone"
find_editor.select_all
find_editor.paste "https://37signals.com"
assert_editor_html do
assert_selector %(a[href="https://37signals.com"]), text: "Hello everyone", count: 1
assert_no_selector %(a + a)
end
end
このテストは、隣接する <a> 要素が生成されないこと(assert_no_selector %(a + a))と、単一のリンク要素のみが存在することを検証しています。
設計判断
リンクノードの生成前にクリアする方式 が採用されました。
この設計は、Lexicalのコアな振る舞いを変更せず、既存のAPIを組み合わせることで実現されています。$toggleLink はLexicalの標準APIであり、null を渡すことでリンクのトグルオフを実現できます。
リンク境界のクリアと新規リンクの適用を分離することで、選択範囲の構造に関わらず一貫した動作を保証できます。Lexicalのノード生成メカニズムに介入することなく、2回の $toggleLink 呼び出しのみで問題を解決しています。
まとめ
本PRは、リンク貼り付け時の既存リンククリア処理を追加することで、複数のリンクノード生成問題を解決しました。$toggleLink(null) の1行追加により、選択範囲の構造に依存しない一貫したリンク生成が実現され、ユーザーが直感的に期待する単一リンクの生成が保証されています。