概要
自作のブログエディタ(見たまま編集できるリッチエディタ)に対して、使い勝手に関わる細かい改善をまとめて行った。過去記事の移行作業をしている中で見つかった不便な点を、一つずつ潰していった形になる。
この記事では、今回行った修正を分野ごとに整理して記録しておく。
投稿まわり
下書き保存ボタンの追加
これまで明示的な下書き保存の手段がなかったため、ボタンを一つ追加した。押すとタイトル・本文・カテゴリに加えて、設定した投稿日も一緒に保存する。
公開済みの記事を編集しているときは、ボタンの文言を「下書きに戻して保存する」に切り替えるようにした。状態によって表示を変えることで、操作の結果を誤解しにくくしている。
過去の投稿日をタイムラインに出すかどうかの制御
過去に書いた記事を移行すると、公開ボタンを押した「今」の時刻が記録され、トップページのタイムラインの先頭に出てしまう問題があった。
これを解決するため、記事に「タイムラインに表示するかどうか」を表すフラグを持たせた。さらに公開時に記録する日時を、「設定した投稿日 + 公開した瞬間の時刻」で組み立てるようにした。たとえば投稿日を数年前に設定して昼に公開すると、タイムライン上でもその当時の日時の位置に並ぶ。
編集画面には「過去の投稿日のままタイムラインに表示する」というチェックボックスを置き、投稿日を過去に変更すると自動的にオフ(=タイムラインに出さない)になるようにした。チェックを入れたときだけ、当時の位置にさかのぼって表示される。
タイムライン取得のクエリ側にも条件を足し、オフの記事はトップのタイムラインからは外れるが、ブログの一覧や記事ページには通常どおり残るようにしている。
書き味の改善
段落・見出しの先頭でEnterすると上に空行を入れる
文章のある行の先頭にカーソルを置いてEnterを押したとき、ブラウザによっては下に改行が入ってしまうことがあった。これを「上に空行が入り、カーソルは今の行に残る」という挙動に統一した。段落だけでなく、見出しや引用の先頭でも同じように動く。
本文の一番先頭だけは、カーソルが行ではなく本文全体の境界に乗ることがあり、判定が漏れていた。境界にいる場合は先頭の行を対象として拾い直すことで、冒頭でも確実に上へ空行が入るようにした。
画像の下にできる余計な空行をなくした
画像のような「カーソルを直接置けないブロック」の直後に、毎回かならず空の段落を入れていたため、画像の下に常に一行分の余白ができていた。そこを詰めようとすると画像ごと消えてしまうという問題もあった。
空の段落を入れるのは「次もカーソルを置けないブロックが続く場合」と「文書の末尾がそういうブロックの場合」だけに限定した。画像の次が文章なら、余計な余白は入らない。
取り消し / やり直し(Ctrl+Z / Ctrl+Y)
このエディタは独自にDOMを組み立てているため、ブラウザ標準の取り消し機能が正しく効かなかった。そこで独自の履歴を持たせた。
編集のたびに本文の内部表現をスナップショットとして履歴に積み、取り消し時にはそこから画面を作り直す。作り直す方式にしたことで、画像の削除ボタンなどのイベントも壊れずに復活する。連続した文字入力は短い時間でまとめて一手としている。
最初は取り消し後にカーソルが末尾に飛んでいたが、各スナップショットに「本文の先頭から何文字目か」という形でカーソル位置も記録し、復元時に元の位置へ戻すようにした。文字数で覚えているため、画面を作り直しても位置がずれない。
なお、HTMLソース編集欄やタイトルなどの通常の入力欄では、ブラウザ標準の取り消しに任せるよう切り分けている。
HTML編集タブ
過去記事の本文をHTMLのまま貼り付けて移行したい、という要望から、リッチエディタとHTMLソースを行き来できるタブを追加した。
リッチ ⇆ HTML の切り替えと正規化
「リッチ」と「HTML」のタブを用意し、HTMLタブでは本文がHTMLソースとして表示される。そこへHTMLを貼り付けて、リッチに戻すと内容が反映される。
貼り付けたHTMLはそのまま使うのではなく、エディタが扱える要素(見出し・段落・太字や斜体・リスト・引用・区切り線・画像・YouTube・Xの埋め込み・リンクカード・目次)に正規化してから取り込む。対応していないタグは中の文字だけ残して整理するため、貼り付けで表示が崩れることを防いでいる。取り込み時に script や style は無視し、画像やリンクのURLは http/https のみを許可している。
Xの公式埋め込みHTMLは、本文中のリンクと日付リンクの両方が含まれている。最初のリンクではなく「ツイートのURL」を正しく拾うよう、末尾側からツイートURLの形式に一致するものを優先して抽出するようにした。
タブの固定とカーソル位置の同期
タブをスクロールしても画面上部に追従するよう固定した。さらに、タブを切り替えたときに同じ位置にカーソルが合うようにした。リッチ側で見ているブロックを基準にHTML側の対応位置へ移動し、逆方向では位置の目印を一時的に差し込んで対応する要素を特定している。
リッチ側で本文をスクロールしただけのとき(カーソルは動いていないとき)に位置がずれる問題もあったため、「カーソルが画面内に見えていればそのブロック、見えていなければ画面最上部のブロック」を基準にするよう調整した。
見た目を本文に合わせる・入力時のスクロール対策
HTMLソース欄を、等幅・枠付きの「入力ボックス」のような見た目から、リッチエディタの本文と同じフォント・行間・余白なしの表示に変えた。内容に合わせて縦に自動で伸び、箱の内部ではなくページ全体でスクロールする。
自動で高さを変える処理で、入力のたびに一瞬縮んでページが短くなり、スクロール位置が上にずれて編集できなくなる問題があった。高さを変える前後でスクロール位置を保存・復元することで解消した。
リンクまわり
文中リンクへの対応
文章の途中にリンクを置けるようにした。内部的には標準的なMarkdownのリンク記法で保存し、表示時にリンクへ変換する。書式メニューにリンクボタンを追加し、選択した文字をリンクにできる。HTMLの取り込み時も、段落内のリンクや単独で置かれたリンクを文中リンクとして保持する。
リンク先は http/https のみ許可し、外部リンクには rel="noopener noreferrer" を付与して、別タブで開いても元ページを操作されないようにしている。
リンクの見た目を公開ページでも統一
本文中のリンクの色を編集画面と同じアクセント色に揃え、下線を短い破線にした。外部リンクには小さなアイコンを付けるようにし、編集画面・プレビュー・公開ページで見た目を統一した。アイコンはSVGをマスクとして使い、リンクの文字色に追従させている。
リンクカードのタイトルに記号が含まれると崩れる不具合
リンクカードのデータは、URLやタイトルなどを区切り文字でつないで保存している。取得したタイトルに区切り文字と同じ記号が含まれていると、保存時に項目がずれて画像URLが壊れ、公開ページでカードの画像が消えていた。
保存した文字列を読み戻す側で、項目数が想定より多い場合は「タイトルに区切り記号が混ざった壊れたデータ」とみなし、末尾から順に項目を割り当ててタイトルを復元するようにした。これで既存の壊れたカードも作り直さずに直る。あわせて、リンク情報を取得する処理でHTMLの文字参照(& など)を元の文字に戻すようにし、タイトルがそのまま表示される問題も直した。
リンク切れ(デッドリンク)の検出と打ち消し線
リンク先が切れている場合に打ち消し線を引けるようにした。
リンクの生死をリアルタイムに判定するのは難しい。閲覧者のブラウザから他サイトへアクセスして状態を取るのは、ブラウザのセキュリティ制約(CORS)で正確にできず、表示のたびにサーバー側で確認するのは通信量や実行時間の面で無料運用に向かない。
そこで、編集画面に「リンク切れを確認」ボタンを置き、押したときにだけサーバー側で記事内のリンクへアクセスして確認する方式にした。存在しないことが明確なもの(404・410)や接続できないものを「切れ」と判定し、切れているURLの一覧を専用のテーブルに記録する。誤検知を避けるため、アクセス拒否やタイムアウトは「切れ」とはみなさない。無料運用に配慮し、1回の確認で扱うURL数や同時に処理する数には上限を設けている。
表示時はこの記録を参照するだけなので軽い。記録されたURLのリンクには打ち消し線、リンクカードは薄く表示してタイトルに打ち消し線を引く。次回の確認でつながることが分かれば記録から外れるため、復活にも対応している。
スマホ表示の修正
スマホで編集画面を開いたとき、上部のバーがステータスバーと重なって「もどる」「公開する」のボタンが押せない問題があった。スマホ向けの指定で上の余白が消えていたのが原因で、端末のセーフエリアを考慮しつつ、取得できない端末でも最低限の余白を確保するよう直した。
写真をクリックして編集用の小窓を開き、それを閉じると本文の先頭まで強制的にスクロールしてしまう問題もあった。写真はカーソルを置けないブロックのため、閉じる際にフォーカスを戻すと先頭へ飛んでいた。フォーカス時の自動スクロールを抑止し、念のため位置を保存・復元することで、元の写真の位置のままになるようにした。
まとめ
今回は派手な新機能というより、実際に記事を書いたり移行したりする中で見つかった「引っかかり」を一つずつ取り除く作業だった。書き味や表示の細かい違和感は積み重なると効いてくるので、こうした小さな改善も記録に残しておく。