Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Removed the copy button's hard-coded post-copy behaviour (flashing) and instead fire a text-copied event. The line number selection now closes the popup widget after the copy button is triggered. Implemented a basic toast-message API using PopupWidget. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | line-number-selection |
| Files: | files | file ages | folders |
| SHA3-256: |
8a6ccf9ddd720f3cc5a5c4b2d0a0a954 |
| User & Date: | stephan 2020-08-16 00:50:31.984 |
Context
|
2020-08-16
| ||
| 01:32 | Removed some stray debug output. Added a window.CustomEvent polyfill "just in case." ... (check-in: af47d1531d user: stephan tags: line-number-selection) | |
| 00:50 | Removed the copy button's hard-coded post-copy behaviour (flashing) and instead fire a text-copied event. The line number selection now closes the popup widget after the copy button is triggered. Implemented a basic toast-message API using PopupWidget. ... (check-in: 8a6ccf9ddd user: stephan tags: line-number-selection) | |
|
2020-08-15
| ||
| 23:30 | Renamed TooltipWidget to PopupWidget because's it's not *quite* a tooltip and we're soon going to need something closer to a genuine tooltip. Minor adjacent cleanups and code consolidation. ... (check-in: 3998ccef44 user: stephan tags: line-number-selection) | |
Changes
Changes to src/default.css.
| ︙ | ︙ | |||
1248 1249 1250 1251 1252 1253 1254 | 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%; } | > > > > > > > > > > > | 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 |
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%;
}
.fossil-toast {/* "toast"-style popup message */
padding: 0.25em 0.5em;
margin: 0;
border-radius: 0.25em;
font-size: 1em;
opacity: 0.8;
border-size: 1px;
border-style: dotted;
border-color: rgb( 127, 127, 127, 0.5 );
}
|
Changes to src/fossil.copybutton.js.
1 2 3 4 5 6 7 8 |
(function(F/*fossil object*/){
/**
A basic API for creating and managing a copy-to-clipboard button.
Requires: fossil.bootstrap, fossil.dom
*/
const D = F.dom;
| < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
(function(F/*fossil object*/){
/**
A basic API for creating and managing a copy-to-clipboard button.
Requires: fossil.bootstrap, fossil.dom
*/
const D = F.dom;
/**
Initializes element e as a copy button using the given options
object.
The first argument may be a DOM element or a string (CSS selector
suitable for use with document.querySelector()).
|
| ︙ | ︙ | |||
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
function!).
.cssClass: optional CSS class, or list of classes, to apply to e.
.style: optional object of properties to copy directly into
e.style.
Note that this function's own defaultOptions object holds default
values for some options. Any changes made to that object affect
any future calls to this function.
Be aware that clipboard functionality might or might not be
available in any given environment. If this button appears to
have no effect, that may be because it is not enabled/available
in the current platform.
Example:
| > > > > > > > > > > > > > > | > > > > | 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 |
function!).
.cssClass: optional CSS class, or list of classes, to apply to e.
.style: optional object of properties to copy directly into
e.style.
.oncopy: an optional callback function which is added as an event
listener for the 'text-copied' event (see below). There is
functionally no difference from setting this option or adding a
'text-copied' event listener to the element, and this option is
considered to be a convenience form of that.
Note that this function's own defaultOptions object holds default
values for some options. Any changes made to that object affect
any future calls to this function.
Be aware that clipboard functionality might or might not be
available in any given environment. If this button appears to
have no effect, that may be because it is not enabled/available
in the current platform.
The copy button emits custom event 'text-copied' after it has
successfully copied text to the clipboard. The event's "detail"
member is an object with a "text" property holding the copied
text. Other properties may be added in the future. The event is
not fired if copying to the clipboard fails (e.g. is not
available in the current environment).
Returns the copy-initialized element.
Example:
const button = fossil.copyButton('#my-copy-button', {
copyFromId: 'some-other-element-id'
});
button.addEventListener('text-copied',function(ev){
fossil.dom.flashOnce(ev.target);
console.debug("Copied text:",ev.detail.text);
});
*/
F.copyButton = function f(e, opt){
if('string'===typeof e){
e = document.querySelector(e);
}
opt = F.mergeLastWins(f.defaultOptions, opt);
|
| ︙ | ︙ | |||
76 77 78 79 80 81 82 |
undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value
);
D.copyStyle(e, opt.style);
e.addEventListener(
'click',
function(){
const txt = extract.call(opt);
| < > | > > > > > | 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 |
undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value
);
D.copyStyle(e, opt.style);
e.addEventListener(
'click',
function(){
const txt = extract.call(opt);
if(txt && D.copyTextToClipboard(txt)){
e.dispatchEvent(new CustomEvent('text-copied',{
detail: {text: txt}
}));
}
},
false
);
if('function' === typeof opt.oncopy){
e.addEventListener('text-copied', opt.oncopy, false);
}
return e;
};
F.copyButton.defaultOptions = {
cssClass: 'copy-button',
style: {/*properties copied as-is into element.style*/}
};
})(window.fossil);
|
Changes to src/fossil.dom.js.
| ︙ | ︙ | |||
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 |
in, it must be a function, and it gets callback back at the end
of the asynchronous flashing processes.
This will only activate once per element during that timeframe -
further calls will become no-ops until the blink is
completed. This routine adds a dataset member to the element for
the duration of the blink, to allow it to block multiple blinks.
Returns e, noting that the flash itself is asynchronous and may
still be running, or not yet started, when this function returns.
*/
dom.flashOnce = function f(e,howLongMs,afterFlashCallback){
if(e.dataset.isBlinking){
return;
}
if(!howLongMs || 'number'!==typeof howLongMs){
howLongMs = f.defaultTimeMs;
}
e.dataset.isBlinking = true;
const transition = e.style.transition;
e.style.transition = "opacity "+howLongMs+"ms ease-in-out";
| > > > > > > > | 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
in, it must be a function, and it gets callback back at the end
of the asynchronous flashing processes.
This will only activate once per element during that timeframe -
further calls will become no-ops until the blink is
completed. This routine adds a dataset member to the element for
the duration of the blink, to allow it to block multiple blinks.
If passed 2 arguments and the 2nd is a function, it behaves as if
it were called as (arg1, undefined, arg2).
Returns e, noting that the flash itself is asynchronous and may
still be running, or not yet started, when this function returns.
*/
dom.flashOnce = function f(e,howLongMs,afterFlashCallback){
if(e.dataset.isBlinking){
return;
}
if(2===arguments.length && 'function' ===typeof howLongMs){
afterFlashCallback = howLongMs;
howLongMs = f.defaultTimeMs;
}
if(!howLongMs || 'number'!==typeof howLongMs){
howLongMs = f.defaultTimeMs;
}
e.dataset.isBlinking = true;
const transition = e.style.transition;
e.style.transition = "opacity "+howLongMs+"ms ease-in-out";
|
| ︙ | ︙ |
Changes to src/fossil.numbered-lines.js.
| ︙ | ︙ | |||
47 48 49 50 51 52 53 |
init: function(){
const e = this.e;
const btnCopy = D.span(),
link = D.span();
this.state = {link};
F.copyButton(btnCopy,{
copyFromElement: link,
| | | > > > > | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
init: function(){
const e = this.e;
const btnCopy = D.span(),
link = D.span();
this.state = {link};
F.copyButton(btnCopy,{
copyFromElement: link,
extractText: ()=>link.dataset.url,
oncopy: (ev)=>{
F.toast("Copied: ",D.append(D.code(),ev.detail.text));
D.flashOnce(ev.target, undefined, ()=>lineTip.hide());
}
});//.addEventListener('text-copied', (ev)=>D.flashOnce(ev.target));
D.append(this.e, btnCopy, link)
}
});
tbl.addEventListener('click', function f(ev){
lineTip.show(false);
}, false);
|
| ︙ | ︙ |
Changes to src/fossil.popupwidget.js.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 |
Creates a new tooltip-like widget using the given options object.
Options:
.refresh: callback which is called just before the tooltip is
revealed or moved. It must refresh the contents of the tooltip,
if needed, by applying the content to/within this.e, which is the
| | > | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
Creates a new tooltip-like widget using the given options object.
Options:
.refresh: callback which is called just before the tooltip is
revealed or moved. It must refresh the contents of the tooltip,
if needed, by applying the content to/within this.e, which is the
base DOM element for the tooltip (and is a child of
document.body). If the contents are static and set up via the
.init option then this callback is not needed.
.adjustX: an optional callback which is called when the tooltip
is to be displayed at a given position and passed the X
viewport-relative coordinate. This routine must either return its
argument as-is or return an adjusted value. The intent is to
allow a given tooltip may be positioned more appropriately for a
given context, if needed (noting that the desired position can,
|
| ︙ | ︙ | |||
142 143 144 145 146 147 148 |
Returns this object.
Sidebar: showing/hiding the widget is, as is conventional for
this framework, done by removing/adding the 'hidden' CSS class
to it, so that class must be defined appropriately.
*/
show: function(){
| | | 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
Returns this object.
Sidebar: showing/hiding the widget is, as is conventional for
this framework, done by removing/adding the 'hidden' CSS class
to it, so that class must be defined appropriately.
*/
show: function(){
var x = undefined, y = undefined, 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];
|
| ︙ | ︙ | |||
165 166 167 168 169 170 171 |
if(showIt){
this.refresh();
x = this.options.adjustX.call(this,x);
y = this.options.adjustY.call(this,y);
x += window.pageXOffset;
y += window.pageYOffset;
}
| | | > | | | > > > > > > > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
if(showIt){
this.refresh();
x = this.options.adjustX.call(this,x);
y = this.options.adjustY.call(this,y);
x += window.pageXOffset;
y += window.pageYOffset;
}
console.debug("showIt?",showIt,x,y);
if(showIt){
if('number'===typeof x && 'number'===typeof y){
this.e.style.left = x+"px";
this.e.style.top = y+"px";
}
D.removeClass(this.e, 'hidden');
}else{
D.addClass(this.e, 'hidden');
delete this.e.style.removeProperty('left');
delete this.e.style.removeProperty('top');
}
return this;
},
hide: function(){return this.show(false)}
}/*F.PopupWidget.prototype*/;
/**
Convenience wrapper around a PopupWidget which pops up a shared
PopupWidget instance to show toast-style messages (commonly seen
on Android). Its arguments may be anything suitable for passing
to fossil.dom.append(), and each argument is first append()ed to
the toast widget, then the widget is shown for
F.toast.config.displayTimeMs milliseconds. This is called while
a toast is currently being displayed, the first will be overwritten
and the time until the message is hidden will be reset.
The toast is always shown at the viewport-relative coordinates
defined by the F.toast.config.position.
The toaster's DOM element has the CSS classes fossil-tooltip
and fossil-toast, so can be style via those.
*/
F.toast = function f(/*...*/){
if(!f.toast){
f.toast = function ff(argsObject){
if(!ff.toaster) ff.toaster = new F.PopupWidget({
cssClass: ['fossil-tooltip', 'fossil-toast']
});
if(f._timer) clearTimeout(f._timer);
D.clearElement(ff.toaster.e);
var i = 0;
for( ; i < argsObject.length; ++i ){
D.append(ff.toaster.e, argsObject[i]);
};
ff.toaster.show(f.config.position.x, f.config.position.y);
f._timer = setTimeout(()=>ff.toaster.hide(), f.config.displayTimeMs);
};
}
f.toast(arguments);
};
F.toast.config = {
position: { x: 5, y: 5 /*viewport-relative, pixels*/ },
displayTimeMs: 2500
};
})(window.fossil);
|