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;
}