Fossil SCM

Doc improvements, minor cleanups. Made the URL-copy element's popup position less variable. Uplifted the flash-once and copy-to-clipboard code into the fossil.dom API.

stephan 2020-08-15 08:02 line-number-selection
Commit 738bea54c0b73fe362c6c48807dacd73b6912e516b3338ace2d7a58e46596b40
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -468,8 +468,66 @@
468468
console.error(e, src);
469469
throw e;
470470
}
471471
return e;
472472
};
473
+
474
+ /**
475
+ "Blinks" the given element a single time for the given number of
476
+ milliseconds, defaulting to flashOnce.defaultTimeMs. This will
477
+ only activate once per element during that timeframe - further
478
+ calls will become no-ops until the blink is completed. This
479
+ routine adds a dataset member to the element for the duration of
480
+ the blink, to allow it to block multiple blinks.
481
+
482
+ Returns e.
483
+ */
484
+ dom.flashOnce = function f(e,howLongMs){
485
+ if(e.dataset.isBlinking){
486
+ return;
487
+ }
488
+ if(!howLongMs || 'number'!==typeof howLongMs){
489
+ howLongMs = f.defaultTimeMs;
490
+ }
491
+ e.dataset.isBlinking = true;
492
+ const transition = e.style.transition;
493
+ e.style.transition = "opacity "+howLongMs+"ms ease-in-out";
494
+ const opacity = e.style.opacity;
495
+ e.style.opacity = 0;
496
+ setTimeout(function(){
497
+ e.style.transition = transition;
498
+ e.style.opacity = opacity;
499
+ delete e.dataset.isBlinking;
500
+ }, howLongMs);
501
+ return e;
502
+ };
503
+ dom.flashOnce.defaultTimeMs = 400;
504
+
505
+ /**
506
+ Attempts to copy the given text to the system clipboard. Returns
507
+ true if it succeeds, else false.
508
+ */
509
+ dom.copyTextToClipboard = function(text){
510
+ if( window.clipboardData && window.clipboardData.setData ){
511
+ clipboardData.setData('Text',text);
512
+ return true;
513
+ }else{
514
+ const x = document.createElement("textarea");
515
+ x.style.position = 'fixed';
516
+ x.value = text;
517
+ document.body.appendChild(x);
518
+ x.select();
519
+ var rc;
520
+ try{
521
+ document.execCommand('copy');
522
+ rc = true;
523
+ }catch(err){
524
+ rc = false;
525
+ }finally{
526
+ document.body.removeChild(x);
527
+ }
528
+ return rc;
529
+ }
530
+ };
473531
474532
return F.dom = dom;
475533
})(window.fossil);
476534
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -468,8 +468,66 @@
468 console.error(e, src);
469 throw e;
470 }
471 return e;
472 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
474 return F.dom = dom;
475 })(window.fossil);
476
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -468,8 +468,66 @@
468 console.error(e, src);
469 throw e;
470 }
471 return e;
472 };
473
474 /**
475 "Blinks" the given element a single time for the given number of
476 milliseconds, defaulting to flashOnce.defaultTimeMs. This will
477 only activate once per element during that timeframe - further
478 calls will become no-ops until the blink is completed. This
479 routine adds a dataset member to the element for the duration of
480 the blink, to allow it to block multiple blinks.
481
482 Returns e.
483 */
484 dom.flashOnce = function f(e,howLongMs){
485 if(e.dataset.isBlinking){
486 return;
487 }
488 if(!howLongMs || 'number'!==typeof howLongMs){
489 howLongMs = f.defaultTimeMs;
490 }
491 e.dataset.isBlinking = true;
492 const transition = e.style.transition;
493 e.style.transition = "opacity "+howLongMs+"ms ease-in-out";
494 const opacity = e.style.opacity;
495 e.style.opacity = 0;
496 setTimeout(function(){
497 e.style.transition = transition;
498 e.style.opacity = opacity;
499 delete e.dataset.isBlinking;
500 }, howLongMs);
501 return e;
502 };
503 dom.flashOnce.defaultTimeMs = 400;
504
505 /**
506 Attempts to copy the given text to the system clipboard. Returns
507 true if it succeeds, else false.
508 */
509 dom.copyTextToClipboard = function(text){
510 if( window.clipboardData && window.clipboardData.setData ){
511 clipboardData.setData('Text',text);
512 return true;
513 }else{
514 const x = document.createElement("textarea");
515 x.style.position = 'fixed';
516 x.value = text;
517 document.body.appendChild(x);
518 x.select();
519 var rc;
520 try{
521 document.execCommand('copy');
522 rc = true;
523 }catch(err){
524 rc = false;
525 }finally{
526 document.body.removeChild(x);
527 }
528 return rc;
529 }
530 };
531
532 return F.dom = dom;
533 })(window.fossil);
534
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -2,11 +2,12 @@
22
/*
33
JS counterpart of info.c:output_text_with_line_numbers()
44
which ties an event handler to the line numbers to allow
55
selection of individual lines or ranges.
66
7
- Requires: fossil.bootstrap, fossil.dom, fossil.tooltip
7
+ Requires: fossil.bootstrap, fossil.dom, fossil.tooltip,
8
+ fossil.copybutton
89
*/
910
var tbl = arg || document.querySelectorAll('table.numbered-lines');
1011
if(!tbl) return /* no matching elements */;
1112
else if(!arg){
1213
if(tbl.length>1){ /* multiple query results: recurse */
@@ -17,11 +18,11 @@
1718
}
1819
}
1920
const F = window.fossil, D = F.dom;
2021
const tdLn = tbl.querySelector('td.line-numbers');
2122
const lineState = {
22
- urlArgs: (window.location.search||'').replace(/&?\bln=[^&]*/,''),
23
+ urlArgs: (window.location.search||'?').replace(/&?\bln=[^&]*/,''),
2324
start: 0, end: 0
2425
};
2526
2627
const lineTip = new fossil.TooltipWidget({
2728
refresh: function(){
@@ -41,20 +42,16 @@
4142
);
4243
}else{
4344
D.append(link, "No lines selected.");
4445
}
4546
},
46
- adjustX: function(x){
47
- return x + 20;
48
- },
49
- adjustY: function(y){
50
- return y - this.e.clientHeight/2;
51
- },
47
+ adjustX: (x)=>x,
48
+ adjustY: (y)=>y,
5249
init: function(){
5350
const e = this.e;
54
- const btnCopy = D.addClass(D.span(), 'copy-button');
55
- const link = D.attr(D.span(), 'id', 'fossil-ln-link');
51
+ const btnCopy = D.span(),
52
+ link = D.span();
5653
this.state = {link};
5754
F.copyButton(btnCopy,{
5855
copyFromElement: link,
5956
extractText: ()=>link.dataset.url
6057
});
@@ -97,11 +94,12 @@
9794
}
9895
}
9996
tdLn.querySelectorAll('span.selected-line').forEach(
10097
(e)=>D.removeClass(e, 'selected-line','start','end'));
10198
if(f.mode>0){
102
- lineTip.show(ev.clientX, ev.clientY);
99
+ const rect = ev.target.getBoundingClientRect();
100
+ lineTip.show(rect.right+3, rect.top-4);
103101
const spans = tdLn.querySelectorAll('span');
104102
if(spans.length>=lineState.start){
105103
let i = lineState.start, end = lineState.end || lineState.start, span = spans[i-1];
106104
for( ; i<=end && span; span = spans[i++] ){
107105
span.classList.add('selected-line');
108106
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -2,11 +2,12 @@
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 */
9 var tbl = arg || document.querySelectorAll('table.numbered-lines');
10 if(!tbl) return /* no matching elements */;
11 else if(!arg){
12 if(tbl.length>1){ /* multiple query results: recurse */
@@ -17,11 +18,11 @@
17 }
18 }
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(){
@@ -41,20 +42,16 @@
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 });
@@ -97,11 +94,12 @@
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
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -2,11 +2,12 @@
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 fossil.copybutton
9 */
10 var tbl = arg || document.querySelectorAll('table.numbered-lines');
11 if(!tbl) return /* no matching elements */;
12 else if(!arg){
13 if(tbl.length>1){ /* multiple query results: recurse */
@@ -17,11 +18,11 @@
18 }
19 }
20 const F = window.fossil, D = F.dom;
21 const tdLn = tbl.querySelector('td.line-numbers');
22 const lineState = {
23 urlArgs: (window.location.search||'?').replace(/&?\bln=[^&]*/,''),
24 start: 0, end: 0
25 };
26
27 const lineTip = new fossil.TooltipWidget({
28 refresh: function(){
@@ -41,20 +42,16 @@
42 );
43 }else{
44 D.append(link, "No lines selected.");
45 }
46 },
47 adjustX: (x)=>x,
48 adjustY: (y)=>y,
 
 
 
 
49 init: function(){
50 const e = this.e;
51 const btnCopy = D.span(),
52 link = D.span();
53 this.state = {link};
54 F.copyButton(btnCopy,{
55 copyFromElement: link,
56 extractText: ()=>link.dataset.url
57 });
@@ -97,11 +94,12 @@
94 }
95 }
96 tdLn.querySelectorAll('span.selected-line').forEach(
97 (e)=>D.removeClass(e, 'selected-line','start','end'));
98 if(f.mode>0){
99 const rect = ev.target.getBoundingClientRect();
100 lineTip.show(rect.right+3, rect.top-4);
101 const spans = tdLn.querySelectorAll('span');
102 if(spans.length>=lineState.start){
103 let i = lineState.start, end = lineState.end || lineState.start, span = spans[i-1];
104 for( ; i<=end && span; span = spans[i++] ){
105 span.classList.add('selected-line');
106
--- src/fossil.tooltip.js
+++ src/fossil.tooltip.js
@@ -25,13 +25,19 @@
2525
or return an adjusted value. This API assumes that clients give it
2626
viewport-relative coordinates, and it will take care to translate
2727
those to page-relative.
2828
2929
.adjustY: the Y counterpart of adjustX.
30
+
31
+ .init: optional callback called one time to initialize the
32
+ state of the tooltip. This is called after the this.e has
33
+ been created and added (initially hidden) to the DOM.
34
+
3035
3136
All callback options are called with the TooltipWidget object as
3237
their "this".
38
+
3339
3440
.cssClass: optional CSS class, or list of classes, to apply to
3541
the new element.
3642
3743
.style: optional object of properties to copy directly into
3844
--- src/fossil.tooltip.js
+++ src/fossil.tooltip.js
@@ -25,13 +25,19 @@
25 or return an adjusted value. This API assumes that clients give it
26 viewport-relative coordinates, and it will take care to translate
27 those to page-relative.
28
29 .adjustY: the Y counterpart of adjustX.
 
 
 
 
 
30
31 All callback options are called with the TooltipWidget object as
32 their "this".
 
33
34 .cssClass: optional CSS class, or list of classes, to apply to
35 the new element.
36
37 .style: optional object of properties to copy directly into
38
--- src/fossil.tooltip.js
+++ src/fossil.tooltip.js
@@ -25,13 +25,19 @@
25 or return an adjusted value. This API assumes that clients give it
26 viewport-relative coordinates, and it will take care to translate
27 those to page-relative.
28
29 .adjustY: the Y counterpart of adjustX.
30
31 .init: optional callback called one time to initialize the
32 state of the tooltip. This is called after the this.e has
33 been created and added (initially hidden) to the DOM.
34
35
36 All callback options are called with the TooltipWidget object as
37 their "this".
38
39
40 .cssClass: optional CSS class, or list of classes, to apply to
41 the new element.
42
43 .style: optional object of properties to copy directly into
44

Keyboard Shortcuts

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