Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Implemented dynamic mouse selection of source lines and clipboard tooltip to copy the line range URL. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | line-number-selection |
| Files: | files | file ages | folders |
| SHA3-256: |
3942eb600aedd12aafb4de57710160c4 |
| User & Date: | stephan 2020-08-15 07:41:02.963 |
Context
|
2020-08-15
| ||
| 08:02 | 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. ... (check-in: 738bea54c0 user: stephan tags: line-number-selection) | |
| 07:41 | Implemented dynamic mouse selection of source lines and clipboard tooltip to copy the line range URL. ... (check-in: 3942eb600a user: stephan tags: line-number-selection) | |
| 03:51 | Reimplemented ln=... highlighting to mark the line numbers instead of the code, so that there is no interference with syntax highlighters. Adjusted two skins to use the newer line-marking CSS. ... (check-in: 14ac3e8469 user: stephan tags: line-number-selection) | |
Changes
Changes to src/default.css.
| ︙ | ︙ | |||
1169 1170 1171 1172 1173 1174 1175 1176 1177 |
margin: 0;
padding: 0;
line-height: inherit;
font-size: inherit;
font-family: inherit;
cursor: pointer;
white-space: pre;
}
table.numbered-lines td.line-numbers > span:hover {
| > | < | 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 |
margin: 0;
padding: 0;
line-height: inherit;
font-size: inherit;
font-family: inherit;
cursor: pointer;
white-space: pre;
margin-right: 2px/*keep selection from nudging the right column */;
}
table.numbered-lines td.line-numbers > span:hover {
background-color: rgba(112, 112, 112, 0.25);
}
table.numbered-lines td.file-content {
padding-left: 1em;
}
table.numbered-lines td.file-content > pre {
margin: 0;
padding: 0;
|
| ︙ | ︙ | |||
1213 1214 1215 1216 1217 1218 1219 1220 |
table.numbered-lines td.line-numbers span.selected-line.start {
border-top-width: 1px;
margin-top: -1px/*restore alignment*/;
}
table.numbered-lines td.line-numbers span.selected-line.end {
border-bottom-width: 1px;
margin-top: -1px/*restore alignment*/;
}
| > > > > > > > > > > > > > > > > | 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 |
table.numbered-lines td.line-numbers span.selected-line.start {
border-top-width: 1px;
margin-top: -1px/*restore alignment*/;
}
table.numbered-lines td.line-numbers span.selected-line.end {
border-bottom-width: 1px;
margin-top: -1px/*restore alignment*/;
}
table.numbered-lines td.line-numbers span.selected-line.start.end {
margin-top: -2px/*restore alignment*/;
}
.fossil-tooltip {
text-align: center;
padding: 0.2em 1em;
border: 1px solid black;
border-radius: 0.25em;
position: absolute;
display: inline-block;
z-index: 100;
box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
background-color: inherit;
font-size: 80%;
}
|
Changes to src/fossil.numbered-lines.js.
1 |
(function callee(arg){
| > | | | > > > > | > | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | | > > > | < | | | > > > > > > > > > | > > > > > > | < < < | < | < < < < < < < < < < < < | > | < > | > > > > > > > > | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
(function callee(arg){
/*
JS counterpart of info.c:output_text_with_line_numbers()
which ties an event handler to the line numbers to allow
selection of individual lines or ranges.
Requires: fossil.bootstrap, fossil.dom, fossil.tooltip
*/
var tbl = arg || document.querySelectorAll('table.numbered-lines');
if(!tbl) return /* no matching elements */;
else if(!arg){
if(tbl.length>1){ /* multiple query results: recurse */
tbl.forEach( (t)=>callee(t) );
return;
}else{/* single query result */
tbl = tbl[0];
}
}
const F = window.fossil, D = F.dom;
const tdLn = tbl.querySelector('td.line-numbers');
const lineState = {
urlArgs: (window.location.search||'').replace(/&?\bln=[^&]*/,''),
start: 0, end: 0
};
const lineTip = new fossil.TooltipWidget({
refresh: function(){
const link = this.state.link;
D.clearElement(link);
if(lineState.start){
const ls = [lineState.start];
if(lineState.end) ls.push(lineState.end);
link.dataset.url = (
window.location.toString().split('?')[0]
+ lineState.urlArgs + '&ln='+ls.join('-')
);
D.append(
D.clearElement(link),
' ',
(ls.length===1 ? 'line ' : 'lines ')+ls.join('-')
);
}else{
D.append(link, "No lines selected.");
}
},
adjustX: function(x){
return x + 20;
},
adjustY: function(y){
return y - this.e.clientHeight/2;
},
init: function(){
const e = this.e;
const btnCopy = D.addClass(D.span(), 'copy-button');
const link = D.attr(D.span(), 'id', 'fossil-ln-link');
this.state = {link};
F.copyButton(btnCopy,{
copyFromElement: link,
extractText: ()=>link.dataset.url
});
D.append(this.e, btnCopy, link)
}
});
tbl.addEventListener('click', function f(ev){
lineTip.show(false);
}, false);
tdLn.addEventListener('click', function f(ev){
if('SPAN'!==ev.target.tagName) return;
else if('number' !== typeof f.mode){
f.mode = 0 /*0=none selected, 1=1 selected, 2=2 selected*/;
}
ev.stopPropagation();
const ln = +ev.target.innerText;
if(2===f.mode){/*reset selection*/
f.mode = 0;
}
if(0===f.mode){
lineState.end = 0;
lineState.start = ln;
f.mode = 1;
}else if(1===f.mode){
if(ln === lineState.start){/*unselect line*/
//console.debug("Unselected line #"+ln);
lineState.start = 0;
f.mode = 0;
}else{
if(ln<lineState.start){
lineState.end = lineState.start;
lineState.start = ln;
}else{
lineState.end = ln;
}
//console.debug("Selected range: ",rng);
f.mode = 2;
}
}
tdLn.querySelectorAll('span.selected-line').forEach(
(e)=>D.removeClass(e, 'selected-line','start','end'));
if(f.mode>0){
lineTip.show(ev.clientX, ev.clientY);
const spans = tdLn.querySelectorAll('span');
if(spans.length>=lineState.start){
let i = lineState.start, end = lineState.end || lineState.start, span = spans[i-1];
for( ; i<=end && span; span = spans[i++] ){
span.classList.add('selected-line');
if(i===lineState.start) span.classList.add('start');
if(i===end) span.classList.add('end');
}
}
lineTip.refresh();
}else{
lineTip.show(false);
}
}, false);
})();
|
Added src/fossil.tooltip.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
(function(F/*fossil object*/){
/**
A very basic tooltip widget.
Requires: fossil.bootstrap, fossil.dom
*/
const D = F.dom;
/**
Creates a new tooltip widget using the given options object.
The options are available to clients after this returns via
theTooltip.options.
Options:
.refresh: required callback which is called whenever the tooltip
is revealed or moved. It must refresh the contents of the
tooltip, if needed, by applying it to this.e, which is the base
DOM element for the tooltip.
.adjustX: an optional callback which is called when the tooltip
is to be displayed at a given position and passed the X
coordinate. This routine must either return its argument as-is
or return an adjusted value. This API assumes that clients give it
viewport-relative coordinates, and it will take care to translate
those to page-relative.
.adjustY: the Y counterpart of adjustX.
All callback options are called with the TooltipWidget object as
their "this".
.cssClass: optional CSS class, or list of classes, to apply to
the new element.
.style: optional object of properties to copy directly into
the element's style object.
Example:
const tip = new fossil.TooltipWidget({
adjustX: (x)=>x+20,
adjustY: function(y){return y - this.e.clientHeight/2},
refresh: function(){
// (re)populate/refresh the contents of the main
// wrapper element, this.e.
}
});
*/
F.TooltipWidget = function f(opt){
opt = F.mergeLastWins(f.defaultOptions,opt);
this.options = opt;
const e = this.e = D.addClass(D.div(), opt.cssClass);
this.show(false);
if(opt.style){
let k;
for(k in opt.style){
if(opt.style.hasOwnProperty(k)) e.style[k] = opt.style[k];
}
}
D.append(document.body, e/*must be in the DOM for size calc. to work*/);
if(opt.init){
opt.init.call(this);
}
};
F.TooltipWidget.defaultOptions = {
cssClass: 'fossil-tooltip',
style: {/*properties copied as-is into element.style*/},
adjustX: (x)=>x,
adjustY: (y)=>y,
refresh: function(hw){
console.error("TooltipWidget refresh() option must be provided by the client.");
}
};
F.TooltipWidget.prototype = {
isShown: function(){return !this.e.classList.contains('hidden')},
/** Calls the refresh() method of the options object. */
refresh: function(){this.options.refresh.call(this)},
/**
Usages:
(bool showIt) => hide it or reveal it at its last position.
(x, y) => reveal/move it at/to the given
relative-to-the-viewport position, which will be adjusted to make
it page-relative.
(DOM element) => reveal/move it ad/to a position based on the
the given element (adjusted slightly).
For the latter two, this.options.adjustX() and adjustY() will
be called to adjust it further.
Returns this object.
*/
show: function(){
var x = 0, y = 0, showIt;
if(2===arguments.length){
x = arguments[0];
y = arguments[1];
showIt = true;
}else if(1===arguments.length){
if(arguments[0] instanceof HTMLElement){
const p = arguments[0];
const r = p.getBoundingClientRect();
x = r.x + r.x/5;
y = r.y - r.height/2;
showIt = true;
}else{
showIt = !!arguments[0];
}
}
if(showIt){
this.refresh();
x = this.options.adjustX.call(this,x);
y = this.options.adjustY.call(this,y);
x += window.pageXOffset;
y += window.pageYOffset;
}
D[showIt ? 'removeClass' : 'addClass'](this.e, 'hidden');
if(x || y){
this.e.style.left = x+"px";
this.e.style.top = y+"px";
//console.debug("TooltipWidget.show()", arguments, x, y);
}
return this;
}
}/*/F.TooltipWidget.prototype*/;
})(window.fossil);
|
Changes to src/info.c.
| ︙ | ︙ | |||
2111 2112 2113 2114 2115 2116 2117 |
}
cgi_append_content("</td><td class='file-content'><pre>",-1);
if(zExt && *zExt){
cgi_printf("<code class='language-%h'>",zExt);
}else{
cgi_append_content("<code>", -1);
}
| | > | | 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 |
}
cgi_append_content("</td><td class='file-content'><pre>",-1);
if(zExt && *zExt){
cgi_printf("<code class='language-%h'>",zExt);
}else{
cgi_append_content("<code>", -1);
}
cgi_printf("%z", htmlize(z, nZ));
CX("</code></pre></td></tr></tbody></table>\n");
if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
builtin_request_js("scroll.js");
}
style_emit_fossil_js_apis(0, "dom", "copybutton", "tooltip",
"numbered-lines", 0);
}
/*
** COMMAND: test-line-numbers
**
** Usage: %fossil test-line-numbers FILE ?LN-SPEC?
**
|
| ︙ | ︙ |
Changes to src/main.mk.
| ︙ | ︙ | |||
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ | > > | 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.copybutton.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.tooltip.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ |
| ︙ | ︙ |
Changes to win/Makefile.mingw.
| ︙ | ︙ | |||
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ | > > | 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 | $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.copybutton.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.tooltip.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ |
| ︙ | ︙ |
Changes to win/Makefile.msc.
| ︙ | ︙ | |||
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
"$(SRCDIR)\ci_edit.js" \
"$(SRCDIR)\copybtn.js" \
"$(SRCDIR)\default.css" \
"$(SRCDIR)\diff.tcl" \
"$(SRCDIR)\forum.js" \
"$(SRCDIR)\fossil.bootstrap.js" \
"$(SRCDIR)\fossil.confirmer.js" \
"$(SRCDIR)\fossil.dom.js" \
"$(SRCDIR)\fossil.fetch.js" \
"$(SRCDIR)\fossil.numbered-lines.js" \
"$(SRCDIR)\fossil.page.fileedit.js" \
"$(SRCDIR)\fossil.page.forumpost.js" \
"$(SRCDIR)\fossil.page.wikiedit.js" \
"$(SRCDIR)\fossil.storage.js" \
"$(SRCDIR)\fossil.tabs.js" \
"$(SRCDIR)\graph.js" \
"$(SRCDIR)\href.js" \
"$(SRCDIR)\login.js" \
"$(SRCDIR)\markdown.md" \
"$(SRCDIR)\menu.js" \
"$(SRCDIR)\sbsdiff.js" \
"$(SRCDIR)\scroll.js" \
| > > | 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
"$(SRCDIR)\ci_edit.js" \
"$(SRCDIR)\copybtn.js" \
"$(SRCDIR)\default.css" \
"$(SRCDIR)\diff.tcl" \
"$(SRCDIR)\forum.js" \
"$(SRCDIR)\fossil.bootstrap.js" \
"$(SRCDIR)\fossil.confirmer.js" \
"$(SRCDIR)\fossil.copybutton.js" \
"$(SRCDIR)\fossil.dom.js" \
"$(SRCDIR)\fossil.fetch.js" \
"$(SRCDIR)\fossil.numbered-lines.js" \
"$(SRCDIR)\fossil.page.fileedit.js" \
"$(SRCDIR)\fossil.page.forumpost.js" \
"$(SRCDIR)\fossil.page.wikiedit.js" \
"$(SRCDIR)\fossil.storage.js" \
"$(SRCDIR)\fossil.tabs.js" \
"$(SRCDIR)\fossil.tooltip.js" \
"$(SRCDIR)\graph.js" \
"$(SRCDIR)\href.js" \
"$(SRCDIR)\login.js" \
"$(SRCDIR)\markdown.md" \
"$(SRCDIR)\menu.js" \
"$(SRCDIR)\sbsdiff.js" \
"$(SRCDIR)\scroll.js" \
|
| ︙ | ︙ | |||
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 | echo "$(SRCDIR)\ci_edit.js" >> $@ echo "$(SRCDIR)\copybtn.js" >> $@ echo "$(SRCDIR)\default.css" >> $@ echo "$(SRCDIR)\diff.tcl" >> $@ echo "$(SRCDIR)\forum.js" >> $@ echo "$(SRCDIR)\fossil.bootstrap.js" >> $@ echo "$(SRCDIR)\fossil.confirmer.js" >> $@ echo "$(SRCDIR)\fossil.dom.js" >> $@ echo "$(SRCDIR)\fossil.fetch.js" >> $@ echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@ echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ echo "$(SRCDIR)\fossil.storage.js" >> $@ echo "$(SRCDIR)\fossil.tabs.js" >> $@ echo "$(SRCDIR)\graph.js" >> $@ echo "$(SRCDIR)\href.js" >> $@ echo "$(SRCDIR)\login.js" >> $@ echo "$(SRCDIR)\markdown.md" >> $@ echo "$(SRCDIR)\menu.js" >> $@ echo "$(SRCDIR)\sbsdiff.js" >> $@ echo "$(SRCDIR)\scroll.js" >> $@ | > > | 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 | echo "$(SRCDIR)\ci_edit.js" >> $@ echo "$(SRCDIR)\copybtn.js" >> $@ echo "$(SRCDIR)\default.css" >> $@ echo "$(SRCDIR)\diff.tcl" >> $@ echo "$(SRCDIR)\forum.js" >> $@ echo "$(SRCDIR)\fossil.bootstrap.js" >> $@ echo "$(SRCDIR)\fossil.confirmer.js" >> $@ echo "$(SRCDIR)\fossil.copybutton.js" >> $@ echo "$(SRCDIR)\fossil.dom.js" >> $@ echo "$(SRCDIR)\fossil.fetch.js" >> $@ echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@ echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ echo "$(SRCDIR)\fossil.storage.js" >> $@ echo "$(SRCDIR)\fossil.tabs.js" >> $@ echo "$(SRCDIR)\fossil.tooltip.js" >> $@ echo "$(SRCDIR)\graph.js" >> $@ echo "$(SRCDIR)\href.js" >> $@ echo "$(SRCDIR)\login.js" >> $@ echo "$(SRCDIR)\markdown.md" >> $@ echo "$(SRCDIR)\menu.js" >> $@ echo "$(SRCDIR)\sbsdiff.js" >> $@ echo "$(SRCDIR)\scroll.js" >> $@ |
| ︙ | ︙ |