概要

性質上、RedmineのチケットやWikiにコードを貼り付ける機会は多々あると思います。

と、コードで囲まれている部分の脇にコピーボタンをつけて、コピペの手間を軽減します。

確認した環境

Redmine 5.1

さっくりとした手順

  1. 設定ファイル(4個分)のバックアップを取ります。
  2. パッチファイル(4個分)を作成します。
  3. 4つのファイルに対してパッチを適用します。
  4. Webサービスを再起動します。

ファイルのバックアップ

  • app/views/journals/new.js.erb
sudo cp -pi /redmine/root/directory/app/views/journals/new.js.erb /path/to/backup/directory/new.js.erb.$(date +%Y%m%d)
  • app/views/journals/update.js.erb
sudo cp -pi /redmine/root/directory/app/views/journals/update.js.erb /path/to/backup/directory/update.js.erb.$(date +%Y%m%d)
  • public/javascripts/application.js
sudo cp -pi /redmine/root/directory/public/javascripts/application.js /path/to/backup/directory/application.js.erb.$(date +%Y%m%d)
  • public/stylesheets/application.css
sudo cp -pi /redmine/root/directory/public/stylesheets/application.css /path/to/backup/directory/application.css.erb.$(date +%Y%m%d)

/redmine/root/directoryは、自分の環境に合わせます。(var/lib/redmineなど)

また、/path/to/backup/directtoryは任意のバックアップディレクトリを指定します。

ファイルのバックアップ確認

  • app/views/journals/new.js.erb
diff -u /path/to/backup/directory/new.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/new.js.erb
  • app/views/journals/update.js.erb
diff -u /path/to/backup/directory/update.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/update.js.erb
  • public/javascripts/application.js
diff -u /path/to/backup/directory/application.js.erb.$(date +%Y%m%d) /redmine/root/directory/public/javascripts/application.js
  • public/stylesheets/application.css
diff -u /path/to/backup/directory/application.css.erb.$(date +%Y%m%d) /redmine/root/directory/public/stylesheets/application.css

それぞれ、バックアップしたファイル → バックアップ元ファイルで差分(diff)を取り、エラーがないこと(差分が無いこと)でバックアップを確認します。

パッチファイル作成

cd /hoge && pwd

任意の作業ディレクトリに移動します。

  • new.js.erb.patch
tee new.js.erb.patch > /dev/null << 'EOF'
--- new.js.erb
+++ new.js.erb.patch
@@ -11,3 +11,4 @@
 $('#issue_private_notes').prop('checked', true);
 <% end %>

+addCopyButtonToPreTag();
EOF
  • update.js.erb.patch
tee update.js.erb.patch > /dev/null << 'EOF'
--- update.js.erb
+++ update.js.erb.patch
@@ -7,6 +7,7 @@
   $("#journal-<%= @journal.id %>-notes").replaceWith('<%= escape_javascript(render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))) %>');
   $("#journal-<%= @journal.id %>-notes").show();
   $("#journal-<%= @journal.id %>-form").remove();
+  addCopyButtonToPreTag();
   var journal_header = $("#change-<%= @journal.id %>>div.note>h4.note-header");
   var journal_updated_info = journal_header.find("span.update-info");
   if (journal_updated_info.length > 0) {
EOF
  • application.js.patch
tee application.js.patch > /dev/null << 'EOF'
--- application.js
+++ application.js.patch
@@ -1123,6 +1123,49 @@
   });
 });

+function addCopyButtonToPreTag() {
+  $('.wiki .copy-contents').remove();
+  var copyContents =
+    $("<div class='copy-contents'>").append(
+      $("<button class='code-copy-button' title='Copy' onclick='copyText($(this).parent().next(\"pre\"), $(this).parent());'>")
+      .append("<span class='icon-only icon-copy'>")
+    );
+  $('.wiki pre').before(copyContents);
+  $('.code-copy-button[title]').tooltip({
+      show: {
+        delay: 400
+      },
+      position: {
+        my: "center bottom-5",
+        at: "center top"
+      }
+  });
+}
+function copyText(target, copyEl) {
+  // Selecting strings in 2 ways for cross-browser support
+  // 1. Use select();
+  copyEl.append("<textarea class='tmp'>");
+  var tmp = copyEl.find('.tmp');
+  tmp.val(target.text());
+  tmp.select();
+  // 2. Use createRange();
+  var range = document.createRange();
+  range.selectNode(target[0]);
+  window.getSelection().removeAllRanges();
+  window.getSelection().addRange(range);
+  // Copy and Cleanup
+  var copied = document.execCommand('copy');
+  window.getSelection().removeAllRanges();
+  tmp.remove();
+  // Show copied messages
+  if (copied){
+    copyEl.append("<div class='copied-message'>Copied.</div>");
+    var copiedMessage = copyEl.find('.copied-message');
+    copiedMessage.show();
+    copiedMessage.fadeOut('slow', function() { $(this).remove(); });
+  }
+}
+
 function inlineAutoComplete(element) {
     'use strict';

@@ -1241,6 +1284,7 @@
 $(document).ready(setupAttachmentDetail);
 $(document).ready(setupTabs);
 $(document).ready(setupFilePreviewNavigation);
+$(document).ready(addCopyButtonToPreTag);
 $(document).ready(setupWikiTableSortableHeader);
 $(document).on('focus', '[data-auto-complete=true]', function(event) {
   inlineAutoComplete(event.target);
EOF
  • application.css.patch
tee application.css.patch > /dev/null << 'EOF'
--- _old/application.css
+++ application.css.patch
@@ -449,6 +449,30 @@
   height: initial;
 }

+.copy-contents {
+  position: relative;
+}
+.copy-contents .code-copy-button {
+  position: absolute;
+  display: flex;
+  right: -13px;
+  top: 0px;
+  border: none;
+  background-color: transparent;
+}
+.copy-contents .copied-message {
+  position: absolute;
+  display: flex;
+  right: -13px;
+  top: 20px;
+  display: none;
+  color:#505050;
+}
+.copy-contents .tmp {
+  position: fixed;
+  left: 200%;
+}
+
EOF

パッチファイルの所有権変更

  • 変更前確認
ls -l *.patch

上記、作成した4つのパッチがあり、作業アカウントが所有者になっていることを確認

  • 所有者変更
sudo chown www-data:www-data *.patch

→ redmineの実行ユーザを指定します。

  • 変更後確認
ls -l *.patch

4つのパッチの所有者がredminの実行ユーザーになっていることを確認

パッチ適用

  • app/views/journals/new.js.erb にパッチ適用
sudo -u www-data patch /redmine/root/directory/app/views/journals/new.js.erb < /hoge/new.js.erb.patch
  • /app/views/journals/update.js.erb にパッチ適用
sudo -u www-data patch /redmine/root/directory/app/views/journals/update.js.erb < /hoge/update.js.erb.patch
  • public/javascripts/application.js にパッチ適用
sudo -u www-data patch /redmine/root/directory/public/javascripts/application.js < /hoge/application.js.patch
  • public/stylesheets/application.css にパッチ適用
sudo -u www-data patch /redmine/root/directory/public/stylesheets/application.css < /hoge/application.css.patch

それぞれ、ファイルがある箇所を絶対パスで実行してください。特に、application.jsとapplication.cssの取り違えに注意ください。

パッチが上手くいかないときは?

エディタなどを利用して、

https://www.redmine.org/attachments/25075

の内容を、追記してください。

diffによるパッチ適用確認

  • app/views/journals/new.js.erb
diff -u /path/to/backup/directory/new.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/new.js.erb
  • app/views/journals/update.js.erb
diff -u /path/to/backup/directory/update.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/update.js.erb
  • public/javascripts/application.js
diff -u /path/to/backup/directory/application.js.erb.$(date +%Y%m%d) /redmine/root/directory/public/javascripts/application.js
  • public/stylesheets/application.css
diff -u /path/to/backup/directory/application.css.erb.$(date +%Y%m%d) /redmine/root/directory/public/stylesheets/application.css

として、作成したパッチファイルと同じかを確認します。

設定反映

  • Webサービス再起動
sudo systemctl restart apache2.service
  • Webサービス再起動確認
systemctl status apache2.service

active(running)を確認します。

上記適用後、

  1. コードブロックにコピーボタンがあり、
  2. ボタンクリックで内容がクリップボードに保存される

ことが確認できれば設定完了です。