画像ボタンのファイルピッカーを画像・動画に限定し、モバイルネイティブピッカーを活性化

basecamp/lexxy

ツールバーの画像ボタン(name="image")に accept="image/*,video/*" を設定することで、iOS・Androidのネイティブフォト/ビデオピッカーが起動するようになりました。ファイルボタン(name="file")は従来どおり制限なしのファイルピッカーを開きます。

背景

変更前は、画像ボタンとファイルボタンの両方が同一の uploadAttachments コマンドを呼び出しており、どちらも accept 属性のない汎用ファイルピッカーを開く動作でした。モバイルブラウザでは accept 属性の有無によってUIが大きく異なり、accept="image/*,video/*" を指定するとOSのネイティブ写真・動画ライブラリが直接開きます。画像アップロード用のボタンが汎用ファイルブラウザを開くことは、モバイルユーザーにとって直感的ではありませんでした。

この差異を解消するために、画像ボタンとファイルボタンのコマンドが分離され、それぞれの用途に適したファイルピッカーが起動するよう変更されました。

技術的な変更

CommandDispatcher のリファクタリングにより、単一だった uploadAttachments コマンドが uploadImageuploadFile の2つに分離されました。共通のファイル入力生成ロジックはプライベートメソッド #dispatchUploadAttachment に集約されています。

変更前:

dispatchUploadAttachments() {
  const input = createElement("input", {
    type: "file",
    multiple: true,
    style: "display: none;",
    onchange: ({ target: { files } }) => {
      this.contents.uploadFiles(files, { selectLast: true })
    }
  })
  // ...
}

変更後:

dispatchUploadImage() {
  this.#dispatchUploadAttachment("image/*,video/*")
}

dispatchUploadFile() {
  this.#dispatchUploadAttachment()
}

#dispatchUploadAttachment(accept = null) {
  const attributes = {
    type: "file",
    multiple: true,
    style: "display: none;",
    onchange: ({ target: { files } }) => {
      this.contents.uploadFiles(files, { selectLast: true })
    }
  }

  if (accept) attributes.accept = accept

  const input = createElement("input", attributes)
  // ...
}

acceptnull の場合は属性自体を設定しないため、ファイルボタンの動作は変更前と完全に同一です。ツールバーの data-command 属性も対応して更新されており、画像ボタンは uploadImage、ファイルボタンは uploadFile を参照します。なお、画像ボタンの title"Add images" から "Add images and video" に更新され、動画も受け付けることが明示されました。

設計判断

共通ロジックをプライベートメソッドに委譲する設計 が採用されました。uploadImageuploadFile はそれぞれ1行で #dispatchUploadAttachment に処理を委ねており、accept 値の違いだけで振る舞いを切り替えています。これにより、ファイル入力の生成・DOM操作・イベントハンドリングといったコアロジックの重複を避けつつ、各コマンドのインターフェースをシンプルに保っています。

テスト側では、EditorHandle#uploadFile{ via: "image" } または { via: "file" } オプションが追加され、どちらのボタンを経由するかを選択できるようになりました。既存のブラウザテストは via: "file" に更新されており、PDFやテキストファイルのアップロードテストが引き続き制限なしのファイルボタンを使用することが明示されています。また、accept 属性の有無を検証する新テストがそれぞれ追加されており、モバイルピッカーの動作仕様がテストとして文書化されています。

まとめ

本PRは、単一コマンドの分離と accept 属性の追加という最小限の変更で、モバイルユーザーの操作体験を改善した変更です。プライベートメソッドへの委譲パターンにより後方互換性を維持しながら、画像・動画アップロードとファイルアップロードの意図的な使い分けをコードレベルで明確に表現しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
a6a1921b

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→セクション群(各論)→まとめ(結論)の3部構成が明確に適用されており、必須要素(背景、技術的変更)と任意要素(設計判断)がすべて含まれています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```javascript:src/editor/command_dispatcher.js)とGitHubのPRリンク記法([PR #907](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

CommandDispatcherのリファクタリングや`accept`属性の挙動など、専門知識を持つエンジニアを対象とした適切な技術レベルで記述されています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られています。可読性が非常に高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロック(変更前後の`dispatchUploadAttachments`関連メソッド)は、提供されたDiffの内容と正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`accept`属性、`CommandDispatcher`、プライベートメソッドなどの技術用語が、PR情報と整合性が取れており、文脈上も正しく使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

コマンドの分離、共通ロジックの集約、テストコードの変更点など、技術的な変更内容に関する説明はすべてDiffの内容に裏付けられており、論理的かつ正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(コマンド分離、accept属性の追加、テストの変更など)は、PRのTitle, Description, Diffの内容によって裏付けられており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#907)、メソッド名、属性値(`image/*,video/*`)などの固有名詞や値はすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRのタイトル「Restrict image button file picker to images and videos」の内容を的確に要約し、その影響(ネイティブピッカーの活性化)まで含んでおり、非常に適切です。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

「iOS・Androidのネイティブピッカー」という言及はPR Descriptionに記載されており、PR情報に基づかない外部知識の追加はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「〜ようになりました」「変更前は〜でした」といった時間表現は、PRによる変更の前後関係を正確に反映しています。