Fossil SCM

fossil-scm / src / fossil.numbered-lines.js
Blame History Raw 121 lines
1
(function callee(arg){
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.popupwidget,
8
fossil.copybutton
9
*/
10
var tbl = arg || document.querySelectorAll('table.numbered-lines');
11
if(tbl && !arg){
12
if(tbl.length>1){ /* multiple query results: recurse */
13
tbl.forEach( (t)=>callee(t) );
14
return;
15
}else{/* single query result */
16
tbl = tbl[0];
17
}
18
}
19
if(!tbl) return /* no matching elements */;
20
const F = window.fossil, D = F.dom;
21
const tdLn = tbl.querySelector('td.line-numbers');
22
const urlArgsRaw = (window.location.search||'?')
23
.replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
24
.replace(/&?\bln=[^&]*/,'') /* inbound line number/range */
25
.replace('?&','?');
26
const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 };
27
const lineTip = new F.PopupWidget({
28
refresh: function(){
29
const link = this.state.link;
30
D.clearElement(link);
31
if(lineState.start){
32
const ls = [lineState.start];
33
if(lineState.end) ls.push(lineState.end);
34
link.dataset.url = (
35
window.location.toString().split('?')[0]
36
+ lineState.urlArgs + '&ln='+ls.join('-')
37
);
38
D.append(
39
D.clearElement(link),
40
' Copy link to '+(
41
ls.length===1 ? 'line ' : 'lines '
42
)+ls.join('-')
43
);
44
}else{
45
D.append(link, "No lines selected.");
46
}
47
},
48
init: function(){
49
const e = this.e;
50
const btnCopy = D.attr(D.button(), 'id', 'linenum-copy-button');
51
link = D.label('linenum-copy-button');
52
this.state = {link};
53
F.copyButton(btnCopy,{
54
copyFromElement: link,
55
extractText: ()=>link.dataset.url,
56
oncopy: (ev)=>{
57
setTimeout(()=>lineTip.hide(), 400);
58
// arguably too snazzy: F.toast.message("Copied link to clipboard.");
59
}
60
});
61
D.append(this.e, btnCopy, link);
62
}
63
});
64
65
tbl.addEventListener('click', ()=>lineTip.hide(), true);
66
67
tdLn.addEventListener('click', function f(ev){
68
if('SPAN'!==ev.target.tagName) return;
69
else if('number' !== typeof f.mode){
70
f.mode = 0 /*0=none selected, 1=1 selected, 2=2 selected*/;
71
f.spans = tdLn.querySelectorAll('span');
72
f.selected = tdLn.querySelectorAll('span.selected-line');
73
f.unselect = (e)=>D.removeClass(e, 'selected-line','start','end');
74
}
75
ev.stopPropagation();
76
const ln = +ev.target.innerText;
77
if(2===f.mode){/*Reset selection*/
78
f.mode = 0;
79
}
80
if(0===f.mode){/*Select single line*/
81
lineState.end = 0;
82
lineState.start = ln;
83
f.mode = 1;
84
}else if(1===f.mode){
85
if(ln === lineState.start){/*Unselect line*/
86
lineState.start = 0;
87
f.mode = 0;
88
}else{/*Select range*/
89
if(ln<lineState.start){
90
lineState.end = lineState.start;
91
lineState.start = ln;
92
}else{
93
lineState.end = ln;
94
}
95
f.mode = 2;
96
}
97
}
98
if(f.selected){/*Unmark previously-selected lines.*/
99
f.selected.forEach(f.unselect);
100
f.selected = undefined;
101
}
102
if(0===f.mode){
103
lineTip.hide();
104
}else{/*Mark selected lines*/
105
const rect = ev.target.getBoundingClientRect();
106
f.selected = [];
107
if(f.spans.length>=lineState.start){
108
let i = lineState.start, end = lineState.end || lineState.start, span = f.spans[i-1];
109
for( ; i<=end && span; span = f.spans[i++] ){
110
span.classList.add('selected-line');
111
f.selected.push(span);
112
if(i===lineState.start) span.classList.add('start');
113
if(i===end) span.classList.add('end');
114
}
115
}
116
lineTip.refresh().show(rect.right+3, rect.top-4);
117
}
118
}, false);
119
120
})();
121

Keyboard Shortcuts

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