diff --git a/app/assets/javascripts/application-legacy.js b/app/assets/javascripts/application-legacy.js index 833e7d8d1..9662d2396 100644 --- a/app/assets/javascripts/application-legacy.js +++ b/app/assets/javascripts/application-legacy.js @@ -1441,4 +1441,3 @@ $(document).ready(setupFilePreviewNavigation); $(document).on('focus', '[data-auto-complete=true]', function(event) { inlineAutoComplete(event.target); }); -document.addEventListener("DOMContentLoaded", () => { setupCopyButtonsToPreElements(); }); diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index df3bc8ddc..aabb78410 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1923,11 +1923,10 @@ module ApplicationHelper end def copy_object_url_link(url) - link_to_function( - sprite_icon('copy-link', l(:button_copy_link)), 'copyDataClipboardTextToClipboard(this);', - class: 'icon icon-copy-link', - data: {'clipboard-text' => url} - ) + link_to sprite_icon('copy-link', l(:button_copy_link)), + '#', + class: 'icon icon-copy-link', + data: {clipboard_text: url, controller: 'clipboard', action: 'clipboard#copyText'} end private diff --git a/app/javascript/controllers/clipboard_controller.js b/app/javascript/controllers/clipboard_controller.js new file mode 100644 index 000000000..1ea07c446 --- /dev/null +++ b/app/javascript/controllers/clipboard_controller.js @@ -0,0 +1,55 @@ +/** + * Redmine - project management software + * Copyright (C) 2006- Jean-Philippe Lang + * This code is released under the GNU General Public License. + */ +import { Controller } from "@hotwired/stimulus" + +// Connects to data-controller="clipboard" +export default class extends Controller { + static targets = ['pre']; + + copyPre(e) { + e.preventDefault(); + const element = e.currentTarget; + let textToCopy = (this.preTarget.querySelector("code") || this.preTarget).textContent.replace(/\n$/, ''); + if (this.preTarget.querySelector("code.syntaxhl")) { textToCopy = textToCopy.replace(/ $/, ''); } // Workaround for half-width space issue in Textile's highlighted code + + this.copy(textToCopy).then(() => { + updateSVGIcon(element, "checked"); + setTimeout(() => updateSVGIcon(element, "copy-pre-content"), 2000); + }); + } + + copyText(e) { + e.preventDefault(); + this.copy(e.currentTarget.dataset.clipboardText); + + const element = e.currentTarget.closest('.drdn.expanded'); + if (element !== null) { + element.classList.remove('expanded'); + } + } + + copy(text) { + if (navigator.clipboard) { + return navigator.clipboard.writeText(text).catch(() => { + return this.fallback(text); + }); + } else { + return this.fallback(text); + } + } + + fallback(text) { + const temp = document.createElement('textarea'); + temp.value = text; + temp.style.position = 'fixed'; + temp.style.left = '-9999px'; + document.body.appendChild(temp); + temp.select(); + document.execCommand('copy'); + document.body.removeChild(temp); + return Promise.resolve(); + } +} diff --git a/app/views/journals/update.js.erb b/app/views/journals/update.js.erb index 218a164de..cd75cd8fa 100644 --- a/app/views/journals/update.js.erb +++ b/app/views/journals/update.js.erb @@ -14,7 +14,6 @@ } else { journal_header.append('<%= escape_javascript(render_journal_update_info(@journal)) %>'); } - setupCopyButtonsToPreElements(); setupHoverTooltips(); <% end %> diff --git a/lib/redmine/wiki_formatting/common_mark/formatter.rb b/lib/redmine/wiki_formatting/common_mark/formatter.rb index d9d3e9640..9ef52692e 100644 --- a/lib/redmine/wiki_formatting/common_mark/formatter.rb +++ b/lib/redmine/wiki_formatting/common_mark/formatter.rb @@ -56,6 +56,7 @@ module Redmine SCRUBBERS = [ SyntaxHighlightScrubber.new, Redmine::WikiFormatting::TablesortScrubber.new, + Redmine::WikiFormatting::CopypreScrubber.new, FixupAutoLinksScrubber.new, ExternalLinksScrubber.new, AlertsIconsScrubber.new diff --git a/lib/redmine/wiki_formatting/copypre_scrubber.rb b/lib/redmine/wiki_formatting/copypre_scrubber.rb new file mode 100644 index 000000000..2afd02823 --- /dev/null +++ b/lib/redmine/wiki_formatting/copypre_scrubber.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- Jean-Philippe Lang +# This code is released under the GNU General Public License. + +module Redmine + module WikiFormatting + class CopypreScrubber < Loofah::Scrubber + def scrub(node) + return unless node.name == 'pre' + + node['data-clipboard-target'] = 'pre' + # Wrap the
element with a container and add a copy button
+ node.wrap(wrapper)
+
+ # Copy the contents of the pre tag when copyButton is clicked
+ node.parent.prepend_child(button)
+ end
+
+ def wrapper
+ @wrapper ||= Nokogiri::HTML5.fragment('').children.first
+ end
+
+ def button
+ icon = ApplicationController.helpers.sprite_icon('copy-pre-content', size: 18)
+ button_copy = ApplicationController.helpers.l(:button_copy)
+ html = '' + icon + ''
+ @button ||= Nokogiri::HTML5.fragment(html).children.first
+ end
+ end
+ end
+end
diff --git a/lib/redmine/wiki_formatting/textile/formatter.rb b/lib/redmine/wiki_formatting/textile/formatter.rb
index 51eb5ff01..57d8dbab4 100644
--- a/lib/redmine/wiki_formatting/textile/formatter.rb
+++ b/lib/redmine/wiki_formatting/textile/formatter.rb
@@ -22,7 +22,8 @@ module Redmine
module Textile
SCRUBBERS = [
SyntaxHighlightScrubber.new,
- Redmine::WikiFormatting::TablesortScrubber.new
+ Redmine::WikiFormatting::TablesortScrubber.new,
+ Redmine::WikiFormatting::CopypreScrubber.new
]
class Formatter
diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb
index 4d0834773..1d0967203 100644
--- a/test/helpers/application_helper_test.rb
+++ b/test/helpers/application_helper_test.rb
@@ -1320,20 +1320,20 @@ class ApplicationHelperTest < Redmine::HelperTest
"content" => "<div class=\"bold\">content</div>
",
"" => "<script>some script;</script>
",
# do not escape pre/code tags
- "\nline 1\nline2
" => "line 1\nline2
",
- "\nline 1\nline2
" => "\nline 1\nline2
",
- "content
" => "<div class=\"foo\">content</div>
",
- "content
" => "<div class=\"<foo\">content</div>
",
+ "\nline 1\nline2
" => pre_wrapper("line 1\nline2
"),
+ "\nline 1\nline2
" => pre_wrapper("\nline 1\nline2
"),
+ "content
" => pre_wrapper("<div class=\"foo\">content</div>
"),
+ "content
" => pre_wrapper("<div class=\"<foo\">content</div>
"),
"