こちらの記事は 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
やったー!
効用
- 「コマンドはコピペして貼り付ける」のが暗黙のルールとなっている本番環境へのリリース作業において,逐一スニペットの中身をマウスで選択する手間が省けます.
- 自分の画面だけに実装されるので,他の人に自慢できます.
- フロント周りがちょっとだけ楽しくなります.