##// END OF EJS Templates
hgweb: add a floating tooltip to invite on followlines action...
Denis Laxalde -
r31848:7160bdd5 default
parent child Browse files
Show More
@@ -1,166 +1,212
1 1 // followlines.js - JavaScript utilities for followlines UI
2 2 //
3 3 // Copyright 2017 Logilab SA <contact@logilab.fr>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //** Install event listeners for line block selection and followlines action */
9 9 document.addEventListener('DOMContentLoaded', function() {
10 10 var sourcelines = document.getElementsByClassName('sourcelines')[0];
11 11 if (typeof sourcelines === 'undefined') {
12 12 return;
13 13 }
14 14 // URL to complement with "linerange" query parameter
15 15 var targetUri = sourcelines.dataset.logurl;
16 16 if (typeof targetUri === 'undefined')Β {
17 17 return;
18 18 }
19 19
20 // tooltip to invite on lines selection
21 var tooltip = document.createElement('div');
22 tooltip.id = 'followlines-tooltip';
23 tooltip.classList.add('hidden');
24 var initTooltipText = 'click to start following lines history from here';
25 tooltip.textContent = initTooltipText;
26 sourcelines.appendChild(tooltip);
27
28 var tooltipTimeoutID;
29 //* move the "tooltip" with cursor (top-right) and show it after 1s */
30 function moveAndShowTooltip(e) {
31 if (typeof tooltipTimeoutID !== 'undefined') {
32 // avoid accumulation of timeout callbacks (blinking)
33 window.clearTimeout(tooltipTimeoutID);
34 }
35 tooltip.classList.add('hidden');
36 var x = (e.clientX + 10) + 'px',
37 y = (e.clientY - 20) + 'px';
38 tooltip.style.top = y;
39 tooltip.style.left = x;
40 tooltipTimeoutID = window.setTimeout(function() {
41 tooltip.classList.remove('hidden');
42 }, 1000);
43 }
44
45 // on mousemove, show tooltip close to cursor position
46 sourcelines.addEventListener('mousemove', moveAndShowTooltip);
47
20 48 // retrieve all direct <span> children of <pre class="sourcelines">
21 49 var spans = Array.prototype.filter.call(
22 50 sourcelines.children,
23 51 function(x) { return x.tagName === 'SPAN' });
24 52
25 53 // add a "followlines-select" class to change cursor type in CSS
26 54 for (var i = 0; i < spans.length; i++) {
27 55 spans[i].classList.add('followlines-select');
28 56 }
29 57
30 58 var lineSelectedCSSClass = 'followlines-selected';
31 59
32 60 //** add CSS class on <span> element in `from`-`to` line range */
33 61 function addSelectedCSSClass(from, to) {
34 62 for (var i = from; i <= to; i++) {
35 63 spans[i].classList.add(lineSelectedCSSClass);
36 64 }
37 65 }
38 66
39 67 //** remove CSS class from previously selected lines */
40 68 function removeSelectedCSSClass() {
41 69 var elements = sourcelines.getElementsByClassName(
42 70 lineSelectedCSSClass);
43 71 while (elements.length) {
44 72 elements[0].classList.remove(lineSelectedCSSClass);
45 73 }
46 74 }
47 75
48 76 // ** return the <span> element parent of `element` */
49 77 function findParentSpan(element) {
50 78 var parent = element.parentElement;
51 79 if (parent === null) {
52 80 return null;
53 81 }
54 82 if (element.tagName == 'SPAN' && parent.isSameNode(sourcelines)) {
55 83 return element;
56 84 }
57 85 return findParentSpan(parent);
58 86 }
59 87
60 88 //** event handler for "click" on the first line of a block */
61 89 function lineSelectStart(e) {
62 90 var startElement = findParentSpan(e.target);
63 91 if (startElement === null) {
64 92 // not a <span> (maybe <a>): abort, keeping event listener
65 93 // registered for other click with <span> target
66 94 return;
67 95 }
96
97 // update tooltip text
98 tooltip.textContent = 'click again to terminate line block selection here';
99
68 100 var startId = parseInt(startElement.id.slice(1));
69 101 startElement.classList.add(lineSelectedCSSClass); // CSS
70 102
71 103 // remove this event listener
72 104 sourcelines.removeEventListener('click', lineSelectStart);
73 105
74 106 //** event handler for "click" on the last line of the block */
75 107 function lineSelectEnd(e) {
76 108 var endElement = findParentSpan(e.target);
77 109 if (endElement === null) {
78 110 // not a <span> (maybe <a>): abort, keeping event listener
79 111 // registered for other click with <span> target
80 112 return;
81 113 }
82 114
83 115 // remove this event listener
84 116 sourcelines.removeEventListener('click', lineSelectEnd);
85 117
118 // hide tooltip and disable motion tracking
119 tooltip.classList.add('hidden');
120 sourcelines.removeEventListener('mousemove', moveAndShowTooltip);
121 window.clearTimeout(tooltipTimeoutID);
122
123 //* restore initial "tooltip" state */
124 function restoreTooltip() {
125 tooltip.textContent = initTooltipText;
126 sourcelines.addEventListener('mousemove', moveAndShowTooltip);
127 }
128
86 129 // compute line range (startId, endId)
87 130 var endId = parseInt(endElement.id.slice(1));
88 131 if (endId == startId) {
89 132 // clicked twice the same line, cancel and reset initial state
90 // (CSS and event listener for selection start)
133 // (CSS, event listener for selection start, tooltip)
91 134 removeSelectedCSSClass();
92 135 sourcelines.addEventListener('click', lineSelectStart);
136 restoreTooltip();
93 137 return;
94 138 }
95 139 var inviteElement = endElement;
96 140 if (endId < startId) {
97 141 var tmp = endId;
98 142 endId = startId;
99 143 startId = tmp;
100 144 inviteElement = startElement;
101 145 }
102 146
103 147 addSelectedCSSClass(startId - 1, endId -1); // CSS
104 148
105 149 // append the <div id="followlines"> element to last line of the
106 150 // selection block
107 151 var divAndButton = followlinesBox(targetUri, startId, endId);
108 152 var div = divAndButton[0],
109 153 button = divAndButton[1];
110 154 inviteElement.appendChild(div);
111 155
112 156 //** event handler for cancelling selection */
113 157 function cancel() {
114 158 // remove invite box
115 159 div.parentNode.removeChild(div);
116 160 // restore initial event listeners
117 161 sourcelines.addEventListener('click', lineSelectStart);
118 162 sourcelines.removeEventListener('click', cancel);
119 163 // remove styles on selected lines
120 164 removeSelectedCSSClass();
165 // restore tooltip element
166 restoreTooltip();
121 167 }
122 168
123 169 // bind cancel event to click on <button>
124 170 button.addEventListener('click', cancel);
125 171 // as well as on an click on any source line
126 172 sourcelines.addEventListener('click', cancel);
127 173 }
128 174
129 175 sourcelines.addEventListener('click', lineSelectEnd);
130 176
131 177 }
132 178
133 179 sourcelines.addEventListener('click', lineSelectStart);
134 180
135 181 //** return a <div id="followlines"> and inner cancel <button> elements */
136 182 function followlinesBox(targetUri, fromline, toline) {
137 183 // <div id="followlines">
138 184 var div = document.createElement('div');
139 185 div.id = 'followlines';
140 186
141 187 // <div class="followlines-cancel">
142 188 var buttonDiv = document.createElement('div');
143 189 buttonDiv.classList.add('followlines-cancel');
144 190
145 191 // <button>x</button>
146 192 var button = document.createElement('button');
147 193 button.textContent = 'x';
148 194 buttonDiv.appendChild(button);
149 195 div.appendChild(buttonDiv);
150 196
151 197 // <div class="followlines-link">
152 198 var aDiv = document.createElement('div');
153 199 aDiv.classList.add('followlines-link');
154 200
155 201 // <a href="/log/<rev>/<file>?patch=&linerange=...">
156 202 var a = document.createElement('a');
157 203 var url = targetUri + '?patch=&linerange=' + fromline + ':' + toline;
158 204 a.setAttribute('href', url);
159 205 a.textContent = 'follow lines ' + fromline + ':' + toline;
160 206 aDiv.appendChild(a);
161 207 div.appendChild(aDiv);
162 208
163 209 return [div, button];
164 210 }
165 211
166 212 }, false);
@@ -1,471 +1,487
1 1 body {
2 2 margin: 0;
3 3 padding: 0;
4 4 background: white;
5 5 color: black;
6 6 font-family: sans-serif;
7 7 }
8 8
9 9 .container {
10 10 padding-left: 115px;
11 11 }
12 12
13 13 .main {
14 14 position: relative;
15 15 background: white;
16 16 padding: 2em 2em 2em 0;
17 17 }
18 18
19 19 #.main {
20 20 width: 98%;
21 21 }
22 22
23 23 .overflow {
24 24 width: 100%;
25 25 overflow: auto;
26 26 }
27 27
28 28 .menu {
29 29 width: 90px;
30 30 margin: 0;
31 31 font-size: 80%;
32 32 text-align: left;
33 33 position: absolute;
34 34 top: 20px;
35 35 left: 20px;
36 36 right: auto;
37 37 }
38 38
39 39 .menu ul {
40 40 list-style: none;
41 41 padding: 0;
42 42 margin: 10px 0 0 0;
43 43 border-left: 2px solid #999;
44 44 }
45 45
46 46 .menu li {
47 47 margin-bottom: 3px;
48 48 padding: 2px 4px;
49 49 background: white;
50 50 color: black;
51 51 font-weight: normal;
52 52 }
53 53
54 54 .menu li.active {
55 55 font-weight: bold;
56 56 }
57 57
58 58 .menu img {
59 59 width: 75px;
60 60 height: 90px;
61 61 border: 0;
62 62 }
63 63
64 64 div.atom-logo {
65 65 margin-top: 10px;
66 66 }
67 67
68 68 .atom-logo img{
69 69 width: 14px;
70 70 height: 14px;
71 71 border: 0;
72 72 }
73 73
74 74 .menu a { color: black; display: block; }
75 75
76 76 .search {
77 77 position: absolute;
78 78 top: .7em;
79 79 right: 2em;
80 80 }
81 81
82 82 form.search div#hint {
83 83 display: none;
84 84 position: absolute;
85 85 top: 40px;
86 86 right: 0px;
87 87 width: 190px;
88 88 padding: 5px;
89 89 background: #ffc;
90 90 font-size: 70%;
91 91 border: 1px solid yellow;
92 92 border-radius: 5px;
93 93 }
94 94
95 95 form.search:hover div#hint { display: block; }
96 96
97 97 a { text-decoration:none; }
98 98 .age { white-space:nowrap; }
99 99 .date { white-space:nowrap; }
100 100 .indexlinks { white-space:nowrap; }
101 101 .parity0,
102 102 .stripes4 > :nth-child(4n+1),
103 103 .stripes2 > :nth-child(2n+1) { background-color: #f0f0f0; }
104 104 .parity1,
105 105 .stripes4 > :nth-child(4n+3),
106 106 .stripes2 > :nth-child(2n+2) { background-color: white; }
107 107 .plusline { color: green; }
108 108 .minusline { color: #dc143c; } /* crimson */
109 109 .atline { color: purple; }
110 110
111 111 .diffstat-table {
112 112 margin-top: 1em;
113 113 }
114 114 .diffstat-file {
115 115 white-space: nowrap;
116 116 font-size: 90%;
117 117 }
118 118 .diffstat-total {
119 119 white-space: nowrap;
120 120 font-size: 90%;
121 121 }
122 122 .diffstat-graph {
123 123 width: 100%;
124 124 }
125 125 .diffstat-add {
126 126 background-color: green;
127 127 float: left;
128 128 }
129 129 .diffstat-remove {
130 130 background-color: red;
131 131 float: left;
132 132 }
133 133
134 134 .navigate {
135 135 text-align: right;
136 136 font-size: 60%;
137 137 margin: 1em 0;
138 138 }
139 139
140 140 .tag {
141 141 color: #999;
142 142 font-size: 70%;
143 143 font-weight: normal;
144 144 margin-left: .5em;
145 145 vertical-align: baseline;
146 146 }
147 147
148 148 .branchhead {
149 149 color: #000;
150 150 font-size: 80%;
151 151 font-weight: normal;
152 152 margin-left: .5em;
153 153 vertical-align: baseline;
154 154 }
155 155
156 156 ul#graphnodes .branchhead {
157 157 font-size: 75%;
158 158 }
159 159
160 160 .branchname {
161 161 color: #000;
162 162 font-size: 60%;
163 163 font-weight: normal;
164 164 margin-left: .5em;
165 165 vertical-align: baseline;
166 166 }
167 167
168 168 h3 .branchname {
169 169 font-size: 80%;
170 170 }
171 171
172 172 /* Common */
173 173 pre { margin: 0; }
174 174
175 175 h2 { font-size: 120%; border-bottom: 1px solid #999; }
176 176 h2 a { color: #000; }
177 177 h3 {
178 178 margin-top: +.7em;
179 179 font-size: 100%;
180 180 }
181 181
182 182 /* log and tags tables */
183 183 .bigtable {
184 184 border-bottom: 1px solid #999;
185 185 border-collapse: collapse;
186 186 font-size: 90%;
187 187 width: 100%;
188 188 font-weight: normal;
189 189 text-align: left;
190 190 }
191 191
192 192 .bigtable td {
193 193 vertical-align: top;
194 194 }
195 195
196 196 .bigtable th {
197 197 padding: 1px 4px;
198 198 border-bottom: 1px solid #999;
199 199 }
200 200 .bigtable tr { border: none; }
201 201 .bigtable .age { width: 7em; }
202 202 .bigtable .author { width: 15em; }
203 203 .bigtable .description { }
204 204 .bigtable .description .base { font-size: 70%; float: right; line-height: 1.66; }
205 205 .bigtable .node { width: 5em; font-family: monospace;}
206 206 .bigtable .permissions { width: 8em; text-align: left;}
207 207 .bigtable .size { width: 5em; text-align: right; }
208 208 .bigtable .annotate { text-align: right; }
209 209 .bigtable td.annotate { font-size: smaller; }
210 210 .bigtable td.source { font-size: inherit; }
211 211 tr.thisrev a { color:#999999; text-decoration: none; }
212 212 tr.thisrev td.source { color:#009900; }
213 213 td.annotate {
214 214 white-space: nowrap;
215 215 }
216 216 div.annotate-info {
217 217 display: none;
218 218 position: absolute;
219 219 background-color: #FFFFFF;
220 220 border: 1px solid #999;
221 221 text-align: left;
222 222 color: #000000;
223 223 padding: 5px;
224 224 }
225 225 div.annotate-info a { color: #0000FF; }
226 226 td.annotate:hover div.annotate-info { display: inline; }
227 227
228 228 .source, .sourcefirst {
229 229 font-family: monospace;
230 230 white-space: pre;
231 231 padding: 1px 4px;
232 232 font-size: 90%;
233 233 }
234 234 .sourcefirst { border-bottom: 1px solid #999; font-weight: bold; }
235 235 .source a { color: #999; font-size: smaller; font-family: monospace;}
236 236 .bottomline { border-bottom: 1px solid #999; }
237 237
238 238 .sourcelines {
239 239 font-size: 90%;
240 240 position: relative;
241 241 counter-reset: lineno;
242 242 }
243 243
244 244 .wrap > span {
245 245 white-space: pre-wrap;
246 246 }
247 247
248 248 .linewraptoggle {
249 249 float: right;
250 250 }
251 251
252 252 .diffblocks { counter-reset: lineno; }
253 253 .diffblocks > div { counter-increment: lineno; }
254 254
255 255 .sourcelines > span {
256 256 display: inline-block;
257 257 box-sizing: border-box;
258 258 width: 100%;
259 259 padding: 1px 0px 1px 5em;
260 260 counter-increment: lineno;
261 261 }
262 262
263 263 .sourcelines > span:before {
264 264 -moz-user-select: -moz-none;
265 265 -khtml-user-select: none;
266 266 -webkit-user-select: none;
267 267 -ms-user-select: none;
268 268 user-select: none;
269 269 display: inline-block;
270 270 margin-left: -5em;
271 271 width: 4em;
272 272 font-size: smaller;
273 273 color: #999;
274 274 text-align: right;
275 275 content: counters(lineno, ".");
276 276 float: left;
277 277 }
278 278
279 279 .sourcelines > span:target, tr:target td {
280 280 background-color: #bfdfff;
281 281 }
282 282
283 283 div.overflow pre.sourcelines > span.followlines-select:hover {
284 284 cursor: cell;
285 285 }
286 286
287 287 pre.sourcelines > span.followlines-selected {
288 288 background-color: #99C7E9;
289 289 }
290 290
291 291 div#followlines {
292 292 background-color: #B7B7B7;
293 293 border: 1px solid #CCC;
294 294 border-radius: 5px;
295 295 padding: 4px;
296 296 position: absolute;
297 297 }
298 298
299 299 div.followlines-cancel {
300 300 text-align: right;
301 301 }
302 302
303 303 div.followlines-cancel > button {
304 304 line-height: 80%;
305 305 padding: 0;
306 306 border: 0;
307 307 border-radius: 2px;
308 308 background-color: inherit;
309 309 font-weight: bold;
310 310 }
311 311
312 312 div.followlines-cancel > button:hover {
313 313 color: #FFFFFF;
314 314 background-color: #CF1F1F;
315 315 }
316 316
317 317 div.followlines-link {
318 318 margin: 2px;
319 319 margin-top: 4px;
320 320 font-family: sans-serif;
321 321 }
322 322
323 div#followlines-tooltip {
324 display: none;
325 position: fixed;
326 background-color: #ffc;
327 border: 1px solid #999;
328 padding: 2px;
329 }
330
331 .sourcelines:hover > div#followlines-tooltip {
332 display: inline;
333 }
334
335 .sourcelines:hover > div#followlines-tooltip.hidden {
336 display: none;
337 }
338
323 339 .sourcelines > a {
324 340 display: inline-block;
325 341 position: absolute;
326 342 left: 0px;
327 343 width: 4em;
328 344 height: 1em;
329 345 }
330 346
331 347 .fileline { font-family: monospace; }
332 348 .fileline img { border: 0; }
333 349
334 350 .tagEntry .closed { color: #99f; }
335 351
336 352 /* Changeset entry */
337 353 #changesetEntry {
338 354 border-collapse: collapse;
339 355 font-size: 90%;
340 356 width: 100%;
341 357 margin-bottom: 1em;
342 358 }
343 359
344 360 #changesetEntry th {
345 361 padding: 1px 4px;
346 362 width: 4em;
347 363 text-align: right;
348 364 font-weight: normal;
349 365 color: #999;
350 366 margin-right: .5em;
351 367 vertical-align: top;
352 368 }
353 369
354 370 div.description {
355 371 border-left: 2px solid #999;
356 372 margin: 1em 0 1em 0;
357 373 padding: .3em;
358 374 white-space: pre;
359 375 font-family: monospace;
360 376 }
361 377
362 378 /* Graph */
363 379 div#wrapper {
364 380 position: relative;
365 381 border-top: 1px solid black;
366 382 border-bottom: 1px solid black;
367 383 margin: 0;
368 384 padding: 0;
369 385 }
370 386
371 387 canvas {
372 388 position: absolute;
373 389 z-index: 5;
374 390 top: -0.7em;
375 391 margin: 0;
376 392 }
377 393
378 394 ul#graphnodes {
379 395 position: absolute;
380 396 z-index: 10;
381 397 top: -1.0em;
382 398 list-style: none inside none;
383 399 padding: 0;
384 400 }
385 401
386 402 ul#nodebgs {
387 403 list-style: none inside none;
388 404 padding: 0;
389 405 margin: 0;
390 406 top: -0.7em;
391 407 }
392 408
393 409 ul#graphnodes li, ul#nodebgs li {
394 410 height: 39px;
395 411 }
396 412
397 413 ul#graphnodes li .info {
398 414 display: block;
399 415 font-size: 70%;
400 416 position: relative;
401 417 top: -3px;
402 418 }
403 419
404 420 /* Comparison */
405 421 .legend {
406 422 padding: 1.5% 0 1.5% 0;
407 423 }
408 424
409 425 .legendinfo {
410 426 border: 1px solid #999;
411 427 font-size: 80%;
412 428 text-align: center;
413 429 padding: 0.5%;
414 430 }
415 431
416 432 .equal {
417 433 background-color: #ffffff;
418 434 }
419 435
420 436 .delete {
421 437 background-color: #faa;
422 438 color: #333;
423 439 }
424 440
425 441 .insert {
426 442 background-color: #ffa;
427 443 }
428 444
429 445 .replace {
430 446 background-color: #e8e8e8;
431 447 }
432 448
433 449 .header {
434 450 text-align: center;
435 451 }
436 452
437 453 .block {
438 454 border-top: 1px solid #999;
439 455 }
440 456
441 457 .breadcrumb {
442 458 color: gray;
443 459 }
444 460
445 461 .breadcrumb a {
446 462 color: blue;
447 463 }
448 464
449 465 .scroll-loading {
450 466 -webkit-animation: change_color 1s linear 0s infinite alternate;
451 467 -moz-animation: change_color 1s linear 0s infinite alternate;
452 468 -o-animation: change_color 1s linear 0s infinite alternate;
453 469 animation: change_color 1s linear 0s infinite alternate;
454 470 }
455 471
456 472 @-webkit-keyframes change_color {
457 473 from { background-color: #A0CEFF; } to { }
458 474 }
459 475 @-moz-keyframes change_color {
460 476 from { background-color: #A0CEFF; } to { }
461 477 }
462 478 @-o-keyframes change_color {
463 479 from { background-color: #A0CEFF; } to { }
464 480 }
465 481 @keyframes change_color {
466 482 from { background-color: #A0CEFF; } to { }
467 483 }
468 484
469 485 .scroll-loading-error {
470 486 background-color: #FFCCCC !important;
471 487 }
General Comments 0
You need to be logged in to leave comments. Login now