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.
Commit
371943c936e649b62e4af5f8e6f8d5e9323d30d18d5f487d3c218fd361d9f457
Parent
d57c1a797ce9cff…
1 file changed
+68
-3
+68
-3
| --- src/graph.js | ||
| +++ src/graph.js | ||
| @@ -102,10 +102,11 @@ | ||
| 102 | 102 | idTimer: 0, /* The tooltip dwell timer id. */ |
| 103 | 103 | idTimerClose: 0, /* The tooltip close timer id. */ |
| 104 | 104 | ixHover: -1, /* The id of the element with the mouse. */ |
| 105 | 105 | ixActive: -1, /* The id of the element with the tooltip. */ |
| 106 | 106 | nodeHover: null, /* Graph node under mouse when ixHover==-2 */ |
| 107 | + imgCopy: null, /* The image for the "Copy Hash" icon. */ | |
| 107 | 108 | posX: 0, posY: 0 /* The last mouse position. */ |
| 108 | 109 | }; |
| 109 | 110 | |
| 110 | 111 | /* Functions used to control the tooltip popup and its timer */ |
| 111 | 112 | function hideGraphTooltip(){ |
| @@ -595,13 +596,13 @@ | ||
| 595 | 596 | if( tooltipInfo.ixHover==-2 ){ |
| 596 | 597 | ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow |
| 597 | 598 | var h = tx.rowinfo[ix].h |
| 598 | 599 | var dest = tx.baseUrl + "/info/" + h |
| 599 | 600 | if( tx.fileDiff ){ |
| 600 | - html = "artifact <a href=\""+dest+"\">"+h+"</a>" | |
| 601 | + html = "artifact <a id=\"copyhash\" href=\""+dest+"\">"+h+"</a>" | |
| 601 | 602 | }else{ |
| 602 | - html = "check-in <a href=\""+dest+"\">"+h+"</a>" | |
| 603 | + html = "check-in <a id=\"copyhash\" href=\""+dest+"\">"+h+"</a>" | |
| 603 | 604 | } |
| 604 | 605 | tooltipInfo.ixActive = -2; |
| 605 | 606 | }else if( tooltipInfo.ixHover>=0 ){ |
| 606 | 607 | ix = tooltipInfo.ixHover |
| 607 | 608 | var br = tx.rowinfo[ix].br |
| @@ -609,15 +610,36 @@ | ||
| 609 | 610 | var hbr = br.replace(/&/g, "&") |
| 610 | 611 | .replace(/</g, "<") |
| 611 | 612 | .replace(/>/g, ">") |
| 612 | 613 | .replace(/"/g, """) |
| 613 | 614 | .replace(/'/g, "'"); |
| 614 | - html = "branch <a href=\""+dest+"\">"+hbr+"</a>" | |
| 615 | + html = "branch <a id=\"copyhash\" href=\""+dest+"\">"+hbr+"</a>" | |
| 615 | 616 | tooltipInfo.ixActive = ix; |
| 616 | 617 | } |
| 617 | 618 | if( html ){ |
| 618 | 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 | + } | |
| 619 | 641 | var s = getComputedStyle(document.body) |
| 620 | 642 | if( tx.rowinfo[ix].bg.length ){ |
| 621 | 643 | tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg |
| 622 | 644 | }else{ |
| 623 | 645 | tooltipObj.style.backgroundColor = s.getPropertyValue('background-color') |
| @@ -624,10 +646,15 @@ | ||
| 624 | 646 | } |
| 625 | 647 | tooltipObj.style.borderColor = |
| 626 | 648 | tooltipObj.style.color = s.getPropertyValue('color') |
| 627 | 649 | tooltipObj.style.visibility = "hidden" |
| 628 | 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; | |
| 629 | 656 | tooltipObj.style.display = "inline" |
| 630 | 657 | tooltipObj.style.position = "absolute" |
| 631 | 658 | var x = tooltipInfo.posX + 4 + window.pageXOffset |
| 632 | 659 | - absoluteX(tooltipObj.offsetParent) |
| 633 | 660 | tooltipObj.style.left = x+"px" |
| @@ -744,5 +771,43 @@ | ||
| 744 | 771 | var txJson = dataObj.textContent || dataObj.innerText; |
| 745 | 772 | var tx = JSON.parse(txJson); |
| 746 | 773 | TimelineGraph(tx); |
| 747 | 774 | } |
| 748 | 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 | +} | |
| 749 | 814 |
| --- 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, "&") |
| 610 | .replace(/</g, "<") |
| 611 | .replace(/>/g, ">") |
| 612 | .replace(/"/g, """) |
| 613 | .replace(/'/g, "'"); |
| 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, "&") |
| 611 | .replace(/</g, "<") |
| 612 | .replace(/>/g, ">") |
| 613 | .replace(/"/g, """) |
| 614 | .replace(/'/g, "'"); |
| 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 |