##// END OF EJS Templates
hgweb: add a link to followlines in descending direction...
Denis Laxalde -
r31940:6ce09d2c default
parent child Browse files
Show More
@@ -1,219 +1,229 b''
1 // followlines.js - JavaScript utilities for followlines UI
1 // followlines.js - JavaScript utilities for followlines UI
2 //
2 //
3 // Copyright 2017 Logilab SA <contact@logilab.fr>
3 // Copyright 2017 Logilab SA <contact@logilab.fr>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //** Install event listeners for line block selection and followlines action */
8 //** Install event listeners for line block selection and followlines action */
9 document.addEventListener('DOMContentLoaded', function() {
9 document.addEventListener('DOMContentLoaded', function() {
10 var sourcelines = document.getElementsByClassName('sourcelines')[0];
10 var sourcelines = document.getElementsByClassName('sourcelines')[0];
11 if (typeof sourcelines === 'undefined') {
11 if (typeof sourcelines === 'undefined') {
12 return;
12 return;
13 }
13 }
14 // URL to complement with "linerange" query parameter
14 // URL to complement with "linerange" query parameter
15 var targetUri = sourcelines.dataset.logurl;
15 var targetUri = sourcelines.dataset.logurl;
16 if (typeof targetUri === 'undefined')Β {
16 if (typeof targetUri === 'undefined')Β {
17 return;
17 return;
18 }
18 }
19
19
20 // tooltip to invite on lines selection
20 // tooltip to invite on lines selection
21 var tooltip = document.createElement('div');
21 var tooltip = document.createElement('div');
22 tooltip.id = 'followlines-tooltip';
22 tooltip.id = 'followlines-tooltip';
23 tooltip.classList.add('hidden');
23 tooltip.classList.add('hidden');
24 var initTooltipText = 'click to start following lines history from here';
24 var initTooltipText = 'click to start following lines history from here';
25 tooltip.textContent = initTooltipText;
25 tooltip.textContent = initTooltipText;
26 sourcelines.appendChild(tooltip);
26 sourcelines.appendChild(tooltip);
27
27
28 //* position "element" on top-right of cursor */
28 //* position "element" on top-right of cursor */
29 function positionTopRight(element, event) {
29 function positionTopRight(element, event) {
30 var x = (event.clientX + 10) + 'px',
30 var x = (event.clientX + 10) + 'px',
31 y = (event.clientY - 20) + 'px';
31 y = (event.clientY - 20) + 'px';
32 element.style.top = y;
32 element.style.top = y;
33 element.style.left = x;
33 element.style.left = x;
34 }
34 }
35
35
36 var tooltipTimeoutID;
36 var tooltipTimeoutID;
37 //* move the "tooltip" with cursor (top-right) and show it after 1s */
37 //* move the "tooltip" with cursor (top-right) and show it after 1s */
38 function moveAndShowTooltip(e) {
38 function moveAndShowTooltip(e) {
39 if (typeof tooltipTimeoutID !== 'undefined') {
39 if (typeof tooltipTimeoutID !== 'undefined') {
40 // avoid accumulation of timeout callbacks (blinking)
40 // avoid accumulation of timeout callbacks (blinking)
41 window.clearTimeout(tooltipTimeoutID);
41 window.clearTimeout(tooltipTimeoutID);
42 }
42 }
43 tooltip.classList.add('hidden');
43 tooltip.classList.add('hidden');
44 positionTopRight(tooltip, e);
44 positionTopRight(tooltip, e);
45 tooltipTimeoutID = window.setTimeout(function() {
45 tooltipTimeoutID = window.setTimeout(function() {
46 tooltip.classList.remove('hidden');
46 tooltip.classList.remove('hidden');
47 }, 1000);
47 }, 1000);
48 }
48 }
49
49
50 // on mousemove, show tooltip close to cursor position
50 // on mousemove, show tooltip close to cursor position
51 sourcelines.addEventListener('mousemove', moveAndShowTooltip);
51 sourcelines.addEventListener('mousemove', moveAndShowTooltip);
52
52
53 // retrieve all direct <span> children of <pre class="sourcelines">
53 // retrieve all direct <span> children of <pre class="sourcelines">
54 var spans = Array.prototype.filter.call(
54 var spans = Array.prototype.filter.call(
55 sourcelines.children,
55 sourcelines.children,
56 function(x) { return x.tagName === 'SPAN' });
56 function(x) { return x.tagName === 'SPAN' });
57
57
58 // add a "followlines-select" class to change cursor type in CSS
58 // add a "followlines-select" class to change cursor type in CSS
59 for (var i = 0; i < spans.length; i++) {
59 for (var i = 0; i < spans.length; i++) {
60 spans[i].classList.add('followlines-select');
60 spans[i].classList.add('followlines-select');
61 }
61 }
62
62
63 var lineSelectedCSSClass = 'followlines-selected';
63 var lineSelectedCSSClass = 'followlines-selected';
64
64
65 //** add CSS class on <span> element in `from`-`to` line range */
65 //** add CSS class on <span> element in `from`-`to` line range */
66 function addSelectedCSSClass(from, to) {
66 function addSelectedCSSClass(from, to) {
67 for (var i = from; i <= to; i++) {
67 for (var i = from; i <= to; i++) {
68 spans[i].classList.add(lineSelectedCSSClass);
68 spans[i].classList.add(lineSelectedCSSClass);
69 }
69 }
70 }
70 }
71
71
72 //** remove CSS class from previously selected lines */
72 //** remove CSS class from previously selected lines */
73 function removeSelectedCSSClass() {
73 function removeSelectedCSSClass() {
74 var elements = sourcelines.getElementsByClassName(
74 var elements = sourcelines.getElementsByClassName(
75 lineSelectedCSSClass);
75 lineSelectedCSSClass);
76 while (elements.length) {
76 while (elements.length) {
77 elements[0].classList.remove(lineSelectedCSSClass);
77 elements[0].classList.remove(lineSelectedCSSClass);
78 }
78 }
79 }
79 }
80
80
81 // ** return the <span> element parent of `element` */
81 // ** return the <span> element parent of `element` */
82 function findParentSpan(element) {
82 function findParentSpan(element) {
83 var parent = element.parentElement;
83 var parent = element.parentElement;
84 if (parent === null) {
84 if (parent === null) {
85 return null;
85 return null;
86 }
86 }
87 if (element.tagName == 'SPAN' && parent.isSameNode(sourcelines)) {
87 if (element.tagName == 'SPAN' && parent.isSameNode(sourcelines)) {
88 return element;
88 return element;
89 }
89 }
90 return findParentSpan(parent);
90 return findParentSpan(parent);
91 }
91 }
92
92
93 //** event handler for "click" on the first line of a block */
93 //** event handler for "click" on the first line of a block */
94 function lineSelectStart(e) {
94 function lineSelectStart(e) {
95 var startElement = findParentSpan(e.target);
95 var startElement = findParentSpan(e.target);
96 if (startElement === null) {
96 if (startElement === null) {
97 // not a <span> (maybe <a>): abort, keeping event listener
97 // not a <span> (maybe <a>): abort, keeping event listener
98 // registered for other click with <span> target
98 // registered for other click with <span> target
99 return;
99 return;
100 }
100 }
101
101
102 // update tooltip text
102 // update tooltip text
103 tooltip.textContent = 'click again to terminate line block selection here';
103 tooltip.textContent = 'click again to terminate line block selection here';
104
104
105 var startId = parseInt(startElement.id.slice(1));
105 var startId = parseInt(startElement.id.slice(1));
106 startElement.classList.add(lineSelectedCSSClass); // CSS
106 startElement.classList.add(lineSelectedCSSClass); // CSS
107
107
108 // remove this event listener
108 // remove this event listener
109 sourcelines.removeEventListener('click', lineSelectStart);
109 sourcelines.removeEventListener('click', lineSelectStart);
110
110
111 //** event handler for "click" on the last line of the block */
111 //** event handler for "click" on the last line of the block */
112 function lineSelectEnd(e) {
112 function lineSelectEnd(e) {
113 var endElement = findParentSpan(e.target);
113 var endElement = findParentSpan(e.target);
114 if (endElement === null) {
114 if (endElement === null) {
115 // not a <span> (maybe <a>): abort, keeping event listener
115 // not a <span> (maybe <a>): abort, keeping event listener
116 // registered for other click with <span> target
116 // registered for other click with <span> target
117 return;
117 return;
118 }
118 }
119
119
120 // remove this event listener
120 // remove this event listener
121 sourcelines.removeEventListener('click', lineSelectEnd);
121 sourcelines.removeEventListener('click', lineSelectEnd);
122
122
123 // hide tooltip and disable motion tracking
123 // hide tooltip and disable motion tracking
124 tooltip.classList.add('hidden');
124 tooltip.classList.add('hidden');
125 sourcelines.removeEventListener('mousemove', moveAndShowTooltip);
125 sourcelines.removeEventListener('mousemove', moveAndShowTooltip);
126 window.clearTimeout(tooltipTimeoutID);
126 window.clearTimeout(tooltipTimeoutID);
127
127
128 //* restore initial "tooltip" state */
128 //* restore initial "tooltip" state */
129 function restoreTooltip() {
129 function restoreTooltip() {
130 tooltip.textContent = initTooltipText;
130 tooltip.textContent = initTooltipText;
131 sourcelines.addEventListener('mousemove', moveAndShowTooltip);
131 sourcelines.addEventListener('mousemove', moveAndShowTooltip);
132 }
132 }
133
133
134 // compute line range (startId, endId)
134 // compute line range (startId, endId)
135 var endId = parseInt(endElement.id.slice(1));
135 var endId = parseInt(endElement.id.slice(1));
136 if (endId == startId) {
136 if (endId == startId) {
137 // clicked twice the same line, cancel and reset initial state
137 // clicked twice the same line, cancel and reset initial state
138 // (CSS, event listener for selection start, tooltip)
138 // (CSS, event listener for selection start, tooltip)
139 removeSelectedCSSClass();
139 removeSelectedCSSClass();
140 sourcelines.addEventListener('click', lineSelectStart);
140 sourcelines.addEventListener('click', lineSelectStart);
141 restoreTooltip();
141 restoreTooltip();
142 return;
142 return;
143 }
143 }
144 var inviteElement = endElement;
144 var inviteElement = endElement;
145 if (endId < startId) {
145 if (endId < startId) {
146 var tmp = endId;
146 var tmp = endId;
147 endId = startId;
147 endId = startId;
148 startId = tmp;
148 startId = tmp;
149 inviteElement = startElement;
149 inviteElement = startElement;
150 }
150 }
151
151
152 addSelectedCSSClass(startId - 1, endId -1); // CSS
152 addSelectedCSSClass(startId - 1, endId -1); // CSS
153
153
154 // append the <div id="followlines"> element to last line of the
154 // append the <div id="followlines"> element to last line of the
155 // selection block
155 // selection block
156 var divAndButton = followlinesBox(targetUri, startId, endId);
156 var divAndButton = followlinesBox(targetUri, startId, endId);
157 var div = divAndButton[0],
157 var div = divAndButton[0],
158 button = divAndButton[1];
158 button = divAndButton[1];
159 inviteElement.appendChild(div);
159 inviteElement.appendChild(div);
160 // set position close to cursor (top-right)
160 // set position close to cursor (top-right)
161 positionTopRight(div, e);
161 positionTopRight(div, e);
162
162
163 //** event handler for cancelling selection */
163 //** event handler for cancelling selection */
164 function cancel() {
164 function cancel() {
165 // remove invite box
165 // remove invite box
166 div.parentNode.removeChild(div);
166 div.parentNode.removeChild(div);
167 // restore initial event listeners
167 // restore initial event listeners
168 sourcelines.addEventListener('click', lineSelectStart);
168 sourcelines.addEventListener('click', lineSelectStart);
169 sourcelines.removeEventListener('click', cancel);
169 sourcelines.removeEventListener('click', cancel);
170 // remove styles on selected lines
170 // remove styles on selected lines
171 removeSelectedCSSClass();
171 removeSelectedCSSClass();
172 // restore tooltip element
172 // restore tooltip element
173 restoreTooltip();
173 restoreTooltip();
174 }
174 }
175
175
176 // bind cancel event to click on <button>
176 // bind cancel event to click on <button>
177 button.addEventListener('click', cancel);
177 button.addEventListener('click', cancel);
178 // as well as on an click on any source line
178 // as well as on an click on any source line
179 sourcelines.addEventListener('click', cancel);
179 sourcelines.addEventListener('click', cancel);
180 }
180 }
181
181
182 sourcelines.addEventListener('click', lineSelectEnd);
182 sourcelines.addEventListener('click', lineSelectEnd);
183
183
184 }
184 }
185
185
186 sourcelines.addEventListener('click', lineSelectStart);
186 sourcelines.addEventListener('click', lineSelectStart);
187
187
188 //** return a <div id="followlines"> and inner cancel <button> elements */
188 //** return a <div id="followlines"> and inner cancel <button> elements */
189 function followlinesBox(targetUri, fromline, toline) {
189 function followlinesBox(targetUri, fromline, toline) {
190 // <div id="followlines">
190 // <div id="followlines">
191 var div = document.createElement('div');
191 var div = document.createElement('div');
192 div.id = 'followlines';
192 div.id = 'followlines';
193
193
194 // <div class="followlines-cancel">
194 // <div class="followlines-cancel">
195 var buttonDiv = document.createElement('div');
195 var buttonDiv = document.createElement('div');
196 buttonDiv.classList.add('followlines-cancel');
196 buttonDiv.classList.add('followlines-cancel');
197
197
198 // <button>x</button>
198 // <button>x</button>
199 var button = document.createElement('button');
199 var button = document.createElement('button');
200 button.textContent = 'x';
200 button.textContent = 'x';
201 buttonDiv.appendChild(button);
201 buttonDiv.appendChild(button);
202 div.appendChild(buttonDiv);
202 div.appendChild(buttonDiv);
203
203
204 // <div class="followlines-link">
204 // <div class="followlines-link">
205 var aDiv = document.createElement('div');
205 var aDiv = document.createElement('div');
206 aDiv.classList.add('followlines-link');
206 aDiv.classList.add('followlines-link');
207
207 aDiv.textContent = 'follow history of lines ' + fromline + ':' + toline + ':';
208 // <a href="/log/<rev>/<file>?patch=&linerange=...">
208 var linesep = document.createElement('br');
209 var a = document.createElement('a');
209 aDiv.appendChild(linesep);
210 // link to "ascending" followlines
211 var aAsc = document.createElement('a');
210 var url = targetUri + '?patch=&linerange=' + fromline + ':' + toline;
212 var url = targetUri + '?patch=&linerange=' + fromline + ':' + toline;
211 a.setAttribute('href', url);
213 aAsc.setAttribute('href', url);
212 a.textContent = 'follow lines ' + fromline + ':' + toline;
214 aAsc.textContent = 'ascending';
213 aDiv.appendChild(a);
215 aDiv.appendChild(aAsc);
216 var sep = document.createTextNode(' / ');
217 aDiv.appendChild(sep);
218 // link to "descending" followlines
219 var aDesc = document.createElement('a');
220 aDesc.setAttribute('href', url + '&descend=');
221 aDesc.textContent = 'descending';
222 aDiv.appendChild(aDesc);
223
214 div.appendChild(aDiv);
224 div.appendChild(aDiv);
215
225
216 return [div, button];
226 return [div, button];
217 }
227 }
218
228
219 }, false);
229 }, false);
General Comments 0
You need to be logged in to leave comments. Login now