Browse Source

Merge branch 'clipboardjs' into master

This replaces clippy.sfw with Javascript for issue #1241
pull/1442/head
Florian Zschocke 1 year ago
parent
commit
60df2321a6

+ 8
- 1
releases.moxie View File

@@ -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
}

+ 5
- 1
src/main/distrib/data/defaults.properties View File

@@ -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.
#

+ 5
- 0
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java View File

@@ -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);
}

+ 2
- 2
src/main/java/com/gitblit/wicket/pages/SummaryPage.html View File

@@ -61,7 +61,7 @@
<wicket:fragment wicket:id="ownersFragment">
</wicket:fragment>
</wicket:extend>
</wicket:extend>
</body>
</html>

+ 7
- 13
src/main/java/com/gitblit/wicket/pages/TicketPage.html View File

@@ -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>

+ 5
- 6
src/main/java/com/gitblit/wicket/pages/TicketPage.java View File

@@ -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

+ 7
- 13
src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html View File

@@ -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">

+ 17
- 13
src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java View File

@@ -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");

+ 7
- 0
src/main/resources/clipboard/clipboard.min.js
File diff suppressed because it is too large
View File


+ 74
- 0
src/main/resources/clipboard/gitblit-ctcbtn.js View File

@@ -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;
}

BIN
src/main/resources/clippy.swf View File


+ 171
- 1
src/main/resources/gitblit.css View File

@@ -2408,4 +2408,174 @@ table.filestore-status {
font-weight: 200;
font-size: 1em;
font-variant: normal;
}
}
/*
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)
}

Loading…
Cancel
Save