From 76ef9bff098b82133ad9d74ba7b82af74027495c Mon Sep 17 00:00:00 2001 From: ishikawa999 Date: Mon, 8 Mar 2021 04:40:42 +0000 Subject: [PATCH 1/2] Add link to copy object's url to the clipboard --- app/helpers/application_helper.rb | 8 +++++ app/helpers/journals_helper.rb | 4 ++- app/views/issues/_action_menu.html.erb | 1 + config/locales/en.yml | 1 + public/images/page_link.png | Bin 0 -> 830 bytes public/javascripts/application.js | 17 +++++++++ public/stylesheets/application.css | 1 + test/functional/issues_controller_test.rb | 2 ++ test/helpers/journals_helper_test.rb | 1 + test/system/copy_to_clipboard_test.rb | 56 ++++++++++++++++++++++++++++++ 10 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 public/images/page_link.png create mode 100644 test/system/copy_to_clipboard_test.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index deb977b4f..f4f0e9f3c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1818,6 +1818,14 @@ module ApplicationHelper ) end + def copy_object_url_link(url) + link_to_function( + l(:button_copy_link), "copyTextToClipboard(this);", + class: 'icon icon-copy-link', + data: {'clipboard-text' => url} + ) + end + private def wiki_helper diff --git a/app/helpers/journals_helper.rb b/app/helpers/journals_helper.rb index a6cb97632..ae5749150 100644 --- a/app/helpers/journals_helper.rb +++ b/app/helpers/journals_helper.rb @@ -29,9 +29,11 @@ module JournalsHelper def render_journal_actions(issue, journal, options={}) links = [] dropbown_links = [] + indice = journal.indice || @journal.issue.visible_journals_with_index.find{|j| j.id == @journal.id}.indice + + dropbown_links << copy_object_url_link(issue_url(issue, anchor: "note-#{indice}", only_path: false)) if journal.notes.present? if options[:reply_links] - indice = journal.indice || @journal.issue.visible_journals_with_index.find{|j| j.id == @journal.id}.indice links << link_to(l(:button_quote), quoted_issue_path(issue, :journal_id => journal, :journal_indice => indice), :remote => true, diff --git a/app/views/issues/_action_menu.html.erb b/app/views/issues/_action_menu.html.erb index 93e5bad63..a1dcd6312 100644 --- a/app/views/issues/_action_menu.html.erb +++ b/app/views/issues/_action_menu.html.erb @@ -6,6 +6,7 @@ :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %> <%= watcher_link(@issue, User.current) %> <%= actions_dropdown do %> + <%= copy_object_url_link(issue_url(@issue, only_path: false)) %> <%= link_to l(:button_copy), project_copy_issue_path(@project, @issue), :class => 'icon icon-copy' if User.current.allowed_to?(:copy_issues, @project) && Issue.allowed_target_projects.any? %> <%= link_to l(:button_delete), issue_path(@issue), diff --git a/config/locales/en.yml b/config/locales/en.yml index 979417a32..704664e3e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1145,6 +1145,7 @@ en: button_change_password: Change password button_copy: Copy button_copy_and_follow: Copy and follow + button_copy_link: Copy link button_annotate: Annotate button_update: Update button_configure: Configure diff --git a/public/images/page_link.png b/public/images/page_link.png new file mode 100644 index 0000000000000000000000000000000000000000..312eab0914ab59271384686255d1be913a6b3add GIT binary patch literal 830 zcmV-E1Ht@>P)VWgGzD=Y79#JI$lhEn`|2MpRa?Bt#-nSD~P0P(mbVe{KrOBoKnSsk>m|ML{6l zBosu@om4j#WzNLRAk+{k1JRvL(MfE&vvbb->v>W{*z*1_uMP}0cIRX*?mz+wk%*#O z%0D-+$B*g1nRkvI+_3E8Pr1NC6@5M&4vWaLCnNlr;lNlr4i91z&)eBGqL{L{GNu;Fof}GS9{gM5BJuH;2QWk8yuOZdB3pGR#s8bd~ zAmt<>3Q=YH$t5YJ5;7@+8Uh6=ktBgY6#6Pa%2F?h910?U8cLT43KAj$Z1*==ra&gILO{WkHfs(--F=bly9l~${z@AT>V$oat!YAD@M zBE0v_F{`g#^wOSP-u~!wvlmXdd*uqFqoZ0^{&nEMDU+=!>({S0wrQhFmoB}Yq1)~0 z^{A)L8Xjzdr(W4_exYO6u3a{4*kIeXZMJUR>Q=ksjW_p!rAwDKYUs90>6Q|C>56o@ zbrSq^Xk7Gq#>dAsn@un`Hz$?w$;Ss`%jV7L%9ShgHFV~C)6M>B`Tp%|nqPc&G*A3| z)Qe+}vT4-x^t1~XE@(6wR;^lPWMo9n*~E!cy~)YRsT2{`?fqeIw-e7N@mOA%UcCIq z_kH(_EK7|>pM*1Wt2^DaDAp|cvp*@(ZZDKpYkKC^?97(`0sb&XTXy7N#sB~S07*qo IM6N<$f;YmCWB>pF literal 0 HcmV?d00001 diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 770d5900c..bef8f592f 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -563,6 +563,23 @@ function randomKey(size) { return key; } +function copyTextToClipboard(target) { + if (target) { + var temp = document.createElement('textarea'); + temp.value = target.getAttribute('data-clipboard-text'); + document.body.appendChild(temp); + temp.select(); + document.execCommand('copy'); + if (temp.parentNode) { + temp.parentNode.removeChild(temp); + } + if ($(target).closest('.drdn.expanded').length) { + $(target).closest('.drdn.expanded').removeClass("expanded"); + } + } + return false; +} + function updateIssueFrom(url, el) { $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){ $(this).data('valuebeforeupdate', $(this).val()); diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 1c2f76c15..5235ba6e0 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -1612,6 +1612,7 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { .icon-file.application-pdf { background-image: url(../images/files/pdf.png); } .icon-file.application-zip { background-image: url(../images/files/zip.png); } .icon-file.application-gzip { background-image: url(../images/files/zip.png); } +.icon-copy-link { background-image: url(../images/page_link.png); } .sort-handle.ajax-loading { background-image: url(../images/loading.gif); } tr.ui-sortable-helper { border:1px solid #e4e4e4; } diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 91d11c6c8..f52b5f974 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -2076,6 +2076,7 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'a', {:count => 2, :text => /Edit/} assert_select 'a', {:count => 0, :text => /Log time/} assert_select 'a', {:count => 0, :text => /Watch/} + assert_select 'div.drdn-items a', {:count => 1, :text => /Copy link/} assert_select 'div.drdn-items a', {:count => 0, :text => /Copy/} assert_select 'div.drdn-items a', {:count => 0, :text => /Delete/} end @@ -2097,6 +2098,7 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'a', {:count => 2, :text => /Edit/} assert_select 'a', :text => /Log time/ assert_select 'a', :text => /Watch/ + assert_select 'div.drdn-items a', :text => /Copy link/ assert_select 'div.drdn-items a', :text => /Copy/ assert_select 'div.drdn-items a', :text => /Delete/ end diff --git a/test/helpers/journals_helper_test.rb b/test/helpers/journals_helper_test.rb index 218b9498a..9280d3ce8 100644 --- a/test/helpers/journals_helper_test.rb +++ b/test/helpers/journals_helper_test.rb @@ -59,5 +59,6 @@ class JournalsHelperTest < Redmine::HelperTest assert_select_in journal_actions, 'a[title=?][class="icon-only icon-comment"]', 'Quote' assert_select_in journal_actions, 'a[title=?][class="icon-only icon-edit"]', 'Edit' assert_select_in journal_actions, 'div[class="drdn-items"] a[class="icon icon-del"]' + assert_select_in journal_actions, 'div[class="drdn-items"] a[class="icon icon-copy-link"]' end end diff --git a/test/system/copy_to_clipboard_test.rb b/test/system/copy_to_clipboard_test.rb new file mode 100644 index 000000000..c54508fea --- /dev/null +++ b/test/system/copy_to_clipboard_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2020 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../application_system_test_case', __FILE__) + +class CopyToClipboardSystemTest < ApplicationSystemTestCase + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, + :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, + :enumerations, :custom_fields, :custom_values, :custom_fields_trackers, + :watchers, :journals, :journal_details, :versions, + :workflows, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def test_copy_issue_url_to_clipboard + log_user('jsmith', 'jsmith') + visit 'issues/1' + + # Copy issue url to Clipboard + first('.contextual span.icon-actions').click + find('.contextual div.drdn-items a.icon-copy-link').click + + # Paste the value copied to the clipboard into the textarea to get and test + first('.icon-edit').click + find('textarea#issue_notes').send_keys([:control, 'v']) + assert find('textarea#issue_notes').value.end_with?('/issues/1') + end + + def test_copy_issue_journal_url_to_clipboard + log_user('jsmith', 'jsmith') + visit 'issues/1' + + # Copy issue journal url to Clipboard + first('#note-2 .icon-actions').click + first('#note-2 div.drdn-items a.icon-copy-link').click + + # Paste the value copied to the clipboard into the textarea to get and test + first('.icon-edit').click + find('textarea#issue_notes').send_keys([:control, 'v']) + assert find('textarea#issue_notes').value.end_with?('/issues/1#note-2') + end +end -- 2.11.0