0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザ自動化で note の記事に 1 行足そうとして 3 回壊した——リッチエディタを壊さない 3 原則(14/14 で通った手順)

0
Posted at

Chrome 拡張からの自動操作で、自分の note 記事 14 本の末尾に同じ 1 行(サイトへの導線)を足そうとしました。「末尾をクリック → End → Enter → 文字を打つ」で 3 回壊れました。原因は note 固有というより、フォーカス・キャレット・保存前の検査を分けて扱っていなかったこと。DOM の Selection で位置を作り、1 手ごとに状態を数える手順にしたら 14/14 通りました。失敗 3 型と手順を書きます。

この記事の位置づけ(2026-09-26 追記)

  • この記事で新しく示すこと: note のリッチエディタを自動操作で壊した 3 型と、DOM の Selection で位置を作って 14/14 通った手順。
  • 当てはまる範囲: 2026-09 時点の note の編集画面・当方の記事 14 本。画面が変われば変わります。
  • 言葉: 本文の「器」=点検や処理のための自作スクリプトのことです。

先に結論

リッチエディタ(contenteditable)を自動化するときの 3 原則(と、その前の第 0 段階):

  1. これから触るノードが本当に目的のノードか確かめる——対象段落の本文が期待したアンカー文字列と一致するか
  2. フォーカスを信用しない——クリックした位置とキャレットの位置が一致するとは限らない
  3. キャレット位置を自分で作る——Selection API で狙った段落の末尾に置いてから入力する
  4. 入力後の状態を確かめてから保存する——足した文が独立した段落として正確に 1 回だけ存在し、先頭の見出しが文字列そのまま変わっていないこと。保存は人が押す

input/textarea は、今回使ったブラウザ自動化の器では値の差し替えが返る入力手段(form_input)だけで触った。contenteditable には、対象ノードとキャレット位置を確定しないまま type/key を送らない(確定してからの Return と type は下の成功手順のとおり)。

リッチエディタを壊さない 3 原則。失敗 3 型(見出しに混入・段落が結合・リンクカード化)→ フォーカスを信用しない(第 0 段階=対象ノードの確認)・キャレット位置を作る・入力後の状態を確かめてから保存

失敗 3 型(note・ProseMirror・2026-09-15)

型 やったこと 起きたこと
① 先頭見出しに混入 末尾のリスト項目をクリック → End → Enter → type キャレットが文頭に飛び、先頭の見出しに 1 行が混入(文字数 3,488→3,575)。Ctrl+Z ×40 で戻し、「Leave site?」を破棄
② 段落が結合 2 段落にまたがる範囲を選択して Enter 選択範囲に段落の境界(\n\n)が含まれ、2 つの段落が 1 つに結合
③ リンクカード化 URL で終わる段落の末尾で Enter URL が埋め込みカードに変換され、テキストとして残らない

3 つの原因は少しずつ違います——①はキャレット位置、②は複数段落にまたがる選択範囲、③は URL 末尾で Enter したときのエディタ固有の変換。共通するのは入力位置・選択範囲・編集文脈を暗黙にエディタへ任せていたこと。もう 1 件、別のチャットアプリで検索欄をクリックしたつもりがメッセージ入力欄に文字を打っていた(投稿は確認できず、空にして戻した)。これは第 0 段階=対象ノードの確認を飛ばした事故です。

通った手順(4 手・14/14)

以下は抜粋で、そのまま動くスクリプトではありません。⓪①④は page 側の JS、②③は自動化器からのキー送信(Return・type)で、ここではコメントで示しています。

// ⓪ 触るノードが目的のノードか確かめる(本文が期待したアンカーと一致しなければ止める)
const p = [...document.querySelectorAll('.ProseMirror p')].at(-2);
if (!p.textContent.includes(EXPECTED_ANCHOR)) throw new Error('対象が違う: ' + p.textContent.slice(0, 40));
const h2Before = document.querySelector('.ProseMirror h2')?.textContent;
// ① キャレットを、その段落 p の末尾に自分で置く(URL で終わる段落は避けて、その前の段落を選ぶ)
const range = document.createRange();
range.selectNodeContents(p); range.collapse(false);
const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range);
// ② Enter を送る → 段落数が増えたかを数える(今回の note では 1 回目の Return が反映されないケースがあったので、増えていなければもう 1 回)
// ③ 本文を type
// ④ 保存の前に確かめる: 足した文が独立段落として正確に 1 回だけ在るか・先頭の見出しが文字列そのまま h2Before と等しいか
//    const ps=[...document.querySelectorAll('.ProseMirror p')].filter(x=>x.textContent===ADDED_LINE); ps.length===1 && document.querySelector('.ProseMirror h2').textContent===h2Before
  • ①で「URL で終わる段落」を避けるのは型③のため。1 つ前の段落の末尾に置く。
  • ②の Return は、送ったあとに段落数を数える。増えていなければもう 1 回。増えたら type。
  • ④は JS で「足した文と完全一致する段落がちょうど 1 つ」「先頭 h2 の文字列が編集前と等しい」を見る(文字数だけだと、同じ長さの別文字列に壊れても緑になる)。合っていれば「公開に進む」を人が押す。

14 本で 14 本とも、足した行が独立段落・見出し無傷・URL がテキストのまま、を確認してから公開しました。

3 原則に戻す

失敗は note 固有の話に見えますが、直したのは note でなく手順でした。

  • フォーカスを信用しない——型①の原因。クリックは「そのあたり」にフォーカスを置くだけで、キャレットがどこにあるかは別。
  • キャレット位置を作る——Selection API で「この段落の末尾」と指定する。型②(段落境界を含む選択)も型③(URL 末尾)も、位置を自分で決めれば避けられる。
  • 入力後の状態を確かめてから保存する——1 手ごとに JS で見る。「打った」と「入った」は別。保存ボタンは人が押す(取り返しがつかない操作は自動化しない)。

順番にすると 対象確認 → キャレット作成 → 入力 → 事後検査 → 人が保存。

範囲と限界

  • 観測は note のエディタ(ProseMirror 実装)・Chrome 拡張・2026-09-15・「末尾に 1 行足す」操作だけ。他のエディタ(Notion・Slack 等)や他の操作には外挿しません。
  • 「1 回目の Return が反映されない」は今回の環境での観測で、一般則ではありません。だから「数える」を手順に入れています。

本家(検証の記録つき)

この記事の本家(検証環境・判定・証拠)は Sumitsuke Lab → 対象確認・キャレット作成・事後検査を分けたら 14/14(検証の記録つき)。

AI の利用について

自動操作と JS の下書きは AI(Claude Code)が行い、失敗 3 型の記録・手順の確定・公開の押下は人間が行いました。対象は自分の記事 14 本。他人の投稿・アカウントは触っていません。

受託でも同じ手順で読んでいます

「自動化したら別の場所に入った」——フォーカス・キャレット・保存前検査の 3 つに分けて見ます。テキストだけで受けています。
▶ Sumitsuke / 動かない箇所の点検と修理

次に読む: 入力後の状態を確かめる、を図の生成にも——目視で取り切れなかったはみ出し 3 件


この記事は Zenn にも同じ内容を掲載しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?