From c4fc5b38fa69036eee93758a536f65e3ac8ccb76 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Sun, 20 Nov 2022 18:53:26 +0100 Subject: Replace SWF clippy with clipboardjs on repository page Shockwave Flash is dead. But Gitblit still uses it to copy the repository URLs to the clip board. Which doesn't work anymore since no browser uses Flash anymore, so this has degraded disgracefully. Instead, we can use JavaScript to copy directly to the clipboard, now that there are APIs for it. So replace the use of clippy.swf on the repository page with clipboard.js[1]. This right now only has the functionality to copy to clipboard but now visual feedback, yet. This addresses GH issue #1241. [1] https://clipboardjs.com --- .../java/com/gitblit/wicket/pages/SummaryPage.html | 11 ++++++-- .../gitblit/wicket/panels/RepositoryUrlPanel.html | 12 ++------- .../gitblit/wicket/panels/RepositoryUrlPanel.java | 30 ++++++++++++---------- src/main/resources/clipboard/clipboard.min.js | 7 +++++ 4 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 src/main/resources/clipboard/clipboard.min.js diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html index 2d1b6a56..b6afc301 100644 --- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html +++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html @@ -61,7 +61,14 @@ - - + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html index a537277f..1cadd7d5 100644 --- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html @@ -85,17 +85,9 @@ - + - + diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java index 207f1250..2411e750 100644 --- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java @@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.wicket.Component; import org.apache.wicket.RequestCycle; +import org.apache.wicket.behavior.SimpleAttributeModifier; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.image.ContextImage; import org.apache.wicket.markup.html.panel.Fragment; @@ -140,8 +141,7 @@ public class RepositoryUrlPanel extends BasePanel { RepositoryUrl repoUrl = item.getModelObject(); // repository url Fragment fragment = new Fragment("repoUrl", "actionFragment", this); - Component content = new Label("content", repoUrl.url).setRenderBodyOnly(true); - WicketUtils.setCssClass(content, "commandMenuItem"); + Component content = new Label("content", repoUrl.url).setOutputMarkupId(true); fragment.add(content); item.add(fragment); @@ -150,7 +150,7 @@ public class RepositoryUrlPanel extends BasePanel { String tooltip = getProtocolPermissionDescription(repository, repoUrl); WicketUtils.setHtmlTooltip(permissionLabel, tooltip); fragment.add(permissionLabel); - fragment.add(createCopyFragment(repoUrl.url)); + fragment.add(createCopyFragment(repoUrl.url, content.getMarkupId())); } }; @@ -199,13 +199,15 @@ public class RepositoryUrlPanel extends BasePanel { } } - urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true)); + Label primaryUrlLabel = new Label("primaryUrl", primaryUrl.url); + primaryUrlLabel.setOutputMarkupId(true); + urlPanel.add(primaryUrlLabel); Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.hasPermission() ? primaryUrl.permission.toString() : externalPermission); String tooltip = getProtocolPermissionDescription(repository, primaryUrl); WicketUtils.setHtmlTooltip(permissionLabel, tooltip); urlPanel.add(permissionLabel); - urlPanel.add(createCopyFragment(primaryUrl.url)); + urlPanel.add(createCopyFragment(primaryUrl.url, primaryUrlLabel.getMarkupId())); return urlPanel; } @@ -317,12 +319,13 @@ public class RepositoryUrlPanel extends BasePanel { // command-line String command = substitute(clientApp.command, repoUrl.url, baseURL, user.username, repository.name); Label content = new Label("content", command); + content.setOutputMarkupId(true); WicketUtils.setCssClass(content, "commandMenuItem"); fragment.add(content); repoLinkItem.add(fragment); // copy function for command - fragment.add(createCopyFragment(command)); + fragment.add(createCopyFragment(command, content.getMarkupId())); } }}; appMenu.add(actionItems); @@ -346,16 +349,17 @@ public class RepositoryUrlPanel extends BasePanel { return permissionLabel; } - protected Fragment createCopyFragment(String text) { + protected Fragment createCopyFragment(String text, String target) { if (app().settings().getBoolean(Keys.web.allowFlashCopyToClipboard, true)) { - // clippy: flash-based copy & paste + // javascript: browser JS API based copy to clipboard Fragment copyFragment = new Fragment("copyFunction", "clippyPanel", this); - String baseUrl = WicketUtils.getGitblitURL(getRequest()); - ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf"); - clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(text)); - copyFragment.add(clippy); + ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png"); + // Add the ID of the target element that holds the text to copy to clipboard + img.add(new SimpleAttributeModifier("data-clipboard-target", "#"+target)); + copyFragment.add(img); return copyFragment; - } else { + } + else { // javascript: manual copy & paste with modal browser prompt dialog Fragment copyFragment = new Fragment("copyFunction", "jsPanel", this); ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png"); diff --git a/src/main/resources/clipboard/clipboard.min.js b/src/main/resources/clipboard/clipboard.min.js new file mode 100644 index 00000000..1103f811 --- /dev/null +++ b/src/main/resources/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1 Date: Mon, 21 Nov 2022 01:11:52 +0100 Subject: Add a "Copied" tooltip to the copy-to-clipboard button This is not the ideal version, since the height is too low for the tooltip used for the drop-down menus. Probably has something to do with the container or something. But at least something is there now, even if not the most beautiful. --- .../com/gitblit/wicket/pages/RepositoryPage.java | 5 + .../java/com/gitblit/wicket/pages/SummaryPage.html | 7 - .../gitblit/wicket/panels/RepositoryUrlPanel.html | 10 +- src/main/resources/clipboard/gitblit-ctcbtn.js | 74 +++++++++ src/main/resources/gitblit.css | 172 ++++++++++++++++++++- 5 files changed, 256 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/clipboard/gitblit-ctcbtn.js diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java index 4d30e049..b30aee97 100644 --- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java @@ -171,6 +171,11 @@ public abstract class RepositoryPage extends RootPage { add(searchForm); searchForm.setTranslatedAttributes(); + // load clipboard library to copy repository URL + addBottomScript("../../clipboard/clipboard.min.js"); + // instantiate clipboard + addBottomScript("../../clipboard/gitblit-ctcbtn.js"); + // set stateless page preference setStatelessHint(true); } diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html index b6afc301..8915ecf5 100644 --- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html +++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html @@ -62,13 +62,6 @@ - - - - - \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html index 1cadd7d5..e4b7427a 100644 --- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html @@ -12,12 +12,14 @@
-
+
[repository primary url] - + + +
[repository primary url permission]
@@ -33,7 +35,7 @@
-
+
@@ -87,7 +89,7 @@ - + diff --git a/src/main/resources/clipboard/gitblit-ctcbtn.js b/src/main/resources/clipboard/gitblit-ctcbtn.js new file mode 100644 index 00000000..ddb2ddad --- /dev/null +++ b/src/main/resources/clipboard/gitblit-ctcbtn.js @@ -0,0 +1,74 @@ +// Instantiate the clipboarding +var clipboard = new ClipboardJS('.ctcbtn'); + +clipboard.on('success', function (e) { + showTooltip(e.trigger, "Copied!"); +}); + +clipboard.on('error', function (e) { + showTooltip(e.trigger, fallbackMessage(e.action)); +}); + +// Attach events to buttons to clear tooltip again +var btns = document.querySelectorAll('.ctcbtn'); +for (var i = 0; i < btns.length; i++) { + btns[i].addEventListener('mouseleave', clearTooltip); + btns[i].addEventListener('blur', clearTooltip); +} + + +function findTooltipped(elem) { + do { + if (elem.classList.contains('tooltipped')) return elem; + elem = elem.parentElement; + } while (elem != null); + return null; +} + +// Show or hide tooltip by setting the tooltipped-active class +// on a parent that contains tooltipped. Since the copy button +// could be and image, or be hidden after clicking, the tooltipped +// element might be higher in the hierarchy. +var ttset; +function showTooltip(elem, msg) { + let ttelem = findTooltipped(elem); + if (ttelem != null) { + ttelem.classList.add('tooltipped-active'); + ttelem.setAttribute('data-tt-text', msg); + ttset=Date.now(); + } + else { + console.warn("Could not find any tooltipped element for clipboard button.", elem); + } +} + +function clearTooltip(e) { + let ttelem = findTooltipped(e.currentTarget); + if (ttelem != null) { + let now = Date.now(); + if (now - ttset < 500) { + // Give the tooltip some time to display + setTimeout(function(){ttelem.classList.remove('tooltipped-active')}, 1000) + } + else { + ttelem.classList.remove('tooltipped-active'); + } + } + else { + console.warn("Could not find any tooltipped element for clipboard button.", e.currentTarget); + } +} + +// If the API is not supported, at least fall back to a message saying +// that now that the text is selected, Ctrl-C can be used. +// This is still a problem in the repo URL dropdown. When it is hidden, Ctrl-C doesn't work. +function fallbackMessage(action) { + var actionMsg = ""; + if (/Mac/i.test(navigator.userAgent)) { + actionMsg = "Press ⌘-C to copy"; + } + else { + actionMsg = "Press Ctrl-C to copy"; + } + return actionMsg; +} diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css index f7271788..bd2befd3 100644 --- a/src/main/resources/gitblit.css +++ b/src/main/resources/gitblit.css @@ -2408,4 +2408,174 @@ table.filestore-status { font-weight: 200; font-size: 1em; font-variant: normal; -} \ No newline at end of file +} + + +/* + Copy-to-clipboard tooltip styling from Github's primer.css + https://primer.style/css/components/tooltips + Adjusted to not hover but fade-in/out on clipboard events. +*/ + +.tooltipped { + position:relative +} + +.tooltipped:after { + position: absolute; + z-index: 1000000; + padding: 5px 8px; + + font: normal normal 11px/1.5 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; + color: #fff; + background: rgba(42, 42, 42, .8); + text-align: center; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-wrap: break-word; + white-space: pre; + pointer-events: none; + content: attr(data-tt-text); + border-radius: 3px; + -webkit-font-smoothing:subpixel-antialiased; + + opacity: 0; + transition: 0.5s opacity; +} + +.tooltipped:before { + position: absolute; + z-index: 1000001; + + width: 0; + height: 0; + color: rgba(42, 42, 42, .8); + pointer-events: none; + content: ""; + border:5px solid transparent; + + opacity: 0; + transition: 0.5s opacity; +} + +.tooltipped-active:before, .tooltipped-active:after { + opacity: 1; + text-decoration:none +} + +.tooltipped-s:after, .tooltipped-se:after, .tooltipped-sw:after { + top: 100%; + right: 50%; + margin-top:5px +} + +.tooltipped-s:before, .tooltipped-se:before, .tooltipped-sw:before { + top: auto; + right: 50%; + bottom: -5px; + margin-right: -5px; + border-bottom-color:rgba(42, 42, 42, .8) +} + +.tooltipped-se:after { + right: auto; + left: 50%; + margin-left:-15px +} + +.tooltipped-sw:after { + margin-right:-15px +} + +.tooltipped-n:after, .tooltipped-ne:after, .tooltipped-nw:after { + right: 50%; + bottom: 100%; + margin-bottom:5px +} + +.tooltipped-n:before, .tooltipped-ne:before, .tooltipped-nw:before { + top: -5px; + right: 50%; + bottom: auto; + margin-right: -5px; + border-top-color:rgba(42, 42, 42, .8) +} + +.tooltipped-ne:after { + right: auto; + left: 50%; + margin-left:-15px +} + +.tooltipped-nw:after { + margin-right:-15px +} + +.tooltipped-s:after, .tooltipped-n:after { + -webkit-transform: translateX(50%); + -ms-transform: translateX(50%); + transform:translateX(50%) +} + +.tooltipped-w:after { + right: 100%; + bottom: 50%; + margin-right: 5px; + -webkit-transform: translateY(50%); + -ms-transform: translateY(50%); + transform:translateY(50%) +} + +.tooltipped-w:before { + top: 50%; + bottom: 50%; + left: -5px; + margin-top: -5px; + border-left-color:rgba(42, 42, 42, .8) +} + +.tooltipped-e:after { + bottom: 50%; + left: 100%; + margin-left: 5px; + -webkit-transform: translateY(50%); + -ms-transform: translateY(50%); + transform:translateY(50%) +} + +.tooltipped-e:before { + top: 50%; + right: -5px; + bottom: 50%; + margin-top: -5px; + border-right-color:rgba(42, 42, 42, .8) +} + + +.tooltipped-sticky:before, .tooltipped-sticky:after { + display:inline-block +} + + +.fullscreen-overlay-enabled.dark-theme .tooltipped:after { + color: #000; + background:rgba(200, 200, 200, .8) +} + +.fullscreen-overlay-enabled.dark-theme .tooltipped .tooltipped-s:before, .fullscreen-overlay-enabled.dark-theme .tooltipped .tooltipped-se:before, .fullscreen-overlay-enabled.dark-theme .tooltipped .tooltipped-sw:before { + border-bottom-color:rgba(200, 200, 200, .8) +} + +.fullscreen-overlay-enabled.dark-theme .tooltipped.tooltipped-n:before, .fullscreen-overlay-enabled.dark-theme .tooltipped.tooltipped-ne:before, .fullscreen-overlay-enabled.dark-theme .tooltipped.tooltipped-nw:before { + border-top-color:rgba(200, 200, 200, .8) +} + +.fullscreen-overlay-enabled.dark-theme .tooltipped.tooltipped-e:before { + border-right-color:rgba(200, 200, 200, .8) +} + +.fullscreen-overlay-enabled.dark-theme .tooltipped.tooltipped-w:before { + border-left-color:rgba(200, 200, 200, .8) +} -- cgit v1.2.3 From f4c4030cf6e192217a53c470674a14f811868b7a Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 21 Nov 2022 20:52:10 +0100 Subject: Replace SWF clippy with clipboard.js on ticket page The ticket page also has some copy-to-clipboard buttons, which get updated to work with JS instead of SWF. --- .../java/com/gitblit/wicket/pages/TicketPage.html | 20 +++++++------------- .../java/com/gitblit/wicket/pages/TicketPage.java | 11 +++++------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.html b/src/main/java/com/gitblit/wicket/pages/TicketPage.html index 46c0f7ee..fd64b776 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketPage.html +++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.html @@ -586,20 +586,14 @@ pt push - - - - - + + + + + + + diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java index 1750b859..d6d188df 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java +++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java @@ -102,7 +102,6 @@ import com.gitblit.wicket.panels.CommentPanel; import com.gitblit.wicket.panels.DiffStatPanel; import com.gitblit.wicket.panels.IconAjaxLink; import com.gitblit.wicket.panels.LinkPanel; -import com.gitblit.wicket.panels.ShockWaveComponent; import com.gitblit.wicket.panels.SimpleAjaxLink; /** @@ -1644,12 +1643,12 @@ public class TicketPage extends RepositoryPage { protected Fragment createCopyFragment(String wicketId, String text) { if (app().settings().getBoolean(Keys.web.allowFlashCopyToClipboard, true)) { - // clippy: flash-based copy & paste + // javascript: browser JS API based copy to clipboard Fragment copyFragment = new Fragment(wicketId, "clippyPanel", this); - String baseUrl = WicketUtils.getGitblitURL(getRequest()); - ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf"); - clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(text)); - copyFragment.add(clippy); + ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png"); + // Add the ID of the target element that holds the text to copy to clipboard + img.add(new SimpleAttributeModifier("data-clipboard-text", text)); + copyFragment.add(img); return copyFragment; } else { // javascript: manual copy & paste with modal browser prompt dialog -- cgit v1.2.3 From c04ddf554f3f2c77e268468a43dd645a8b565074 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 21 Nov 2022 21:14:56 +0100 Subject: Remove clippy.swf and update documentation The `clippy.swf` Flash program is no longer needed and can be deleted. The configuration property is now incorrectly named, but we keep the name and update the documentation. Maybe it could be completely deleted one day, when the clipboard.js solution is known to work and be universally supported. --- releases.moxie | 9 ++++++++- src/main/distrib/data/defaults.properties | 6 +++++- src/main/resources/clippy.swf | Bin 5380 -> 0 bytes 3 files changed, 13 insertions(+), 2 deletions(-) delete mode 100644 src/main/resources/clippy.swf diff --git a/releases.moxie b/releases.moxie index 05ac21e8..4e8006ff 100644 --- a/releases.moxie +++ b/releases.moxie @@ -7,6 +7,11 @@ r34: { date: ${project.buildDate} note: '' From 1.10.0 on Gitblit requires Java 8 as minimum Java version. + + Should you have disabled the Flash-based copy-to-clipboard function because it wasn't working anymore + (web.allowFlashCopyToClipboard = false), you may want to rethink this and enable it again. The configuration + property has the same name, but the mechanism was exchanged. Flash is gone, and a modern JavaScript solution + is now used to copy text directly to the clipboard (via clipboard.js). '' html: ~ text: ~ @@ -14,7 +19,8 @@ r34: { fixes: - Fix crash in Gitblit Authority when users were deleted from Gitblit but still had entries (certificates) in the Authority. changes: - - Minimum Java required increased to Java 8 + - Minimum Java required increased to Java 8. + - Replaced the Flash-based approach to copy text to the clipboard with a modern JavaScript solution. (issue-1241) additions: ~ dependencyChanges: - update to JavaMail 1.5.6 (pr-1217 by @paladox) @@ -24,6 +30,7 @@ r34: { - update to Apache commons-io 2.11.0 - update to Apache commons-compress 1.22 - update to libpam4j 1.11 + - added clipboard.js, replacing clippy.swf contributors: - paladox } diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties index 0d072b58..b62285fd 100644 --- a/src/main/distrib/data/defaults.properties +++ b/src/main/distrib/data/defaults.properties @@ -1076,7 +1076,11 @@ web.allowForking = true # SINCE 1.2.0 web.shortCommitIdLength = 6 -# Use Clippy (Flash solution) to provide a copy-to-clipboard button. +# Use a JavaScript browser API to provide a copy-to-clipboard button. +# The clipboard.js library is used to copy text directly to the browser's +# clipboard. +# (This used to be done with a Flash based solution, but has been replaced +# with a modern JavaScript approach, since Flash support is dying out.) # If false, a button with a more primitive JavaScript-based prompt box will # offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative. # diff --git a/src/main/resources/clippy.swf b/src/main/resources/clippy.swf deleted file mode 100644 index e46886cd..00000000 Binary files a/src/main/resources/clippy.swf and /dev/null differ -- cgit v1.2.3