ブロック引用符貼り付け時の段落間空白行を保持

basecamp/lexxy

Lexxy の Markdown 貼り付け機能で、ブロック引用符内部の複数段落間の空白行が失われる不具合を解消しました。この変更により、引用内の段落構造が正しく視覚的に分離されます。

背景

Markdown のブロック引用符内で空白行が消える問題は、エディタが貼り付け時に段落間のスペーサーを付与しないことが原因でした。トップレベルの段落間では空白行が保たれる一方、> で始まるブロック引用符内部では
が挿入されず、段落が連続して表示されます。貼り付けは lexxy:insert-markdown イベント経由で addBlockSpacing が呼び出される設計ですが、addBlockSpacing の対象範囲が限定的だったために発生しました。このギャップ消失は、ホストアプリが期待するレイアウトを壊すため修正が必要でした。

addBlockSpacing の実装は、body > :not(h1, h2, h3, h4, h5, h6) + * という selector でトップレベル子要素のみを対象としていました。ブロック引用符内部の <p> 要素は <blockquote> の子孫であり、直接の子ではないためセレクタにマッチせず、スペーサーが挿入されませんでした。その結果、引用符内の段落は空白行なしで連結され、Markdown の意図した見た目が失われていました。したがって、ブロック引用符内の段落間に空白行を保持するロジックが求められました。

技術的な変更

セレクタを拡張し、blockquote > :not(h1, h2, h3, h4, h5, h6) + * を追加しました。この新しい selector はブロック引用符内部の隣接ブロック要素も対象に含め、従来と同様にヘディング除外条件を維持します。結果として、引用符内の段落間にも <p><br></p> のスペーサーが自動的に挿入されます。

@@
 export function addBlockSpacing(doc) {
-  const blocks = doc.querySelectorAll("body > :not(h1, h2, h3, h4, h5, h6) + *")
-  for (const block of blocks) {
+  const selector = "body > :not(h1, h2, h3, h4, h5, h6) + *, blockquote > :not(h1, h2, h3, h4, h5, h6) + *"
+  for (const block of doc.querySelectorAll(selector)) {
     const spacer = doc.createElement("p")
     spacer.appendChild(doc.createElement("br"))
     block.before(spacer)
   }
 }

テストも三点追加し、機能を検証しました。Playwright でブロック引用符の貼り付け結果を確認し、blockquote 内に二つの <p> が存在し、テキストが正しく保持されていることをアサートしています。また、addBlockSpacing が引用符内部にスペーサーを挿入することを確認するテストと、ユニットテストで同様の振る舞いを検証しています。すべてのテストが成功し、期待通りに空白行が保持されることが保証されました。

設計判断

既存の addBlockSpacing ロジックを拡張するだけのアプローチが選択されました。新規関数や大幅なリファクタリングを行わず、セレクタにブロック引用符のパスを追加することで要件を満たしています。これにより、lexxy:insert-markdown イベントを利用している既存のホストアプリケーションへの破壊的変更は発生しません。

セレクタにブロック引用符内部を含める際、ヘディング要素は除外対象と同様に除外しています。:not(h1, h2, h3, h4, h5, h6) の条件が保持されているため、見出し間に不要なスペーサーが挿入されることはありません。この設計は、他のブロック要素との一貫性を保ちつつ、意図しないレイアウト変化を回避する点で妥当です。

まとめ

今回の変更は、Markdown のブロック引用符内部で段落間の空白行が正しく保持されるよう addBlockSpacing の対象範囲を拡張したものです。最小限のコード変更で機能追加を実現し、既存の API と振る舞いを維持しながら、ホストアプリが期待するレイアウトを提供できるようになりました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
1f83e404

この記事は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 #1155" と余分な語が入っており、推奨される "[#1155]" 形式から外れています。

対象読者への適合性 ✓ PASS

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

専門的なエンジニア向けの記述で、余計な初心者向け解説はなく適切です。

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

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

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

Diff内容との照合 ✓ PASS

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

記事中のコードブロックは提供されたDiffと完全に一致しており、ファイル名・変更内容ともに正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

selector、blockquote、addBlockSpacing などの用語はPRと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

問題の原因・修正内容・テスト追加の説明がPRの記述と合致しており、技術的に正確です。

事実の突合 ✓ PASS

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

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

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

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

PR番号 #1155、テスト追加数「三点」など数値情報は正確です。

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

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

記事タイトルは PR タイトルの日本語訳として適切に一致しています。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース日程など、PRに記載されていない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PRの記述と一致しています。