第24回 コードブロックにコピーボタンを追加―TOPの連載一覧もカード表示へ変更

記事本文のコードブロックへコピー操作を追加し、TOPページの「連載から読む」を3連載のカード表示へ変更しました。Clipboard APIのフォールバック、複数コードブロック、既存の連載カード再利用まで記録します。

執筆:ゆまラボ運営者

公開:更新:
第24回 コードブロックにコピーボタンを追加―TOPの連載一覧もカード表示へ変更

第23回では、カテゴリ・サブカテゴリと連載に固有の説明文を追加し、/series/の連載一覧をカード表示へ変更した。

そのあと記事本文を見返すと、技術記事でよく使うコードブロックにはまだコピー操作がない。コマンドや設定をそのまま使いたい場合でも、コードを選択してコピーする必要がある。

TOPページの「連載から読む」も、連載が3本になったことで表示を見直したくなった。/series/ではすでに説明文付きのカードを使っているが、TOPはタイトルと記事数だけの表示が残っていた。

今回はこの2点を変更する。

コードブロックは表示だけで、コピー操作がなかった#

第5回のテスト記事には、FrontmatterやMarkdown本文をまとめた長いコードブロックがある。

修正前のコードブロック。コピー操作は付いていない

既存のコードブロックにはシンタックスハイライトと横スクロールがあり、長い行も表示できる。

ただ、内容をそのまま使う場合はコードをドラッグして選択する必要がある。短いコマンドなら大きな問題にならないが、複数行の設定やMarkdownを丸ごとコピーしたいときは操作が増える。

同じ記事には、短いコードブロックが複数並ぶ箇所もある。

複数のコードブロックが並ぶ修正前の表示

今回の追加では、インラインコードには手を入れず、Markdownから生成された<pre><code>だけを対象にする。

Markdown生成後のpre codeへボタンを追加する#

現在の記事は、render(post)でMarkdownを描画し、ArticleLayout.astro.prose内へ出力している。

そのため、Markdownの記述方法を変えたり、記事ごとにコピーボタン用のHTMLを書いたりする必要はない。

今回はArticleLayout.astro側で、ページ内に出力された<pre><code>をJavaScriptから取得し、それぞれのコードブロックへボタンを追加する方法にした。

流れは次のようになる。

Markdown

Astroが <pre><code> を生成

ArticleLayoutのスクリプトで対象を取得

各コードブロックへ「コピー」ボタンを追加

記事本文のMarkdownはそのままなので、既存記事を一括修正する作業も発生しない。

Codexで生成HTMLを確認したところ、<pre><code>を含む39記事すべてに拡張用スクリプトが出力されている。確認に使った記事ではコードブロックが5個あり、実行時に5個のボタンが追加された。

Clipboard APIを使い、使えない場合も処理を残す#

コピー処理はnavigator.clipboard.writeText()を優先している。

ブラウザでClipboard APIを利用できる場合は、そのコードブロックの文字列をそのままクリップボードへ渡す。

一方、Clipboard APIを使えない場合にボタンが何もしなくなる構成にはせず、一時的なtextareaを使うフォールバックも追加した。

コピーを押す

navigator.clipboard.writeText()を試す

利用できない場合はtextareaを使う

新しいnpmパッケージは追加していない。Astroの記事レイアウトに小さなクライアントJavaScriptを加えるだけにしている。

JavaScript自体が動かない場合も、コード本文は従来どおりpreとして表示される。コピー操作がなくなるだけで、記事を読めなくなることはない。

コードブロック右上に「コピー」を表示する#

修正後は、コードブロックの右上に「コピー」ボタンが表示される。

コードブロック右上へコピーを追加した状態

既存の横スクロールは残している。

長いコードでは下部のスクロールバーを使って横へ移動でき、コピーボタンはコードブロックの枠内に配置される。コード表示そのものを作り直したわけではない。

ボタンにはaria-labelも設定した。キーボードから操作でき、状態変化を読み上げ側へ伝えるためのライブリージョンも追加している。

コピー後は「コピー済み」へ切り替える#

ボタンを押してコピーに成功すると、表示を「コピー済み」へ変更する。

コピー後に「コピー済み」と表示された状態

見た目だけを変えるのではなく、aria-labelも同じ状態に合わせて変更する。

コピー処理には成功時だけでなく失敗時の表示も追加している。今回のローカル確認ではコピーに成功し、実際にコード文字列がクリップボードへ入ることを確認した。

公開後はHTTPS環境でも同じ操作を確認する。Clipboard APIは実際のブラウザ環境にも影響されるため、公開ページでの確認は残しておく。

複数のコードブロックにも個別に付く#

1つの記事に複数のコードブロックがある場合も、それぞれにボタンを追加する。

2つのコードブロックへ個別にコピーボタンを追加した状態

画像では、

cd C:\000pj\astro\yumaru-site
npm run dev

のブロックと、

http://localhost:4321/blog/cloudflare-test/

のブロックに、それぞれ別の「コピー」が表示されている。

ボタンはページ全体に1つではなく、pre codeごとに追加される。どのコードをコピーするかを選ぶための別UIは必要ない。

第5回で行ったMarkdown記事のローカル確認については、当時の記事にも作業手順を残している。

第5回 Astroへテスト記事を投稿してCloudflareの自動デプロイを確認する

TOPの連載表示は3本になると情報が少なく見える#

もう1つ変更したのがTOPページの「連載から読む」。

修正前は、連載タイトルと記事数を縦に並べていた。

修正前のTOPページの連載表示

この時点では、

  • Astro+Cloudflareでサイトを作る
  • サイトを作ったら ― 運用・SEO・SNS
  • サイトを収益化する ― AdSense・広告運用

の3連載がある。

タイトルだけでも移動はできるが、初めてTOPを見た場合、それぞれの連載が何を扱うのかはタイトルから判断するしかない。

第23回で/series/には説明文・記事数・導線を持つカード表示を追加しているので、TOPだけ別の簡素な表示を残す必要もなくなっていた。

第23回 カテゴリと連載に説明文を追加―連載一覧もカード表示へ変更

/series/のカードをTOPでも再利用する#

TOP用に別のカードを新しく作るのではなく、既存のTaxonomyBrowse.astroを再利用した。

TOPページ側では既存コンポーネントへseriesCardsを指定するだけにし、連載タイトル、説明文、記事数はこれまでのtaxonomy・記事集計から取得する。

記事数を、

全24記事
全4記事
全2記事

のようにHTMLへ手入力してはいない。記事が増えれば既存の集計結果から表示件数も変わる。

/series/側で使っているカード表示も維持している。

TOPでは周囲の見出し階層に合わせてカードタイトルをh3にし、/series/側の既存構造へ不要な影響を出さないようにした。

TOPでは3列、狭い画面では1列にする#

修正後の「連載から読む」は3枚のカードを横に並べる。

TOPページの連載から読むを3列カードへ変更した状態

各カードには次を表示している。

  • 連載タイトル
  • 連載の説明
  • 全○記事
  • この連載を読む →

1本目と2本目は第23回で追加した説明をそのまま利用し、3本目の「サイトを収益化する ― AdSense・広告運用」も同じ構造で表示されている。

PCでは3列。

狭い画面では1列へ切り替える。Codexでは390px幅相当のiframeを使い、カードが1列になることと、カード自体に横スクロールが発生しないことを確認した。

TOP専用に大きな装飾は追加していない。白背景、薄いborder、既存の余白とリンク表示を使い、/series/と近い見え方を維持している。

変更したファイルは3つ#

今回変更したファイルは次の3件。

src/layouts/ArticleLayout.astro
src/components/TaxonomyBrowse.astro
src/pages/index.astro

ArticleLayout.astroには、

  • pre codeへのコピーボタン追加
  • Clipboard APIによるコピー
  • textareaを使ったフォールバック
  • 「コピー」「コピー済み」などの状態表示
  • aria-label
  • ライブリージョン
  • ボタン配置用CSS

を追加した。

TaxonomyBrowse.astroでは、TOPで連載カードを表示するときの見出し階層とレイアウトを調整した。

index.astroでは、新しいカードを直接記述せず、既存コンポーネントへseriesCardsを指定している。

package.jsonとnpm依存関係は変更していない。

ビルドと表示を確認する#

変更後に実行したのは次のコマンド。

npm run build
node scripts/verify.mjs
git diff --check

npm run buildは成功。

158ページを生成し、Pagefindの索引作成とsitemap出力まで完了した。

生成HTML、ローカル静的プレビュー、ヘッドレスChromeでも確認している。

コードブロックについては、

  • pre codeを含む39記事へスクリプトが出力される
  • 確認記事の5コードブロックに5ボタンが追加される
  • コピー後に「コピー済み」へ変わる
  • 長いコードでも既存の横スクロールが残る
  • ボタンがコードブロックの枠内に収まる

ことを確認した。

TOPでは、

  • 3つの連載カードが表示される
  • タイトル、説明、記事数、リンクが出る
  • 広い画面では3列
  • 390px相当では1列
  • カードで横スクロールが発生しない

ことを確認した。

今回の変更に起因するビルドエラーは発生していない。既存記事や同時作業中の記事に由来する未登録タグの警告、Pagefindの日本語ステミング通知、Nodeの実験的機能通知はそのまま残している。

コピーボタンと連載カードの変更点#

今回は記事本文とTOPページの2か所を変更した。

  • Markdownから生成されたコードブロックへ「コピー」を追加
  • コピー成功後は「コピー済み」へ変更
  • Clipboard APIが使えない場合のフォールバックを追加
  • 1記事内の複数コードブロックへ個別にボタンを追加
  • 既存の横スクロール表示を維持
  • TOPの「連載から読む」を3列カードへ変更
  • /series/で使っているカード表示とtaxonomy集計を再利用
  • 連載の記事数を手入力せず既存の集計から表示

記事本文のMarkdownや既存URL、package.json、Cloudflare Workersの構成には変更を入れていない。