Fossil SCM

Add a "Copy Hash" icon to the tooltip, to copy the hash or branch name of the underlying element to the clipboard. See the wiki page linked to this branch for more information.

florian 2019-05-27 09:19 tooltip-copyhash
Commit 371943c936e649b62e4af5f8e6f8d5e9323d30d18d5f487d3c218fd361d9f457
1 file changed +68 -3
+68 -3
--- src/graph.js
+++ src/graph.js
@@ -102,10 +102,11 @@
102102
idTimer: 0, /* The tooltip dwell timer id. */
103103
idTimerClose: 0, /* The tooltip close timer id. */
104104
ixHover: -1, /* The id of the element with the mouse. */
105105
ixActive: -1, /* The id of the element with the tooltip. */
106106
nodeHover: null, /* Graph node under mouse when ixHover==-2 */
107
+ imgCopy: null, /* The image for the "Copy Hash" icon. */
107108
posX: 0, posY: 0 /* The last mouse position. */
108109
};
109110
110111
/* Functions used to control the tooltip popup and its timer */
111112
function hideGraphTooltip(){
@@ -595,13 +596,13 @@
595596
if( tooltipInfo.ixHover==-2 ){
596597
ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow
597598
var h = tx.rowinfo[ix].h
598599
var dest = tx.baseUrl + "/info/" + h
599600
if( tx.fileDiff ){
600
- html = "artifact <a href=\""+dest+"\">"+h+"</a>"
601
+ html = "artifact <a id=\"copyhash\" href=\""+dest+"\">"+h+"</a>"
601602
}else{
602
- html = "check-in <a href=\""+dest+"\">"+h+"</a>"
603
+ html = "check-in <a id=\"copyhash\" href=\""+dest+"\">"+h+"</a>"
603604
}
604605
tooltipInfo.ixActive = -2;
605606
}else if( tooltipInfo.ixHover>=0 ){
606607
ix = tooltipInfo.ixHover
607608
var br = tx.rowinfo[ix].br
@@ -609,15 +610,36 @@
609610
var hbr = br.replace(/&/g, "&amp;")
610611
.replace(/</g, "&lt;")
611612
.replace(/>/g, "&gt;")
612613
.replace(/"/g, "&quot;")
613614
.replace(/'/g, "&#039;");
614
- html = "branch <a href=\""+dest+"\">"+hbr+"</a>"
615
+ html = "branch <a id=\"copyhash\" href=\""+dest+"\">"+hbr+"</a>"
615616
tooltipInfo.ixActive = ix;
616617
}
617618
if( html ){
618619
/* Setup while hidden, to ensure proper dimensions. */
620
+ if( tooltipInfo.imgCopy==null ){
621
+ /* Create the image for the "Copy Hash" icon. */
622
+ tooltipInfo.imgCopy = document.createElement("img");
623
+ tooltipInfo.imgCopy.src =
624
+"data:image/svg+xml;utf8,"+
625
+"<svg xmlns='http:"+"/"+"/www.w3.org/2000/svg' viewBox='0 0 14 16'>"+
626
+"<path style='fill: black; opacity:0' d='M 14 16 H 0 V 0 h 14 v 16 z'/> <path "+
627
+"style='fill:rgb(240,240,240)' d='M 1 0 h 6.6 l 2 2 h 1 l 3.4 3.4 v 8.6 h -10 "+
628
+"v -2 h -3 z'/><path style='fill:rgb(64,64,64)' d='M 2 1 h 5 l 3 3 v 7 h -8 "+
629
+"z'/><path style='fill:rgb(248,248,248)' d='M 3 2 h 3.6 l 2.4 2.4 v 5.6 h -6 "+
630
+"z'/><path style='fill:rgb(80,128,208)' d='M 4 5 h 4 v 1 h -4 z m 0 2 h 4 v 1 "+
631
+"h -4 z'/><path style='fill:rgb(64,64,64)' d='M 5 3 h 5 l 3 3 v 7 h -8 "+
632
+"z'/><path style='fill:rgb(248,248,248)' d='M 10 4.4 v 1.6 h 1.6 z m -4 -0.6 "+
633
+"h 3 v 3 h -3 z m 0 3 h 6 v 5.4 h -6 z'/><path style= 'fill:rgb(80,128,208)' "+
634
+"d='M 7 8 h 4 v 1 h -4 z m 0 2 h 4 v 1 h -4 z'/> "+
635
+"</svg>";
636
+ tooltipInfo.imgCopy.width = 14;
637
+ tooltipInfo.imgCopy.height = 16;
638
+ tooltipInfo.imgCopy.style.verticalAlign = "middle";
639
+ tooltipInfo.imgCopy.style.cursor = "pointer";
640
+ }
619641
var s = getComputedStyle(document.body)
620642
if( tx.rowinfo[ix].bg.length ){
621643
tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg
622644
}else{
623645
tooltipObj.style.backgroundColor = s.getPropertyValue('background-color')
@@ -624,10 +646,15 @@
624646
}
625647
tooltipObj.style.borderColor =
626648
tooltipObj.style.color = s.getPropertyValue('color')
627649
tooltipObj.style.visibility = "hidden"
628650
tooltipObj.innerHTML = html
651
+ /* The "Copy Hash" icon is not added via tooltipObj.innerHTML, to allow
652
+ ** for the image to be cached during the lifetime of the current page. */
653
+ tooltipObj.appendChild(document.createTextNode(' '));
654
+ tooltipObj.appendChild(tooltipInfo.imgCopy);
655
+ tooltipInfo.imgCopy.onclick = clickCopyHash;
629656
tooltipObj.style.display = "inline"
630657
tooltipObj.style.position = "absolute"
631658
var x = tooltipInfo.posX + 4 + window.pageXOffset
632659
- absoluteX(tooltipObj.offsetParent)
633660
tooltipObj.style.left = x+"px"
@@ -744,5 +771,43 @@
744771
var txJson = dataObj.textContent || dataObj.innerText;
745772
var tx = JSON.parse(txJson);
746773
TimelineGraph(tx);
747774
}
748775
}())
776
+
777
+/* The onclick handler for the "Copy Hash" icon on the tooltip. */
778
+var lockCopyHash = false;
779
+function clickCopyHash(e){
780
+ //e.preventDefault();
781
+ e.stopPropagation();
782
+ if( lockCopyHash ) return;
783
+ lockCopyHash = true;
784
+ var link = document.getElementById("copyhash");
785
+ if( link ){
786
+ var hash = link.innerText;
787
+ copyTextToClipboard(hash);
788
+ }
789
+ lockCopyHash = false;
790
+}
791
+/* Create a temporary <textarea> element and copy the contents to clipboard. */
792
+function copyTextToClipboard(text){
793
+ var textArea = document.createElement("textarea");
794
+ textArea.style.position = 'fixed';
795
+ textArea.style.top = 0;
796
+ textArea.style.left = 0;
797
+ textArea.style.width = '2em';
798
+ textArea.style.height = '2em';
799
+ textArea.style.padding = 0;
800
+ textArea.style.border = 'none';
801
+ textArea.style.outline = 'none';
802
+ textArea.style.boxShadow = 'none';
803
+ textArea.style.background = 'transparent';
804
+ textArea.value = text;
805
+ document.body.appendChild(textArea);
806
+ textArea.focus();
807
+ textArea.select();
808
+ try{
809
+ document.execCommand('copy');
810
+ }catch(err){
811
+ }
812
+ document.body.removeChild(textArea);
813
+}
749814
--- src/graph.js
+++ src/graph.js
@@ -102,10 +102,11 @@
102 idTimer: 0, /* The tooltip dwell timer id. */
103 idTimerClose: 0, /* The tooltip close timer id. */
104 ixHover: -1, /* The id of the element with the mouse. */
105 ixActive: -1, /* The id of the element with the tooltip. */
106 nodeHover: null, /* Graph node under mouse when ixHover==-2 */
 
107 posX: 0, posY: 0 /* The last mouse position. */
108 };
109
110 /* Functions used to control the tooltip popup and its timer */
111 function hideGraphTooltip(){
@@ -595,13 +596,13 @@
595 if( tooltipInfo.ixHover==-2 ){
596 ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow
597 var h = tx.rowinfo[ix].h
598 var dest = tx.baseUrl + "/info/" + h
599 if( tx.fileDiff ){
600 html = "artifact <a href=\""+dest+"\">"+h+"</a>"
601 }else{
602 html = "check-in <a href=\""+dest+"\">"+h+"</a>"
603 }
604 tooltipInfo.ixActive = -2;
605 }else if( tooltipInfo.ixHover>=0 ){
606 ix = tooltipInfo.ixHover
607 var br = tx.rowinfo[ix].br
@@ -609,15 +610,36 @@
609 var hbr = br.replace(/&/g, "&amp;")
610 .replace(/</g, "&lt;")
611 .replace(/>/g, "&gt;")
612 .replace(/"/g, "&quot;")
613 .replace(/'/g, "&#039;");
614 html = "branch <a href=\""+dest+"\">"+hbr+"</a>"
615 tooltipInfo.ixActive = ix;
616 }
617 if( html ){
618 /* Setup while hidden, to ensure proper dimensions. */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619 var s = getComputedStyle(document.body)
620 if( tx.rowinfo[ix].bg.length ){
621 tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg
622 }else{
623 tooltipObj.style.backgroundColor = s.getPropertyValue('background-color')
@@ -624,10 +646,15 @@
624 }
625 tooltipObj.style.borderColor =
626 tooltipObj.style.color = s.getPropertyValue('color')
627 tooltipObj.style.visibility = "hidden"
628 tooltipObj.innerHTML = html
 
 
 
 
 
629 tooltipObj.style.display = "inline"
630 tooltipObj.style.position = "absolute"
631 var x = tooltipInfo.posX + 4 + window.pageXOffset
632 - absoluteX(tooltipObj.offsetParent)
633 tooltipObj.style.left = x+"px"
@@ -744,5 +771,43 @@
744 var txJson = dataObj.textContent || dataObj.innerText;
745 var tx = JSON.parse(txJson);
746 TimelineGraph(tx);
747 }
748 }())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
--- src/graph.js
+++ src/graph.js
@@ -102,10 +102,11 @@
102 idTimer: 0, /* The tooltip dwell timer id. */
103 idTimerClose: 0, /* The tooltip close timer id. */
104 ixHover: -1, /* The id of the element with the mouse. */
105 ixActive: -1, /* The id of the element with the tooltip. */
106 nodeHover: null, /* Graph node under mouse when ixHover==-2 */
107 imgCopy: null, /* The image for the "Copy Hash" icon. */
108 posX: 0, posY: 0 /* The last mouse position. */
109 };
110
111 /* Functions used to control the tooltip popup and its timer */
112 function hideGraphTooltip(){
@@ -595,13 +596,13 @@
596 if( tooltipInfo.ixHover==-2 ){
597 ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow
598 var h = tx.rowinfo[ix].h
599 var dest = tx.baseUrl + "/info/" + h
600 if( tx.fileDiff ){
601 html = "artifact <a id=\"copyhash\" href=\""+dest+"\">"+h+"</a>"
602 }else{
603 html = "check-in <a id=\"copyhash\" href=\""+dest+"\">"+h+"</a>"
604 }
605 tooltipInfo.ixActive = -2;
606 }else if( tooltipInfo.ixHover>=0 ){
607 ix = tooltipInfo.ixHover
608 var br = tx.rowinfo[ix].br
@@ -609,15 +610,36 @@
610 var hbr = br.replace(/&/g, "&amp;")
611 .replace(/</g, "&lt;")
612 .replace(/>/g, "&gt;")
613 .replace(/"/g, "&quot;")
614 .replace(/'/g, "&#039;");
615 html = "branch <a id=\"copyhash\" href=\""+dest+"\">"+hbr+"</a>"
616 tooltipInfo.ixActive = ix;
617 }
618 if( html ){
619 /* Setup while hidden, to ensure proper dimensions. */
620 if( tooltipInfo.imgCopy==null ){
621 /* Create the image for the "Copy Hash" icon. */
622 tooltipInfo.imgCopy = document.createElement("img");
623 tooltipInfo.imgCopy.src =
624 "data:image/svg+xml;utf8,"+
625 "<svg xmlns='http:"+"/"+"/www.w3.org/2000/svg' viewBox='0 0 14 16'>"+
626 "<path style='fill: black; opacity:0' d='M 14 16 H 0 V 0 h 14 v 16 z'/> <path "+
627 "style='fill:rgb(240,240,240)' d='M 1 0 h 6.6 l 2 2 h 1 l 3.4 3.4 v 8.6 h -10 "+
628 "v -2 h -3 z'/><path style='fill:rgb(64,64,64)' d='M 2 1 h 5 l 3 3 v 7 h -8 "+
629 "z'/><path style='fill:rgb(248,248,248)' d='M 3 2 h 3.6 l 2.4 2.4 v 5.6 h -6 "+
630 "z'/><path style='fill:rgb(80,128,208)' d='M 4 5 h 4 v 1 h -4 z m 0 2 h 4 v 1 "+
631 "h -4 z'/><path style='fill:rgb(64,64,64)' d='M 5 3 h 5 l 3 3 v 7 h -8 "+
632 "z'/><path style='fill:rgb(248,248,248)' d='M 10 4.4 v 1.6 h 1.6 z m -4 -0.6 "+
633 "h 3 v 3 h -3 z m 0 3 h 6 v 5.4 h -6 z'/><path style= 'fill:rgb(80,128,208)' "+
634 "d='M 7 8 h 4 v 1 h -4 z m 0 2 h 4 v 1 h -4 z'/> "+
635 "</svg>";
636 tooltipInfo.imgCopy.width = 14;
637 tooltipInfo.imgCopy.height = 16;
638 tooltipInfo.imgCopy.style.verticalAlign = "middle";
639 tooltipInfo.imgCopy.style.cursor = "pointer";
640 }
641 var s = getComputedStyle(document.body)
642 if( tx.rowinfo[ix].bg.length ){
643 tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg
644 }else{
645 tooltipObj.style.backgroundColor = s.getPropertyValue('background-color')
@@ -624,10 +646,15 @@
646 }
647 tooltipObj.style.borderColor =
648 tooltipObj.style.color = s.getPropertyValue('color')
649 tooltipObj.style.visibility = "hidden"
650 tooltipObj.innerHTML = html
651 /* The "Copy Hash" icon is not added via tooltipObj.innerHTML, to allow
652 ** for the image to be cached during the lifetime of the current page. */
653 tooltipObj.appendChild(document.createTextNode(' '));
654 tooltipObj.appendChild(tooltipInfo.imgCopy);
655 tooltipInfo.imgCopy.onclick = clickCopyHash;
656 tooltipObj.style.display = "inline"
657 tooltipObj.style.position = "absolute"
658 var x = tooltipInfo.posX + 4 + window.pageXOffset
659 - absoluteX(tooltipObj.offsetParent)
660 tooltipObj.style.left = x+"px"
@@ -744,5 +771,43 @@
771 var txJson = dataObj.textContent || dataObj.innerText;
772 var tx = JSON.parse(txJson);
773 TimelineGraph(tx);
774 }
775 }())
776
777 /* The onclick handler for the "Copy Hash" icon on the tooltip. */
778 var lockCopyHash = false;
779 function clickCopyHash(e){
780 //e.preventDefault();
781 e.stopPropagation();
782 if( lockCopyHash ) return;
783 lockCopyHash = true;
784 var link = document.getElementById("copyhash");
785 if( link ){
786 var hash = link.innerText;
787 copyTextToClipboard(hash);
788 }
789 lockCopyHash = false;
790 }
791 /* Create a temporary <textarea> element and copy the contents to clipboard. */
792 function copyTextToClipboard(text){
793 var textArea = document.createElement("textarea");
794 textArea.style.position = 'fixed';
795 textArea.style.top = 0;
796 textArea.style.left = 0;
797 textArea.style.width = '2em';
798 textArea.style.height = '2em';
799 textArea.style.padding = 0;
800 textArea.style.border = 'none';
801 textArea.style.outline = 'none';
802 textArea.style.boxShadow = 'none';
803 textArea.style.background = 'transparent';
804 textArea.value = text;
805 document.body.appendChild(textArea);
806 textArea.focus();
807 textArea.select();
808 try{
809 document.execCommand('copy');
810 }catch(err){
811 }
812 document.body.removeChild(textArea);
813 }
814

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button