Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Tiptapで校正機能を作った時に考えたこと
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
kirik
May 26, 2026
Technology
84
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Tiptapで校正機能を作った時に考えたこと
https://web-study.connpass.com/event/391357/
kirik
May 26, 2026
More Decks by kirik
See All by kirik
Recoil脱却の現状と挑戦
kirik
2
880
Tiptapで実現する堅牢で柔軟なエディター開発
kirik
1
470
Recoilを剥がしている話
kirik
5
12k
Other Decks in Technology
See All in Technology
CIで使うClaude
iwatatomoya
0
290
AI Coding Agent時代のcdk-nagガードレール 〜組織ルールを強制CIで守り抜く設計の挑戦〜
mhrtech
3
400
AI時代の開発生産性は、個人技からチーム設計へ
moongift
PRO
4
2.3k
AmplifyHostingConstructからSSRフレームワークのためのホスティング設計を考察する/amplify-hosting-construct
fossamagna
1
220
SRE本の知られざる名シーン / The Hidden Gems of Google SRE Book
nari_ex
1
420
凡エンジニアがこの先生きのこるためには。〜TypeScript完全に理解したい〜
alchemy1115
2
310
Compose 新機能総まとめ / What's New in Jetpack Compose
yanzm
0
320
Oracle Exadata Database Service on Cloud@Customer X11M (ExaDB-C@C) サービス概要
oracle4engineer
PRO
2
8.4k
型は壁、Rustでもバグを直すな、表現できなくせよ
nwiizo
14
2.1k
見守りエージェントを作ってみた(ローカルLLM + Hermes Agent)
happysamurai294
0
110
Amplify Gen2でbackend.tsにCDKを定義する/しない事によるCDKの挙動の違いとユースケース
smt7174
1
390
Network Firewallやっていき!
news_it_enj
0
170
Featured
See All Featured
Raft: Consensus for Rubyists
vanstee
141
7.6k
Typedesign – Prime Four
hannesfritz
42
3.1k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Context Engineering - Making Every Token Count
addyosmani
9
1k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
Thoughts on Productivity
jonyablonski
76
5.2k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
340
The Curse of the Amulet
leimatthew05
2
13k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
360
Mind Mapping
helmedeiros
PRO
1
280
[SF Ruby Conf 2025] Rails X
palkan
2
1.2k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Transcript
Tiptap で校正機能を作った時に 考えたこと Rich Text Editor Study 2026/5/19 (火) 株式会社PR
TIMES @kiririLee
• 校正機能について • Tiptap について ◦ Tiptap が持つドキュメント構造 • API
レスポンスをHTMLにマッピングする • 校正箇所が重なった場合を表現する • ProseMirror Plugin で細かい挙動を制御する • まとめ
校正機能について
• 2024年9月12日にリリースされた 機能 • プレスリリース入稿時のエディター上 で文章を校正し、誤りを修正できる 機能 • 一般的に校正対象となるようなルー ルとプレスリリースに特化したルー
ルが組まれている https://prtimes.jp/main/html/rd/p/000001456.000000112.html
デモ動画 https://youtu.be/n0jryll2o1o
Tiptap について
• Tiptap は ProseMirror のラッパーであり、内部的には ProseMirror が 独自のツリーを管理している • ProseMirror
は HTML を直接操作するのではなく、独自のドキュメント構 造に落とし込み、エディタ上の状態と合わせて扱う。
Node と Mark このスライドでは ProseMirror が持つドキュメント構造全体を 「ProseMirror Document Model」 と呼ぶようにします。
API レスポンスを HTML にマッピングする
• 文章の校正処理はAPIに投げている。校正結果は JSON であるため、校正箇所を 示す要素を HTML にマッピングする • 校正箇所を示す要素は proofreading
要素を使用している ◦ ブラウザ標準で proofreading 要素はなく、当社で拡張している要素 ※ マッピング処理は Tiptap が関係しない
• parseHTML (と renderHTML) が HTML 構造を ProseMirror の Mark
に変換するための橋渡し になっているメソッド • HTMLドキュメント上の proofreading 要素を Proofreading Mark として紐づけ、その他のプ ロパティやメソッドなどで 振る舞いを実装 proofreading 要素に対してエディターの振る舞いを付与
• Tiptap から提供される Mark.create で特定の HTML 要素を指定して、 ProseMirror Document Model
の一部として読み込む(パースする) • Mark.create の戻り値は Tiptap では Extension という概念で管理される • 校正機能はコードベース上で proofreading 要素をパースして、振る舞いを付与し た Extension を ProofreadingExtension と呼んでいる proofreading 要素に対してエディターの振る舞いを付与
• 最大のポイントは excludes オプションの 設定 • この後のスライドで登場す る要件を実装するのに必 要不可欠
校正箇所が重なった場合を表現する
校正の指摘箇所が重なる場合のデモ https://youtu.be/MMX-cjl8Jxk
校正箇所は重なる場合がある • 指摘箇所がドキュメント上で重なる。これを視覚的にユーザーにフィードバック したい
• proofreading 要素をネストし、CSS で透明度 20% を重ねる
この時の同一要素のネストが excludes で設定出来る • デフォルトの設定だとできない • この設定を見つけるのにかなり時 間がかかった
設定しないとパース時にフラットにノーマライズされる パースされる前 パースされた後
同一要素をネストする設定方法がすぐに分からなかった • 最初は Tiptap のドキュメント を参照したが、空文字の指定で同一要素のネス トができることは書かれていなかった • 実装した当時(2024年6月ごろ)は、ChatGPT に聞いても教えてくれず、
inline 化した Node で実装する方法しか提案されなかった • Tiptap の作者が同一要素のネストを inline化した Node で実装することを 提案していた • PromseMirror のドキュメントには書かれていたが inline化した Node に よる実装に着手してから気がつく
inline node での実装提案 https://github.com/ueberdosis/tiptap/ discussions/2270
開発当初は inline node で実装していた • 開発当初は、Tiptap 作者が同一要素のネスト実装として inline node を提案し
ていたため、それを採用して開発を進めていた • しかし、改行ができないなどテキスト編集機能に制約が多く、不足を補うための複 雑な実装が増加 • その結果、システムテストで多数の不具合が発生し、inline node ベースの実装 には限界があると判断 • 最終的には、excludes: '' を設定した Mark で再実装 「Mark + excludes: ''」 で実装するというのは、 校正機能開発において最も時間を要した設計判断
None
この節のまとめ
ProseMirror Plugin で 細かい挙動を制御する
実装が必要だった要件 • 校正箇所で Backspace キーが押下された場合、該当する校正箇所を 削除する • 校正の指摘が重複している位置で Backspace キーが押下された場
合、重複しているすべての校正箇所を削除する
Mark のデフォルト挙動デモ https://youtu.be/5Ud2BzmHy3M
PM Plugin 実装後の挙動デモ https://youtu.be/ikJTJ5xaMFs
要件に合わせてMark のデフォルト挙動を変える実装をする
実装の概要 • handleKeyDown で Backspace を検知 • ProseMirror のメソッドである tr.removeMark で
Node に付与 されている Proofreading Mark を削除する • 校正の指摘が重複した場合は、親の Proofreading Mark も削除する
handleKeyDown で Backspace を拾う • Proofreading Extension に addProseMirrorPlugins を持たせ、その中で
ProseMirror の Plugin を返 す • Plugin の実装は完全に ProseMirror の世界 • Tiptap 越しではなく、 ProseMirror の API を直接 扱う
ProseMirror 3つの基本概念
Proofreading Mark を外す処理 • Backspace が押されたタイ ミングで、カーソル位置を取 得 • カーソル位置にある
Node を取得 • Node に付与されている Proofreading Mark を外 す
Proofreading Mark を外す処理 • カーソル位置から直接Mark を消すのではなく、 TextNode に付与されてい る Mark
を消す • 取得した TextNode には太 文字や下線など校正以外の別 Mark も同時に付与されてい る可能性があるため、校正の Mark のみ消す
基本的には Proofreading Mark を外す処理はこれでOK。 校正の指摘が重複した場合、 親の Proofreading Mark も 外す必要がある
HTML から見て親の Proofreading Mark も消す必要がある 子の Proofreading Mark は消せた
校正の指摘が重複したことを判定する • 校正の指摘が重複した、という判 定が必要 • この判定は、TextNode に付与 されている Proofreading Mark
が複数あるかどうかで判 定している
校正の指摘が重複したことを判定する https://youtu.be/jhiXM1EpVwY
コンソールに表示される TextNode HTML構造 • 「食べれ」の TextNode に親の id も付与されて いる
• この id を元に親の Mark を消す
校正の指摘が重複した時、 親の Proofreading Mark も外す HTML構造 ProseMirror Document Model
校正の指摘が重複した時、 親の Proofreading Mark も外す • Mark は TextNode に付与
される • ドキュメント上にある「子で取 得した id」 を持つ Mark が 付与されている TextNode をドキュメント上の Node を 走査して全て探す • 探し出した TextNode から Proofreading Mark を外 す
• 隣接する TextNode を取得するメソッドはあるが、2つ以上重複した場合 (可能性は非常に低い) を考えると横方向に全ての Proofreading Mark を辿るのが野暮ったい(コードから何をしているかが分かりにくい) •
ProseMirror Document Model の Mark はフラットな構造であるた め、ネストや親と子というような関係はない • Mark がフラットであるため、ドキュメント上の Node を全て走査する実装 は結構やる事が多い印象 • (他に良い方法があるかも? unsetMark メソッドで対応できないケースを 紹介したいけどまた今度) 校正の指摘が重複した時、 親の Proofreading Mark も外す
まとめ
• 校正箇所に対してエディター上ではテキスト操作がメインにな るため Mark を使う • 同一要素をネストして扱いたい場合には excludes オプ ションに空文字を指定する
• ProseMirror Plugin を使うと Mark のデフォルト挙動 を柔軟に変えられる