Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tiptapで校正機能を作った時に考えたこと

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

 Tiptapで校正機能を作った時に考えたこと

Avatar for kirik

kirik

May 26, 2026

More Decks by kirik

Other Decks in Technology

Transcript

  1. • 校正機能について • Tiptap について ◦ Tiptap が持つドキュメント構造 • API

    レスポンスをHTMLにマッピングする • 校正箇所が重なった場合を表現する • ProseMirror Plugin で細かい挙動を制御する • まとめ
  2. • Tiptap は ProseMirror のラッパーであり、内部的には ProseMirror が 独自のツリーを管理している • ProseMirror

    は HTML を直接操作するのではなく、独自のドキュメント構 造に落とし込み、エディタ上の状態と合わせて扱う。
  3. • 文章の校正処理はAPIに投げている。校正結果は JSON であるため、校正箇所を 示す要素を HTML にマッピングする • 校正箇所を示す要素は proofreading

    要素を使用している ◦ ブラウザ標準で proofreading 要素はなく、当社で拡張している要素 ※ マッピング処理は Tiptap が関係しない
  4. • parseHTML (と renderHTML) が HTML 構造を ProseMirror の Mark

    に変換するための橋渡し になっているメソッド • HTMLドキュメント上の proofreading 要素を Proofreading Mark として紐づけ、その他のプ ロパティやメソッドなどで 振る舞いを実装 proofreading 要素に対してエディターの振る舞いを付与
  5. • Tiptap から提供される Mark.create で特定の HTML 要素を指定して、 ProseMirror Document Model

    の一部として読み込む(パースする) • Mark.create の戻り値は Tiptap では Extension という概念で管理される • 校正機能はコードベース上で proofreading 要素をパースして、振る舞いを付与し た Extension を ProofreadingExtension と呼んでいる proofreading 要素に対してエディターの振る舞いを付与
  6. 同一要素をネストする設定方法がすぐに分からなかった • 最初は Tiptap のドキュメント を参照したが、空文字の指定で同一要素のネス トができることは書かれていなかった • 実装した当時(2024年6月ごろ)は、ChatGPT に聞いても教えてくれず、

    inline 化した Node で実装する方法しか提案されなかった • Tiptap の作者が同一要素のネストを inline化した Node で実装することを 提案していた • PromseMirror のドキュメントには書かれていたが inline化した Node に よる実装に着手してから気がつく
  7. 開発当初は inline node で実装していた • 開発当初は、Tiptap 作者が同一要素のネスト実装として inline node を提案し

    ていたため、それを採用して開発を進めていた • しかし、改行ができないなどテキスト編集機能に制約が多く、不足を補うための複 雑な実装が増加 • その結果、システムテストで多数の不具合が発生し、inline node ベースの実装 には限界があると判断 • 最終的には、excludes: '' を設定した Mark で再実装 「Mark + excludes: ''」 で実装するというのは、 校正機能開発において最も時間を要した設計判断
  8. 実装の概要 • handleKeyDown で Backspace を検知 • ProseMirror のメソッドである tr.removeMark で

    Node に付与 されている Proofreading Mark を削除する • 校正の指摘が重複した場合は、親の Proofreading Mark も削除する
  9. handleKeyDown で Backspace を拾う • Proofreading Extension に addProseMirrorPlugins を持たせ、その中で

    ProseMirror の Plugin を返 す • Plugin の実装は完全に ProseMirror の世界 • Tiptap 越しではなく、 ProseMirror の API を直接 扱う
  10. Proofreading Mark を外す処理 • カーソル位置から直接Mark を消すのではなく、 TextNode に付与されてい る Mark

    を消す • 取得した TextNode には太 文字や下線など校正以外の別 Mark も同時に付与されてい る可能性があるため、校正の Mark のみ消す
  11. 基本的には Proofreading Mark を外す処理はこれでOK。 校正の指摘が重複した場合、 親の Proofreading Mark も 外す必要がある

    HTML から見て親の Proofreading Mark も消す必要がある 子の Proofreading Mark は消せた
  12. 校正の指摘が重複した時、 親の Proofreading Mark も外す • Mark は TextNode に付与

    される • ドキュメント上にある「子で取 得した id」 を持つ Mark が 付与されている TextNode をドキュメント上の Node を 走査して全て探す • 探し出した TextNode から Proofreading Mark を外 す
  13. • 隣接する TextNode を取得するメソッドはあるが、2つ以上重複した場合 (可能性は非常に低い) を考えると横方向に全ての Proofreading Mark を辿るのが野暮ったい(コードから何をしているかが分かりにくい) •

    ProseMirror Document Model の Mark はフラットな構造であるた め、ネストや親と子というような関係はない • Mark がフラットであるため、ドキュメント上の Node を全て走査する実装 は結構やる事が多い印象 • (他に良い方法があるかも? unsetMark メソッドで対応できないケースを 紹介したいけどまた今度) 校正の指摘が重複した時、 親の Proofreading Mark も外す