From c10a6f284e9d165198204f330ddcf9dbfef2857e Mon Sep 17 00:00:00 2001 From: Tan Le Date: Wed, 17 Jul 2019 09:14:53 +1000 Subject: [PATCH] feat: add copy to clipboard for version numbers (#283) * Add Copy to clipboard button for consumer and provider version * Move clipboard functionalities to a separate file. * Add Copy to clipboard to matrix page. * Add visual cue that the text has been copied * Remove violated CSS rules * Initialize copy-to-clipboard functionality with explicit selector * Rename bootstrap method to reveal better intent. --- .../ui/views/index/_css_and_js.haml | 1 + .../ui/views/index/show-with-tags.haml | 10 ++- lib/pact_broker/ui/views/matrix/show.haml | 14 +++- public/javascripts/clipboard.js | 73 +++++++++++++++++++ public/javascripts/matrix.js | 2 - public/stylesheets/index.css | 19 +++++ 6 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 public/javascripts/clipboard.js diff --git a/lib/pact_broker/ui/views/index/_css_and_js.haml b/lib/pact_broker/ui/views/index/_css_and_js.haml index 485e751a4..60f2cdb07 100644 --- a/lib/pact_broker/ui/views/index/_css_and_js.haml +++ b/lib/pact_broker/ui/views/index/_css_and_js.haml @@ -5,4 +5,5 @@ %script{type: 'text/javascript', src:'/javascripts/jquery.tablesorter.min.js'} %script{type: 'text/javascript', src:'/javascripts/material-menu.js'} %script{type: 'text/javascript', src:'/javascripts/index.js'} +%script{type: 'text/javascript', src:'/javascripts/clipboard.js'} %script{type: 'text/javascript', src:'/javascripts/jquery-confirm.min.js'} diff --git a/lib/pact_broker/ui/views/index/show-with-tags.haml b/lib/pact_broker/ui/views/index/show-with-tags.haml index 1e67ee1b5..6941f4b60 100644 --- a/lib/pact_broker/ui/views/index/show-with-tags.haml +++ b/lib/pact_broker/ui/views/index/show-with-tags.haml @@ -39,8 +39,10 @@ %a{:href => index_item.consumer_group_url } = escape_html(index_item.consumer_name) %td.consumer-version-number - %div + %div.clippable = escape_html(index_item.consumer_version_number) + %button.clippy.hidden{ title: "Copy to clipboard" } + %span.glyphicon.glyphicon-copy - if index_item.latest? .tag.label.label-success latest @@ -56,8 +58,10 @@ %a{ href: index_item.provider_group_url } = escape_html(index_item.provider_name) %td.provider-version-number - %div + %div.clippable = escape_html(index_item.provider_version_number) + %button.clippy.hidden{ title: "Copy to clipboard" } + %span.glyphicon.glyphicon-copy - index_item.provider_version_latest_tag_names.each do | tag_name | .tag.label.label-primary = escape_html(tag_name) @@ -85,6 +89,8 @@ }); $(document).ready(function(){ + initializeClipper(".clippable"); + $("span.pact a").load("/images/doc-text.svg"); $("span.pact-matrix a").load("/images/doc-matrix.svg"); $('td[data-toggle="tooltip"]').each(function(index, td){ diff --git a/lib/pact_broker/ui/views/matrix/show.haml b/lib/pact_broker/ui/views/matrix/show.haml index 4b1ae58cc..a7044fc2a 100644 --- a/lib/pact_broker/ui/views/matrix/show.haml +++ b/lib/pact_broker/ui/views/matrix/show.haml @@ -5,6 +5,7 @@ %script{type: 'text/javascript', src:'/javascripts/jquery-3.3.1.min.js'} %script{type: 'text/javascript', src:'/javascripts/jquery.tablesorter.min.js'} %script{type: 'text/javascript', src:'/javascripts/matrix.js'} + %script{type: 'text/javascript', src:'/javascripts/clipboard.js'} %script{type: 'text/javascript', src:'/js/bootstrap.min.js'} .container @@ -102,9 +103,11 @@ %a{href: line.consumer_name_url} = line.consumer_name %td.consumer-version{'data-sort-value' => line.consumer_version_order} - %div + %div.clippable %a{href: line.consumer_version_number_url} = line.display_consumer_version_number + %button.clippy.hidden{ title: "Copy to clipboard" } + %span.glyphicon.glyphicon-copy - line.latest_consumer_version_tags.each do | tag | .tag-parent{"title": tag.tooltip, "data-toggle": "tooltip", "data-placement": "right"} %a{href: tag.url} @@ -126,9 +129,11 @@ %a{href: line.provider_name_url} = line.provider_name %td.provider-version{'data-sort-value' => line.provider_version_order} - %div + %div.clippable %a{href: line.provider_version_number_url} = line.display_provider_version_number + %button.clippy.hidden{ title: "Copy to clipboard" } + %span.glyphicon.glyphicon-copy - line.latest_provider_version_tags.each do | tag | .tag-parent{"title": tag.tooltip, "data-toggle": "tooltip", "data-placement": "right"} %a{href: tag.url} @@ -145,3 +150,8 @@ = "#{line.verification_status} (number #{line.number})" - else = line.verification_status + + :javascript + $(document).ready(function(){ + initializeClipper(".clippable"); + }); diff --git a/public/javascripts/clipboard.js b/public/javascripts/clipboard.js new file mode 100644 index 000000000..5b40f85ef --- /dev/null +++ b/public/javascripts/clipboard.js @@ -0,0 +1,73 @@ +/** + * Include code to enable copy-to-clipboard functionality + * and currently used on index and matrix tables + * @example in Haml + * %div.clippable + * = Text to be copied + * %button.clippy.hidden{ title: "Copy to clipboard" } + * %span.glyphicon.glyphicon-copy + */ + +/** + * Bootstrap copy-to-clipboard functionality + * @param {string} selector CSS selector of elements that require + * copy-to-clipboard functionality + */ +function initializeClipper(selector) { + const elements = $(selector); + + elements.hover(function() { + $(this).children(".clippy").toggleClass("hidden"); + }); + + elements + .children(".clippy") + .click(function() { + const clippyButton = $(this); + const text = $.trim(clippyButton.closest(selector).text()); + + copyToClipboard(text); + flashClipped(clippyButton); + }); +} + +/** + * Copy text to clipboard using execCommand + * @see https://gist.github.com/Chalarangelo/4ff1e8c0ec03d9294628efbae49216db#file-copytoclipboard-js + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand + * @param {string} text text to be copied to clipboard + */ +function copyToClipboard(text) { + const el = document.createElement('textarea'); + el.value = text; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + + const selected = + document.getSelection().rangeCount > 0 + ? document.getSelection().getRangeAt(0) + : false; + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + if (selected) { + document.getSelection().removeAllRanges(); + document.getSelection().addRange(selected); + } +} + +/** + * Flash a success tick to indicate successful copy-to-clipboard action + * @param {jQuery Element} clipButton button to copy to clipboard + */ +function flashClipped(clippyButton) { + const icon = clippyButton.children("span"); + icon.attr("class", "glyphicon glyphicon-ok success"); + + setTimeout( + function() { icon.attr("class", "glyphicon glyphicon-copy"); }, + 2000 + ); +} diff --git a/public/javascripts/matrix.js b/public/javascripts/matrix.js index fa079a534..dd3f6d913 100644 --- a/public/javascripts/matrix.js +++ b/public/javascripts/matrix.js @@ -1,4 +1,3 @@ - function setTextboxVisibility(selectBox, cssSelector, visibility) { var textbox = selectBox.closest('.selector').find(cssSelector); textbox.toggle(visibility); @@ -65,7 +64,6 @@ $(document).ready(function(){ } }); - $('[data-toggle="tooltip"]').each(function(index, el){ $(el).tooltip({container: $(el)}); }); diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css index 8912b7268..6824e6aec 100644 --- a/public/stylesheets/index.css +++ b/public/stylesheets/index.css @@ -56,6 +56,25 @@ body { padding-top: 10px; } word-wrap: break-word; } +.clippable { + display: flex; + justify-content: flex-start; +} + +.clippy { + padding: 0 4px; + border: none; + background: none; +} + +.clippy:hover { + color: #2a6496; +} + +.success { + color: #5cb85c +} + span.pact { display: inline-block; }