リサーチの「文脈消失」を技術で解決する
その「コピペ」に文脈はありますか?
──リサーチの「文脈消失」をDOM操作で解決した記録
1. イントロ:その「コピペ」に文脈はありますか?
リサーチ中、大量にコピペしたメモが数日後に「ただの文字列」になり、出典探しに時間を溶かす不毛さを感じたことはないでしょうか。
既存のクリップボード拡張機能は便利ですが、「URLと前後関係(文脈)」をセットで保持できないという限界がありました。
「あれ、コピペした覚えはあるんだけど、どこのサイトを見て、このコードを使おうと思ったんだっけ?」とアクセス履歴を漁るも数分以上かかるという始末。
私はシステムエンジニアとして、この課題を放置できませんでした。
「なければ作る」の精神で、ブラウザのDOM操作をハックして解決したのが、今回紹介するClipContextです。
最初にお断りしておきますが、これは万人受けを狙った汎用ツールではなく、私自身が一番のユーザーであり、本気で欲しかったものを形にしたプロダクトです。
2. なぜChrome拡張機能なのか
自身のスキルセットであるJavaScriptをベースに、リサーチの手を止めない軽量で高速な動作を最優先しました。
Manifest V3に準拠した設計を行い、ブラウザのランタイムを最大限に活用しています。
ストレージ戦略とプライバシー
ユーザー(および自分自身)のプライバシーを考慮し、外部DBは一切使用していません。
データは chrome.storage を利用したローカル完結型を選択しました。
また、機密情報を扱う可能性を考慮し、AES-GCM暗号化によるデータ保護を実装しています。
3. 技術的チャレンジ:DOMから「文脈」を切り出す
Selection APIの活用
単に選択範囲のテキストを取得するだけでなく、Selection APIを用いてDOMツリーを遡り、その親ノードや兄弟ノードから「前後150文字」を動的に抽出するロジックを組みました。
これにより、コピーした瞬間にその情報がどのような文脈で語られていたかを自動的に保存します。
周辺テキストの動的抽出
周辺テキストを取得する際のポイントは、ユーザーが選択した Range オブジェクトを起点に、親要素(Container)のテキスト全体から選択範囲のオフセットを計算することです。
/**
* 選択範囲とその前後150文字を抽出するコアロジック
*/
function getContextText(selection) {
const range = selection.getRangeAt(0);
const container = range.commonAncestorContainer;
// 親要素がテキストノードの場合はその親(Element)を取得
const parentElement = container.nodeType === Node.TEXT_NODE
? container.parentElement
: container;
// 親要素内の全テキストを取得
const fullText = parentElement.innerText || parentElement.textContent;
// 選択範囲のテキストを取得
const selectedText = selection.toString();
// 親要素内での選択テキストの開始位置を特定
const startOffset = fullText.indexOf(selectedText);
if (startOffset === -1) return selectedText; // 取得失敗時は選択範囲のみ
// 前後150文字の範囲を計算(境界値チェック付き)
const contextBefore = fullText.substring(Math.max(0, startOffset - 150), startOffset);
const contextAfter = fullText.substring(
startOffset + selectedText.length,
Math.min(fullText.length, startOffset + selectedText.length + 150)
);
return {
before: contextBefore,
selected: selectedText,
after: contextAfter
};
}
commonAncestorContainer の活用:
選択範囲が複数のノードにまたがっていても、それらを包摂する最小の親要素を特定することで、文脈の連続性を保っています。
innerText vs textContent:
innerText を優先することで、CSSで非表示になっているテキスト(スクリプトやスタイルなど)を除外し、ユーザーが実際に目で「読んでいる」状態に近いテキストを抽出しています。
境界値のハンドリング:
substring の引数がマイナスになったり、文字列長を超えたりしないよう、Math.max と Math.min で安全にガードしています。
PDFへの対応
HTMLとはDOM構造が根本的に異なるPDF(ブラウザ内表示)への対応は、一つの大きな壁でした。
PDF.js等の挙動を解析し、テキストとメタデータを一貫した形式で取得するためのレイヤーを実装しています。
パフォーマンスへの配慮
全てのコピーイベントを監視する仕組み上、メインスレッドをブロックしてブラウジング体験を損なうことは許されません。
非同期処理を最適化し、ユーザーが「ツールが動いている」ことを意識させないレベルの低遅延を実現しました。
4. マネジメント視点での「引き算」
UI/UX:究極の自動化
マネジメント業務においても「時間は有限」です。
リサーチの集中力を削がないよう、あえて「操作不要(コピーするだけ)」という自動化に徹底的にこだわりました。
「保存するための操作」をユーザーに強いないことが、ツール継続利用の鍵だと考えています。
拡張性のある機能設計
リサーチャーの多様な出力ニーズに合わせ、3パターンのコピー形式を用意しました。
- テキストのみ: 純粋な引用。
- テキスト + URL: 参照リスト用。
- フル情報: 文脈・タイトル・URLを含む完全な記録。
導入後の変化
このツールを導入してから、自分のリサーチワークフローから「出典を探し直す」という時間が物理的に消失しました。
記憶に頼らず記録に任せることで、よりクリエイティブな思考にリソースを割けるようになっています。
まとめ:エンジニアリングで日常を「ハック」する
小さなツール開発ではありましたが、ブラウザAPIの深い理解、さらにはユーザーの行動導線を考慮した設計など、多くの知見を得ることができました。
今後の展望
今後は、AI(LLM)と連携し、保存した文脈から自動的に要約やタグ付けを行う機能などのロードマップを検討しています。
日々の小さな「不便」をエンジニアリングでハックする楽しさが、この記事を通して少しでも伝われば幸いです。
リンク
ClipContext - Chrome ウェブストア
詳細な使い方・LP
著者プロフィール
成瀬 賢一(Naruse Kenichi)
フロントエンドエンジニア兼システムエンジニア兼マネジメント職。
JavaScript, GAS, React Nativeを用いた開発を得意とし、現在は「知的生産性の極大化」をテーマに様々なツールを自作中。
趣味はダーツ(世界一(!?)を目指して奮闘中)。
Discussion