ブロッククオート先頭での改行時にテキストが書式化されないように修正

basecamp/lexxy

ブロッククオートの最初の段落先頭で Enter を押すと、直前に空段落が挿入されてクオート内部に残り、続く入力が引用形式になる問題を解消し、カーソルが段落先頭にある場合はクオート外に平文段落を作成するように変更しました。

背景

ブロッククオートの先頭で改行した際に書式が漏れる という挙動は、ユーザーが引用文の上部に新規テキストを入力したときに予期せぬ引用が付加されるという UX の欠陥でした。既存実装では INSERT_PARAGRAPH_COMMAND が空段落をブロッククオート内部に生成し、$escapeFromBlankBlockquoteParagraph がその後処理を担当していましたが、カーソルが最初の非空段落のオフセット0にあるケースを網羅できていませんでした。この問題は Basecamp の内部カード #9985732711 で報告されており、Trix の BC4 挙動(引用上部のテキストは書式化しない)と一致させることが求められました。

技術的な変更

src/extensions/format_escape_extension.js$escapeFromBlockquote が新たに $escapeBeforeBlockquoteStart$escapeFromBlankBlockquoteParagraph の両方を呼び出すように変更され、前者がカーソルがブロッククオート最初の段落先頭にあるかを判定して平文段落を前方に挿入します。

function $escapeFromBlockquote() {
  return $escapeBeforeBlockquoteStart() || $escapeFromBlankBlockquoteParagraph()
}

$escapeBeforeBlockquoteStart は次のロジックを実装しています。

function $escapeBeforeBlockquoteStart() {
  const selection = $getSelection()
  if (!$isRangeSelection(selection) || !selection.isCollapsed() || selection.anchor.offset !== 0) return false

  const paragraph = $getNearestNodeOfType(selection.anchor.getNode(), ParagraphNode)
  if (paragraph && !$isBlankNode(paragraph) && !paragraph.getPreviousSibling()) {
    const blockquote = paragraph.getParent()
    if ($isQuoteNode(blockquote)) {
      blockquote.insertBefore($createParagraphNode())
      return true
    }
  }
  return false
}

この実装により、カーソルが最初の非空段落の先頭であり、かつ前に兄弟要素が無い場合に ブロッククオートの直前に新しい空段落 が作成され、ユーザーの入力は引用外の平文として扱われます。既存の $escapeFromBlankBlockquoteParagraph は空段落の分割・移動ロジックを保持し、改行後に空段落が残るケースを引き続き処理します。

設計判断

ハンドラを二段階に分割する設計 が採用されました。元の単一関数はロジックが混在し、条件分岐が複雑化していました。新しい $escapeBeforeBlockquoteStart を導入し、「ブロッククオート開始位置でのエスケープ」「空段落からのエスケープ」 を明確に分離することで、テスト容易性と可読性が向上しました。さらに、$escapeFromBlockquote が短絡評価でいずれかが真なら処理完了とすることで、不要な分岐チェックを回避し、実行コストも僅かに削減されています。

まとめ

ブロッククオート先頭での Enter に対し、引用外に平文段落を挿入するロジックを追加し、書式が意図せず継承される問題を根本的に解消しました。テストスイート(Playwright と Vitest)がすべてパスし、既存機能への影響もなく、ユーザー体験が Trix の期待動作と一致するようになりました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
9ee57f67

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断、まとめの全てが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのファイル名付きシンタックスハイライトは正しい形式です。ただし PR リンクが "[PR #1158](URL)" となっており、要求される形式 "[#1158](URL)" と若干異なります。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

エンジニア向けの専門的な内容で、不要な初心者向け説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の段落構成になっており、トピックセンテンスで始まり、1段落1トピック・長さも適切です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードは Diff に示された追加・変更内容と一致しており、誤りは見られません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている用語は PR やコードベースと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な説明は PR の記述と整合しており、因果関係も正しく示されています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の全ての主張は PR タイトル、説明、Diff、カード番号に裏付けられており、捏造や推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号、カード番号、ファイルパスなどの固有名詞は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の内容を日本語で適切に要約しており、一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR に記載されていない外部知識(バージョンサポート等)は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現のずれはなく、PR の表現と一致しています。