diff options
author | Florian Zschocke <f.zschocke+git@gmail.com> | 2022-12-03 14:34:03 +0100 |
---|---|---|
committer | Florian Zschocke <f.zschocke+git@gmail.com> | 2022-12-03 14:34:03 +0100 |
commit | 60df2321a6f5c5ea18f1d38b4d26291f0ece6012 (patch) | |
tree | 6db83a55d77dcdddaa6ac003a0ff0392b229d09c | |
parent | c84179a751da1e4bb84e7d30ff848c0d197ec6a2 (diff) | |
parent | c04ddf554f3f2c77e268468a43dd645a8b565074 (diff) | |
download | gitblit-60df2321a6f5c5ea18f1d38b4d26291f0ece6012.tar.gz gitblit-60df2321a6f5c5ea18f1d38b4d26291f0ece6012.zip |
Merge branch 'clipboardjs' into master
This replaces clippy.sfw with Javascript for issue #1241
-rw-r--r-- | releases.moxie | 9 | ||||
-rw-r--r-- | src/main/distrib/data/defaults.properties | 6 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/pages/RepositoryPage.java | 5 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/pages/SummaryPage.html | 4 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/pages/TicketPage.html | 20 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/pages/TicketPage.java | 11 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html | 20 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java | 30 | ||||
-rw-r--r-- | src/main/resources/clipboard/clipboard.min.js | 7 | ||||
-rw-r--r-- | src/main/resources/clipboard/gitblit-ctcbtn.js | 74 | ||||
-rw-r--r-- | src/main/resources/clippy.swf | bin | 5380 -> 0 bytes | |||
-rw-r--r-- | src/main/resources/gitblit.css | 172 |
12 files changed, 308 insertions, 50 deletions
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/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 2d1b6a56..8915ecf5 100644 --- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html +++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html @@ -61,7 +61,7 @@ <wicket:fragment wicket:id="ownersFragment">
</wicket:fragment>
-
-</wicket:extend>
+
+</wicket:extend>
</body>
</html>
\ No newline at end of file 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</pre> <img wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard"></img>
</span>
</wicket:fragment>
-
-<!-- flash-based button-press copy & paste -->
-<wicket:fragment wicket:id="clippyPanel">
- <object wicket:message="title:gb.copyToClipboard" style="vertical-align:middle;"
- wicket:id="clippy"
- width="14"
- height="14"
- bgcolor="#ffffff"
- quality="high"
- wmode="transparent"
- scale="noscale"
- allowScriptAccess="sameDomain"></object>
-</wicket:fragment>
+
+ <!-- JavaScript automatic copy to clipboard -->
+ <wicket:fragment wicket:id="clippyPanel">
+ <span class="tooltipped tooltipped-n">
+ <img class="ctcbtn" wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard" />
+ </span>
+ </wicket:fragment>
</wicket:extend>
</body>
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
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html index a537277f..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 @@ <wicket:fragment wicket:id="repositoryUrlFragment">
<div class="btn-toolbar" style="margin: 0px;">
- <div class="btn-group repositoryUrlContainer">
+ <div class="btn-group repositoryUrlContainer tooltipped tooltipped-w">
<img style="vertical-align: middle;padding: 0px 0px 1px 3px;" wicket:id="accessRestrictionIcon"></img>
<span wicket:id="menu"></span>
<div class="repositoryUrl">
<span wicket:id="primaryUrl">[repository primary url]</span>
- <span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
+ <span class="tooltipped tooltipped-n">
+ <span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
+ </span>
</div>
<span class="hidden-phone hidden-tablet repositoryUrlRightCap" wicket:id="primaryUrlPermission">[repository primary url permission]</span>
</div>
@@ -33,7 +35,7 @@ <wicket:fragment wicket:id="applicationMenusFragment">
<div class="btn-toolbar" style="margin: 4px 0px 0px 0px;">
- <div class="btn-group" wicket:id="appMenus">
+ <div class="btn-group tooltipped tooltipped-w" wicket:id="appMenus">
<span wicket:id="appMenu"></span>
</div>
</div>
@@ -85,17 +87,9 @@ </span>
</wicket:fragment>
- <!-- flash-based button-press copy & paste -->
+ <!-- JavaScript automatic copy to clipboard -->
<wicket:fragment wicket:id="clippyPanel">
- <object wicket:message="title:gb.copyToClipboard" style="vertical-align:middle;"
- wicket:id="clippy"
- width="14"
- height="14"
- bgcolor="#ffffff"
- quality="high"
- wmode="transparent"
- scale="noscale"
- allowScriptAccess="sameDomain"></object>
+ <img class="ctcbtn" wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard" />
</wicket:fragment>
<wicket:fragment wicket:id="workingCopyFragment">
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<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body},n="";return"string"==typeof t?n=o(t,e):t instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(null==t?void 0:t.type)?n=o(t.value,e):(n=r()(t),c("copy")),n};function l(t){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var s=function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},e=t.action,n=void 0===e?"copy":e,o=t.container,e=t.target,t=t.text;if("copy"!==n&&"cut"!==n)throw new Error('Invalid "action" value, use either "copy" or "cut"');if(void 0!==e){if(!e||"object"!==l(e)||1!==e.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===n&&e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===n&&(e.hasAttribute("readonly")||e.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes')}return t?f(t,{container:o}):e?"cut"===n?a(e):f(e,{container:o}):void 0};function p(t){return(p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function d(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function y(t,e){return(y=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function h(n){var o=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}();return function(){var t,e=v(n);return t=o?(t=v(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments),e=this,!(t=t)||"object"!==p(t)&&"function"!=typeof t?function(t){if(void 0!==t)return t;throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}(e):t}}function v(t){return(v=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function m(t,e){t="data-clipboard-".concat(t);if(e.hasAttribute(t))return e.getAttribute(t)}var b=function(){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&y(t,e)}(r,i());var t,e,n,o=h(r);function r(t,e){var n;return function(t){if(!(t instanceof r))throw new TypeError("Cannot call a class as a function")}(this),(n=o.call(this)).resolveOptions(e),n.listenClick(t),n}return t=r,n=[{key:"copy",value:function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body};return f(t,e)}},{key:"cut",value:function(t){return a(t)}},{key:"isSupported",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:["copy","cut"],t="string"==typeof t?[t]:t,e=!!document.queryCommandSupported;return t.forEach(function(t){e=e&&!!document.queryCommandSupported(t)}),e}}],(e=[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===p(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=u()(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget,n=this.action(e)||"copy",t=s({action:n,container:this.container,target:this.target(e),text:this.text(e)});this.emit(t?"success":"error",{action:n,text:t,trigger:e,clearSelection:function(){e&&e.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(t){return m("action",t)}},{key:"defaultTarget",value:function(t){t=m("target",t);if(t)return document.querySelector(t)}},{key:"defaultText",value:function(t){return m("text",t)}},{key:"destroy",value:function(){this.listener.destroy()}}])&&d(t.prototype,e),n&&d(t,n),r}()},828:function(t){var e;"undefined"==typeof Element||Element.prototype.matches||((e=Element.prototype).matches=e.matchesSelector||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector),t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}},438:function(t,e,n){var u=n(828);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=u(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},879:function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},370:function(t,e,n){var f=n(879),l=n(438);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!f.string(e))throw new TypeError("Second argument must be a String");if(!f.fn(n))throw new TypeError("Third argument must be a Function");if(f.node(t))return c=e,a=n,(u=t).addEventListener(c,a),{destroy:function(){u.removeEventListener(c,a)}};if(f.nodeList(t))return o=t,r=e,i=n,Array.prototype.forEach.call(o,function(t){t.addEventListener(r,i)}),{destroy:function(){Array.prototype.forEach.call(o,function(t){t.removeEventListener(r,i)})}};if(f.string(t))return t=t,e=e,n=n,l(document.body,t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,u,c,a}},817:function(t){t.exports=function(t){var e,n="SELECT"===t.nodeName?(t.focus(),t.value):"INPUT"===t.nodeName||"TEXTAREA"===t.nodeName?((e=t.hasAttribute("readonly"))||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),e||t.removeAttribute("readonly"),t.value):(t.hasAttribute("contenteditable")&&t.focus(),n=window.getSelection(),(e=document.createRange()).selectNodeContents(t),n.removeAllRanges(),n.addRange(e),n.toString());return n}},279:function(t){function e(){}e.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,u=o.length;i<u;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=e,t.exports.TinyEmitter=e}},r={},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,{a:e}),e},o.d=function(t,e){for(var n in e)o.o(e,n)&&!o.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o(686).default;function o(t){if(r[t])return r[t].exports;var e=r[t]={exports:{}};return n[t](e,e.exports,o),e.exports}var n,r});
\ No newline at end of file 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/clippy.swf b/src/main/resources/clippy.swf Binary files differdeleted file mode 100644 index e46886cd..00000000 --- a/src/main/resources/clippy.swf +++ /dev/null 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)
+}
|