こちらの記事は free_dev_com Advent Calendar 2019 - Adventar 7 日目の記事です。
User JavaScript and CSS とは
Chrome の拡張機能として提供されている,既存のウェブサイトに独自の Javascript や CSS を埋め込むライブラリです.
User JavaScript and CSS - Chrome ウェブストア
これを利用して,ダークモードがないウェブサイトにダークモードを実装してみたり, Yahoo! のサイトに自分の好きなキャラクターのイラストを表示させたり,いろいろなことができます.
今回は,社内のタスク管理に使用している Redmine で,チケットの説明に出てくるスニペットの内容をクリップボードにコピーする機能を Javascript で実装してみました.
画面設計
スニペットの枠外,右下部分にコピーのアイコンを配置し,
アイコンをクリックすると「コピーしました!」のダイアログを表示することにします.
実装
アイコンを読み込む
今回は Font Awesome のアイコンを使用します.
Font Awesome についてはこちら
流れは次の通りです.
- Font Awesome を読み込む link タグを作成する
- head タグを取得し,その子要素として先ほど作成した link タグを追加する
const link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', 'https://use.fontawesome.com/releases/v5.6.3/css/all.css');
document.getElementsByTagName('head')[0].appendChild(link);
スニペットの枠外,右下部分に要素を配置する
流れは次の通りです.
スニペット (pre タグ) を取得する -> 各スニペットが配列で取れます
各スニペットに対して,次の処理を行う
- コピーボタンを配置するための p タグを作成する (snippet)
- 後にコピーボタンとなる span タグを作成する (copyButton)
- (copyButton に title, innerHTML, className などの属性を設定する)
- copyButton にクリックのイベントリスナーを付与する (内容は下記)
- コピー対象となる textarea タグを作成 (textarea タグでなくても行けるかもしれません)
- スニペット内の文章を textarea タグに設定する
- 作成した textarea タグを body タグの子要素として配置 -> 選択 -> コピー
- textarea タグは不要になるので取り除く
- コピーが完了した旨のメッセージを表示
 
- copyButton を snippet の直後に配置する
// スニペットのタグを選択
const snippets = document.getElementsByTagName('pre');
// console.log(snippets); // HTMLCollection
// console.log(typeof snippets); // object
// コピーボタンを配置
Object.keys(snippets).forEach(function (index) {
    let snippet = snippets[index];
    let p = document.createElement('p');
    p.className = 'copy-snippet';
    let copyButton = document.createElement('span');
    copyButton.title = 'Copy to Clipboard';
    copyButton.innerHTML = '<i class="far fa-copy"></i> Copy';
    copyButton.className = 'copy-button';
    copyButton.addEventListener('click', function () {
        let copyTarget = document.createElement('textarea');
        copyTarget.textContent = snippet.innerText.trim(); // 見た目に合わせて文末改行削る?
        let body = document.getElementsByTagName('body')[0];
        body.appendChild(copyTarget);
        copyTarget.select();
        document.execCommand("copy");
        body.removeChild(copyTarget);
        // 後処理まで終わったらおまけ
        (function () {
            alert('コピーしました!');
        })();
    });
    p.appendChild(copyButton);
    snippet.parentNode.insertBefore(p, snippet.nextSibling);
});
その他
画面右下にカエルのイラストを入れて,全体を CSS で整えます.
// カエルがペコペコおじぎする GIF
const imageUrl = 'https://sozai-good.com/download?id=2451&type=7&subnumber=0&extention=gif';
// 画像挿入処理
let img = document.createElement('img');
img.id = 'inserted-image';
img.height = 105;
img.src = imageUrl;
const wrapper = document.getElementById('wrapper');
wrapper.parentNode.insertBefore(img, wrapper.nextSibling);
# wrapper, #main {
    background-image: url("https://www.pakutaso.com/shared/img/thumb/mizuho1102dssd_TP_V.jpg");
    background-repeat: no-repeat;
    background-size: cover;
    background-attachment: fixed;
    opacity: 0.96;
}
# top-menu {
    /*background-color: darkgreen;*/
    background-color: rgba(10, 106, 10, 0.9);
}
# header, #footer {
    /*background-color: forestgreen;*/
    background-color: rgba(0, 0, 0, 0.5);
}
# main-menu li a {
    /*background-color: darkgreen;*/
    background-color: rgba(10, 106, 10, 0.6);
}
# main-menu li .selected {
    color: lightgreen;
}
# content {
    margin-bottom: 20px;
}
# sidebar {
    background-color: white;
}
# footer {
    position: fixed;
    background-color: black;
    right: 0;
    bottom: 0;
}
pre, .CodeRay {
    font-family: Monaco, monospace;
}
# inserted-image {
    position: fixed;
    right: 25px;
    bottom: 45px;
}
.copy-snippet {
    text-align: right;
}
.copy-button {
    cursor: pointer;
    background: none;
    border: 0;
    /* settings when hovered */
    /*transition: color 0.1s linear;*/
}
.copy-button:hover {
    color: red;
}
Redmine の公式ページを表示してみる
Defect #32525: CSV related tests fail with Rails 5.2.4 - Redmine
やったー!
効用
- 「コマンドはコピペして貼り付ける」のが暗黙のルールとなっている本番環境へのリリース作業において,逐一スニペットの中身をマウスで選択する手間が省けます.
- 自分の画面だけに実装されるので,他の人に自慢できます.
- フロント周りがちょっとだけ楽しくなります.


