| | @@ -1,9 +1,13 @@ |
| 1 | 1 | (function callee(arg){ |
| 2 | | - /* JS counterpart of info.c:output_text_with_line_numbers() |
| 3 | | - which ties an event handler to the line numbers to allow |
| 4 | | - selection of individual lines or ranges. */ |
| 2 | + /* |
| 3 | + JS counterpart of info.c:output_text_with_line_numbers() |
| 4 | + which ties an event handler to the line numbers to allow |
| 5 | + selection of individual lines or ranges. |
| 6 | + |
| 7 | + Requires: fossil.bootstrap, fossil.dom, fossil.tooltip |
| 8 | + */ |
| 5 | 9 | var tbl = arg || document.querySelectorAll('table.numbered-lines'); |
| 6 | 10 | if(!tbl) return /* no matching elements */; |
| 7 | 11 | else if(!arg){ |
| 8 | 12 | if(tbl.length>1){ /* multiple query results: recurse */ |
| 9 | 13 | tbl.forEach( (t)=>callee(t) ); |
| | @@ -10,47 +14,105 @@ |
| 10 | 14 | return; |
| 11 | 15 | }else{/* single query result */ |
| 12 | 16 | tbl = tbl[0]; |
| 13 | 17 | } |
| 14 | 18 | } |
| 15 | | - const tdLn = tbl.querySelector('td'), |
| 16 | | - urlArgs = (window.location.search||'').replace(/&?\bln=[^&]*/,''); |
| 17 | | - console.debug("urlArgs =",urlArgs); |
| 19 | + const F = window.fossil, D = F.dom; |
| 20 | + const tdLn = tbl.querySelector('td.line-numbers'); |
| 21 | + const lineState = { |
| 22 | + urlArgs: (window.location.search||'').replace(/&?\bln=[^&]*/,''), |
| 23 | + start: 0, end: 0 |
| 24 | + }; |
| 25 | + |
| 26 | + const lineTip = new fossil.TooltipWidget({ |
| 27 | + refresh: function(){ |
| 28 | + const link = this.state.link; |
| 29 | + D.clearElement(link); |
| 30 | + if(lineState.start){ |
| 31 | + const ls = [lineState.start]; |
| 32 | + if(lineState.end) ls.push(lineState.end); |
| 33 | + link.dataset.url = ( |
| 34 | + window.location.toString().split('?')[0] |
| 35 | + + lineState.urlArgs + '&ln='+ls.join('-') |
| 36 | + ); |
| 37 | + D.append( |
| 38 | + D.clearElement(link), |
| 39 | + ' ', |
| 40 | + (ls.length===1 ? 'line ' : 'lines ')+ls.join('-') |
| 41 | + ); |
| 42 | + }else{ |
| 43 | + D.append(link, "No lines selected."); |
| 44 | + } |
| 45 | + }, |
| 46 | + adjustX: function(x){ |
| 47 | + return x + 20; |
| 48 | + }, |
| 49 | + adjustY: function(y){ |
| 50 | + return y - this.e.clientHeight/2; |
| 51 | + }, |
| 52 | + init: function(){ |
| 53 | + const e = this.e; |
| 54 | + const btnCopy = D.addClass(D.span(), 'copy-button'); |
| 55 | + const link = D.attr(D.span(), 'id', 'fossil-ln-link'); |
| 56 | + this.state = {link}; |
| 57 | + F.copyButton(btnCopy,{ |
| 58 | + copyFromElement: link, |
| 59 | + extractText: ()=>link.dataset.url |
| 60 | + }); |
| 61 | + D.append(this.e, btnCopy, link) |
| 62 | + } |
| 63 | + }); |
| 64 | + |
| 65 | + tbl.addEventListener('click', function f(ev){ |
| 66 | + lineTip.show(false); |
| 67 | + }, false); |
| 68 | + |
| 18 | 69 | tdLn.addEventListener('click', function f(ev){ |
| 19 | | - if(!f.selectedRange){ |
| 20 | | - f.selectedRange = [0,0]; |
| 70 | + if('SPAN'!==ev.target.tagName) return; |
| 71 | + else if('number' !== typeof f.mode){ |
| 21 | 72 | f.mode = 0 /*0=none selected, 1=1 selected, 2=2 selected*/; |
| 22 | 73 | } |
| 23 | | - if('SPAN'===ev.target.tagName){ |
| 24 | | - const rng = f.selectedRange; |
| 25 | | - const ln = +ev.target.innerText; |
| 26 | | - if(2===f.mode){/*reset selection*/ |
| 27 | | - f.mode = 0; |
| 28 | | - //rng[0] = rng[1] = 0; |
| 29 | | - } |
| 30 | | - if(0===f.mode){ |
| 31 | | - rng[1] = 0; |
| 32 | | - rng[0] = ln; |
| 33 | | - //console.debug("Selected line #"+ln); |
| 34 | | - history.pushState(undefined,'',urlArgs+'&ln='+ln); |
| 35 | | - f.mode = 1; |
| 36 | | - }else if(1===f.mode){ |
| 37 | | - if(ln === rng[0]){/*unselect line*/ |
| 38 | | - //console.debug("Unselected line #"+ln); |
| 39 | | - history.pushState(undefined,'',urlArgs+'&ln=on'); |
| 40 | | - rng[0] = 0; |
| 41 | | - f.mode = 0; |
| 42 | | - }else{ |
| 43 | | - if(ln<rng[0]){ |
| 44 | | - rng[1] = rng[0]; |
| 45 | | - rng[0] = ln; |
| 46 | | - }else{ |
| 47 | | - rng[1] = ln; |
| 48 | | - } |
| 49 | | - //console.debug("Selected range: ",rng); |
| 50 | | - history.pushState(undefined,'',urlArgs+'&ln='+rng.join('-')); |
| 51 | | - f.mode = 2; |
| 52 | | - } |
| 53 | | - } |
| 74 | + ev.stopPropagation(); |
| 75 | + const ln = +ev.target.innerText; |
| 76 | + if(2===f.mode){/*reset selection*/ |
| 77 | + f.mode = 0; |
| 78 | + } |
| 79 | + if(0===f.mode){ |
| 80 | + lineState.end = 0; |
| 81 | + lineState.start = ln; |
| 82 | + f.mode = 1; |
| 83 | + }else if(1===f.mode){ |
| 84 | + if(ln === lineState.start){/*unselect line*/ |
| 85 | + //console.debug("Unselected line #"+ln); |
| 86 | + lineState.start = 0; |
| 87 | + f.mode = 0; |
| 88 | + }else{ |
| 89 | + if(ln<lineState.start){ |
| 90 | + lineState.end = lineState.start; |
| 91 | + lineState.start = ln; |
| 92 | + }else{ |
| 93 | + lineState.end = ln; |
| 94 | + } |
| 95 | + //console.debug("Selected range: ",rng); |
| 96 | + f.mode = 2; |
| 97 | + } |
| 98 | + } |
| 99 | + tdLn.querySelectorAll('span.selected-line').forEach( |
| 100 | + (e)=>D.removeClass(e, 'selected-line','start','end')); |
| 101 | + if(f.mode>0){ |
| 102 | + lineTip.show(ev.clientX, ev.clientY); |
| 103 | + const spans = tdLn.querySelectorAll('span'); |
| 104 | + if(spans.length>=lineState.start){ |
| 105 | + let i = lineState.start, end = lineState.end || lineState.start, span = spans[i-1]; |
| 106 | + for( ; i<=end && span; span = spans[i++] ){ |
| 107 | + span.classList.add('selected-line'); |
| 108 | + if(i===lineState.start) span.classList.add('start'); |
| 109 | + if(i===end) span.classList.add('end'); |
| 110 | + } |
| 111 | + } |
| 112 | + lineTip.refresh(); |
| 113 | + }else{ |
| 114 | + lineTip.show(false); |
| 54 | 115 | } |
| 55 | 116 | }, false); |
| 117 | + |
| 56 | 118 | })(); |
| 57 | 119 | |
| 58 | 120 | ADDED src/fossil.tooltip.js |