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.pull/1438/head
@@ -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); | |||
} |
@@ -62,13 +62,6 @@ | |||
</wicket:fragment> | |||
<!-- load clipboard library to copy repository URL --> | |||
<script type="text/javascript" src="clipboard/clipboard.min.js"></script> | |||
<!-- instantiate clipboard --> | |||
<script> | |||
var clipboard = new ClipboardJS('.ctcbtn'); | |||
</script> | |||
</wicket:extend> | |||
</body> | |||
</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> | |||
@@ -87,7 +89,7 @@ | |||
<!-- JavaScript automatic copy to clipboard --> | |||
<wicket:fragment wicket:id="clippyPanel"> | |||
<img class="ctcbtn" data-clipboard-action="copy" wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard" /> | |||
<img class="ctcbtn" wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard" /> | |||
</wicket:fragment> | |||
<wicket:fragment wicket:id="workingCopyFragment"> |
@@ -0,0 +1,72 @@ | |||
// 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); | |||
} | |||
// Show or hide tooltip by setting the tooltipped-active class | |||
// on a parent that containes tooltipped. Since the copy button | |||
// could be and image, or be hidden after clicking, the tooltipped | |||
// emelent 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'); | |||
// e.currentTarget.removeAttribute('data-tt-text'); | |||
} | |||
} | |||
else { | |||
console.warn("Could not find any tooltipped element for clipboard button.", e.currentTarget); | |||
} | |||
} | |||
function findTooltipped(elem) { | |||
do { | |||
if (elem.classList.contains('tooltipped')) return elem; | |||
elem = elem.parentElement; | |||
} while (elem != null); | |||
return null; | |||
} | |||
// 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; | |||
} |
@@ -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) | |||
} |