【WordPress】コード表示にコピーボタンを付ける

WordPress

WordPressの記事において、(Cocoonに親和性の高い方法で)コード表示に対してコピーボタンを付加する方法を紹介します。

「結論だけ知りたい」という人は、ここにジャンプしてください!

 

 

ことのはじまり

コード表示とは?

ここで言う「コード表示」とは、以下のようなものを指しています。

int main()
{
  printf("aaa");
  return 0;
}

所謂、「記事内でコードを紹介したりするためにベタにコードを貼り付けたように表示」することの意です。

 

コピーボタン付加とは?(やりたかったこと)

コード表示をサポートするプラグインなどにもよく実装されているように、記事内にコード表示を行った場合に、閲覧者がワンクリックでコードをクリップボードにコピー(クリップ)できる機能を付けたくなりました。

↓これ(記事中のコード表示の右上角)の「コピーアイコンをクリックしている操作」のようなもの。

 

検討フェーズ

実現方法導出までの検討フェーズの情報です。

 

参考にしたサイト

コピーボタン付加

Cocoonがhighlight.jsを使っているたこともあり、以下の先達の記事を参考にさせていただきました。
ですが、結果的にhighlight.jsに非依存な実装にしました。

 

 

Javascript実装ノウハウ

Javascriptを実装するにあたって、以下の先達の記事を参考にさせていただきました。

 

 

自身の環境

筆者は、WordPressのテーマCocoonに実装されている「コード」の設定(シンタックスハイライトなどが利用できる)を使っています。WordPressの記事をエディタで編集する際には、preタグとcodeタグを使って、コードブロック(wp-block-code)のクラスを適用して記述します。

↓こんな感じ

<pre class="wp-block-code"><code>ここに表示したいコードを書く</code></pre>

また、Cocoonではhighlight.jsを使ったシンタックスハイライトが可能であり、筆者はこの機能を利用しています。

 

こだわったところ

さらに、以下をこだわりました。

  • プラグインやJSライブラリ非依存
    • プラグインやJSライブラリの機能を使ってコピーボタンを付加するのは導入障壁が低く素敵なのですが、将来的にそれらを使わなくなったときに、再度「コピーボタンをどうやって付けるか」を考える必要があるため、これは避けたいなと思いました。
  • 横スクロールバー利用時の位置追従
    • 主にレスポンシブ対応のため、自身のこだわりでコード表示の横スクロールバーを必須としているのですが、テストしていたら、横スクロール時にコピーボタンもスクロールしてしまうという問題が生じました。
    • この解決には苦労しました。落としどころは1つではないと思いますが、若干力わざな位置追従という形に落ち着きました。
    • 本意ではないですが、Javascriptを使ったのでシンプルな実装にはなりませんでした。

 

ポイント

今回の目的実現におけるポイントは以下の通りです。

  • preタグとcodeタグで記述するコード表示への適用を目的とする(Cocoonとの親和性担保)。
  • プラグインやJSライブラリに非依存。
  • WordPressのUIから容易にコード追加が可能(今回はCSSとPHP(中身はほぼJS)だけで実装)。
  • 言語を勉強する機会とする。

 

結論(コピーボタン付加の実現方法と表示結果)

なんとか所望のコピーボタン付加ができました。詳細を以下に記します。
Cocoonでなくても、preとcodeを使って同じ所作でコード表示するなら、そのまま利用可能のはずです。

 

前提

WordPressの「Cocoon設定」の「コード」タブで以下を設定しています。

  • ハイライト表示
    • 「ソースコードをハイライト表示」をチェック

 

実現方法

以下のCSSとPHPを追加しました。

 

CSS

/*----- Copy Button -----*/
.lvg-code-copybtn {
  position: absolute;
  margin: 4px;
  top: 0px;
  right: 0px;
  padding: 0px 6px 0px;
  border: none;
  border-radius: 20%;
  font-size: 18px;
  color: #ccc;
  background-color: #000;
  opacity: 0.9;
  transition: 0.3s;
}
.lvg-code-copybtn:hover {
  cursor: pointer;
  color: #fff;
  opacity: 1;
}
.lvg-code-copybtn:hover:fucus {
  background-color: #ccc;
}
.lvg-code-copybtn:hover:active {
  background-color: #ccc;
}
.lvg-code-copybtn::before {
  font-family: "Font Awesome 5 Free";
  content: "\f0c5";
  background-color: transparent;
}

/*----- Copy Button Notice -----*/
.lvg-code-copybtn-notice {
  position: absolute;
  visibility: hidden;
  pointer-events: none;
  z-index: 1;
  top: 5px;
  right: calc(100% + 4px);
  padding: 2px 6px 0px;
  border-radius: 3px;
  font-weight: bold;
  font-size: 13px;
  color: #000;
  background: #fff;
}
.lvg-code-copybtn-notice-after {
  visibility: visible;
}

/*=== [OPTION] for RWD ===*/
@media screen and (max-width: 1240px) {
  /*----- Copy Button -----*/
  .lvg-code-copybtn {
    margin: 3px;
    padding: 0px 6px 0px;
    font-size: 14px;}
  /*--- Copy Button notice ---*/
  .lvg-code-copybtn-notice {
    top: 1px;
    right: calc(100% + 2px);
    padding: 2px 6px 0px;
    border-radius: 3px;
    font-size: 11px;
  }
}

 

WordPressにおける該CSSの追加先は、以下のどれでもよいです。筆者はstyle.cssに追記しました。

  • 「外観」>「テーマファイルエディター」の「Cocoon Child: スタイルシート (style.css)」
  • 「外観」>「カスタマイズ」>「追加CSS」
  • エディターにおける「カスタムCSS」(対象の記事にのみ有効)

 

PHP

PHPといっても処理はほぼJavascriptです。

<b:if cond='data:view.isSingleItem'><!-- for posts and pages -->
<script type='text/javascript'>
(function() {
  //--- Define constants
  const TAG_CODE = 'pre';
  const TEXT_ALERT_FAILED = 'Copy failed.';

  const TITLE_BUTTON = 'Copy to clipboard';
  const CLASS_BUTTON = 'lvg-code-copybtn';
  const TIME_BUTTON_SCROLL_TIMEOUT = 500;

  const TEXT_NOTICE_BEFORE = '';
  const TEXT_NOTICE_AFTER = 'Copied!';
  const CLASS_NOTICE = 'lvg-code-copybtn-notice';
  const CLASS_NOTICE_AFTER = 'lvg-code-copybtn-notice-after';
  const TIME_NOTICE_TIMEOUT = 1300;

  //--- Define button element
  let btn = document.createElement('button');
  btn.className = CLASS_BUTTON;
  btn.setAttribute('title', TITLE_BUTTON);

  //--- Define notice element
  let ntc = document.createElement('span');
  ntc.className = CLASS_NOTICE;
  ntc.textContent = TEXT_NOTICE_BEFORE;

  //--- Set eventlistener to all code tags
  let nodes = document.getElementsByTagName(TAG_CODE);
  let btns = new Array(nodes.length);
  let timeoutids = new Array(nodes.length);

  for(let i = 0; i < nodes.length; i++){
    btns[i] = btn.cloneNode(true);

    //--- Add eventlistener (click) to copy button
    btns[i].addEventListener('click', () => {
        let sel = window.getSelection();
        let nod = btns[i].parentNode;
        sel.selectAllChildren(nod);
        sel.extend(nod, nod.childNodes.length - 1);
        if(navigator.clipboard){
          navigator
            .clipboard
            .writeText(sel)
            .then(() => {
                sel.removeAllRanges();
                ntc.textContent = TEXT_NOTICE_AFTER;
                ntc.classList.add(CLASS_NOTICE_AFTER);
                setTimeout(() => {
                    ntc.classList.remove(CLASS_NOTICE_AFTER);
                    ntc.textContent = TEXT_NOTICE_BEFORE;
                  }, TIME_NOTICE_TIMEOUT
                );
              }
            )
            .catch(() => {
                alert(TEXT_ALERT_FAILED);
              }
            );
        }
        else{//for older browser
          document.execCommand('copy');
        }
      }
    );

    //--- Add eventlistener (mouseenter) to code node
    nodes[i].addEventListener('mouseenter', () => {
        nodes[i].appendChild(btns[i]);
        btns[i].appendChild(ntc);
      }
    );

    //--- Add eventlistener (mouseleave) to code node
    nodes[i].addEventListener('mouseleave', () => {
        btns[i].remove();
      }
    );

    //--- Add eventlistener (scroll) to code node
    nodes[i].addEventListener('scroll', () => {
        btns[i].style.visibility = 'hidden';
        if(null != timeoutids[i]){
          clearTimeout(timeoutids[i]);
        }
        timeoutids[i] = setTimeout(() => {
            btns[i].style.visibility = 'visible';
          }, TIME_BUTTON_SCROLL_TIMEOUT
        );

        let x = nodes[i].scrollLeft;
        btns[i].style.right = - x + 'px';
      }
    );
  }
}());
</script>
</b:if>

 

WordPressにおける該PHPの追加先は、(フッター付近でないとうまくいかない旨の情報を見かけましたので)筆者はfooter-insert.phpへの追記としました。

  • 「外観」>「テーマファイルエディター」の「Cocoon Child: footer-insert.php (tmp-user/footer-insert.php)」

 

表示結果

記事中に所望のコード表示の記述を書くと、以下のようにコピーボタンが付加されます(コード表示の右上角あたり)。
なお、これは画像です(本記事投稿後に設定やCSSを変更する可能性があるので)。

 

以下にて、実際のコード表示も掲載しておきます。設定が変わっていなければ、上記画像と同じものが実際のコード表示において試していただけるかもしれません。

int main()
{
  printf("aaa");
  return 0;
}

 

タイトルとURLをコピーしました