PageSourceSearch

https://tangentsoft.com/pidp8i/builtin?m=16,20,15,17,18,21,34,19,33,22,32,35&id=7e2fbf00

js tangentsoft.com collected 2026-09-25 18:37:12 UTC 69,070 bytes, 2,786 lines download raw bytes

1/* fossil.bootstrap.js */
2"use strict";
3(function () {
4if(typeof window.CustomEvent === "function") return false;
5window.CustomEvent = function(event, params) {
6if(!params) params = {bubbles: false, cancelable: false, detail: null};
7const evt = document.createEvent('CustomEvent');
8evt.initCustomEvent( event, !!params.bubbles, !!params.cancelable, params.detail );
9return evt;
10};
11})();
12(function(global){
13const F = global.fossil;
14F.nu = (...obj)=>Object.assign(Object.create(null),...obj);
15const timestring = function f(){
16if(!f.rx1){
17f.rx1 = /\.\d+Z$/;
18}
19const d = new Date();
20return d.toISOString().replace(f.rx1,'').split('T').join(' ');
21};
22const localTimeString = function ff(d){
23if(!ff.pad){
24ff.pad = (x)=>(''+x).length>1 ? x : '0'+x;
25}
26d || (d = new Date());
27return [
28d.getFullYear(),'-',ff.pad(d.getMonth()+1),
29'-',ff.pad(d.getDate()),
30' ',ff.pad(d.getHours()),':',ff.pad(d.getMinutes()),
31':',ff.pad(d.getSeconds())
32].join('');
33};
34F.message = function f(){
35const args = Array.prototype.slice.call(arguments,0);
36const tgt = f.targetElement;
37if(args.length) args.unshift(
38localTimeString()+':'
39);
40if(tgt){
41tgt.classList.remove('error');
42tgt.innerText = args.join(' ');
43}
44else{
45if(args.length){
46args.unshift('Fossil status:');
47console.debug.apply(console,args);
48}
49}
50return this;
51};
52F.message.targetElement =
53document.querySelector('#fossil-status-bar');
54if(F.message.targetElement){
55F.message.targetElement.addEventListener(
56'dblclick', ()=>F.message(), false
57);
58}
59F.error = function f(){
60const args = Array.prototype.slice.call(arguments,0);
61const tgt = F.message.targetElement;
62args.unshift(timestring(),'UTC:');
63if(tgt){
64tgt.classList.add('error');
65tgt.innerText = args.join(' ');
66}else{
67args.unshift('Fossil error:');
68console.error.apply(console,args);
69}
70return this;
71};
72F.encodeUrlArgs = function(obj,tgtArray,fakeEncode){
73if(!obj) return '';
74const a = (tgtArray instanceof Array) ? tgtArray : [],
75enc = fakeEncode ? (x)=>x : encodeURIComponent;
76let k, i = 0;
77for( k in obj ){
78if(i++) a.push('&');
79a.push(enc(k),'=',enc(obj[k]));
80}
81return a===tgtArray ? a : a.join('');
82};
83F.repoUrl = function(path,urlParams){
84if(!urlParams) return this.rootPath+path;
85const url=[this.rootPath,path];
86url.push('?');
87if('string'===typeof urlParams) url.push(urlParams);
88else if(urlParams && 'object'===typeof urlParams){
89this.encodeUrlArgs(urlParams, url);
90}
91return url.join('');
92};
93F.isObject = function(v){
94return v &&
95(v instanceof Object) &&
96('[object Object]' === Object.prototype.toString.apply(v) );
97};
98F.mergeLastWins = function(){
99var k, o, i;
100const n = arguments.length, rc={};
101for(i = 0; i < n; ++i){
102if(!F.isObject(o = arguments[i])) continue;
103for( k in o ){
104if(o.hasOwnProperty(k)) rc[k] = o[k];
105}
106}
107return rc;
108};
109F.hashDigits = function(hash,forUrl){
110const n = ('number'===typeof forUrl)
111? forUrl : F.config[forUrl ? 'hashDigitsUrl' : 'hashDigits'];
112return ('string'==typeof hash ? hash.substr(
1130, n
114) : hash);
115};
116F.onPageLoad = function(callback){
117window.addEventListener('load', callback, false);
118return this;
119};
120F.onDOMContentLoaded = function(callback){
121window.addEventListener('DOMContentLoaded', callback, false);
122return this;
123};
124F.shortenFilename = function(name){
125const a = name.split('/');
126if(a.length<=2) return name;
127while(a.length>2) a.shift();
128return '.../'+a.join('/');
129};
130F.page.addEventListener = function f(eventName, callback){
131if(!f.proxy){
132f.proxy = document.createElement('span');
133}
134f.proxy.addEventListener(eventName, callback, false);
135return this;
136};
137F.page.dispatchEvent = function(eventName, eventDetail){
138if(this.addEventListener.proxy){
139try{
140this.addEventListener.proxy.dispatchEvent(
141new CustomEvent(eventName,{detail: eventDetail})
142);
143}catch(e){
144console.error(eventName,"event listener threw:",e);
145}
146}
147return this;
148};
149F.page.setPageTitle = function f(title){
150const t = document.querySelector('title');
151if(t) t.innerText = title || f.$orig;
152return this;
153};
154F.onPageLoad(()=>F.page.setPageTitle.$orig
155= document.querySelector('title').innerText);
156F.debounce = function f(func, waitMs, immediate) {
157var timeoutId;
158if(!waitMs) waitMs = f.$defaultDelay;
159return function() {
160const context = this, args = Array.prototype.slice.call(arguments);
161const later = function() {
162timeoutId = undefined;
163if(!immediate) func.apply(context, args);
164};
165const callNow = immediate && !timeoutId;
166clearTimeout(timeoutId);
167timeoutId = setTimeout(later, waitMs);
168if(callNow) func.apply(context, args);
169};
170};
171F.debounce.$defaultDelay = 500;
172})(window);
173/* fossil.dom.js */
174"use strict";
175(function(F){
176const argsToArray = (a)=>Array.prototype.slice.call(a,0);
177const isArray = (v)=>v instanceof Array;
178const dom = {
179create: function(elemType){
180return document.createElement(elemType);
181},
182createElemFactory: function(eType){
183return function(){
184return document.createElement(eType);
185};
186},
187remove: function(e){
188if(e?.forEach){
189e.forEach(
190(x)=>x?.parentNode?.removeChild(x)
191);
192}else{
193e?.parentNode?.removeChild(e);
194}
195return e;
196},
197clearElement: function f(e){
198if(!f.each){
199f.each = function(e){
200if(e.forEach){
201e.forEach((x)=>f(x));
202return e;
203}
204while(e.firstChild) e.removeChild(e.firstChild);
205};
206}
207argsToArray(arguments).forEach(f.each);
208return arguments[0];
209},
210};
211dom.splitClassList = function f(str){
212if(!f.rx){
213f.rx = /(\s+|\s*,\s*)/;
214}
215return str ? str.split(f.rx) : [str];
216};
217dom.div = dom.createElemFactory('div');
218dom.p = dom.createElemFactory('p');
219dom.code = dom.createElemFactory('code');
220dom.pre = dom.createElemFactory('pre');
221dom.header = dom.createElemFactory('header');
222dom.footer = dom.createElemFactory('footer');
223dom.section = dom.createElemFactory('section');
224dom.span = dom.createElemFactory('span');
225dom.strong = dom.createElemFactory('strong');
226dom.em = dom.createElemFactory('em');
227dom.ins = dom.createElemFactory('ins');
228dom.del = dom.createElemFactory('del');
229dom.label = function(forElem, text){
230const rc = document.createElement('label');
231if(forElem){
232if(forElem instanceof HTMLElement){
233forElem = this.attr(forElem, 'id');
234}
235if(forElem){
236dom.attr(rc, 'for', forElem);
237}
238}
239if(text) this.append(rc, text);
240return rc;
241};
242dom.img = function(src){
243const e = this.create('img');
244if(src) e.setAttribute('src',src);
245return e;
246};
247dom.a = function(href,label){
248const e = this.create('a');
249if(href) e.setAttribute('href',href);
250if(label) e.appendChild(dom.text(true===label ? href : label));
251return e;
252};
253dom.hr = dom.createElemFactory('hr');
254dom.br = dom.createElemFactory('br');
255dom.text = function(){
256return document.createTextNode(argsToArray(arguments).join(''));
257};
258dom.button = function(label,callback){
259const b = this.create('button');
260if(label) b.appendChild(this.text(label));
261if('function' === typeof callback){
262b.addEventListener('click', callback, false);
263}
264return b;
265};
266dom.textarea = function(){
267const rc = this.create('textarea');
268let rows, cols, readonly;
269if(1===arguments.length){
270if('boolean'===typeof arguments[0]){
271readonly = !!arguments[0];
272}else{
273rows = arguments[0];
274}
275}else if(arguments.length){
276rows = arguments[0];
277cols = arguments[1];
278readonly = arguments[2];
279}
280if(rows) rc.setAttribute('rows',rows);
281if(cols) rc.setAttribute('cols', cols);
282if(readonly) rc.setAttribute('readonly', true);
283return rc;
284};
285dom.select = dom.createElemFactory('select');
286dom.option = function(value,label){
287const a = arguments;
288var sel;
289if(1==a.length){
290if(a[0] instanceof HTMLElement){
291sel = a[0];
292}else{
293value = a[0];
294}
295}else if(2==a.length){
296if(a[0] instanceof HTMLElement){
297sel = a[0];
298value = a[1];
299}else{
300value = a[0];
301label = a[1];
302}
303}
304else if(3===a.length){
305sel = a[0];
306value = a[1];
307label = a[2];
308}
309const o = this.create('option');
310if(undefined !== value){
311o.value = value;
312this.append(o, this.text(label || value));
313}else if(undefined !== label){
314this.append(o, label);
315}
316if(sel) this.append(sel, o);
317return o;
318};
319dom.h = function(level){
320return this.create('h'+level);
321};
322dom.ul = dom.createElemFactory('ul');
323dom.li = function(parent){
324const li = this.create('li');
325if(parent) parent.appendChild(li);
326return li;
327};
328dom.createElemFactoryWithOptionalParent = function(childType){
329return function(parent){
330const e = this.create(childType);
331if(parent) parent.appendChild(e);
332return e;
333};
334};
335dom.table = dom.createElemFactory('table');
336dom.thead = dom.createElemFactoryWithOptionalParent('thead');
337dom.tbody = dom.createElemFactoryWithOptionalParent('tbody');
338dom.tfoot = dom.createElemFactoryWithOptionalParent('tfoot');
339dom.tr = dom.createElemFactoryWithOptionalParent('tr');
340dom.td = dom.createElemFactoryWithOptionalParent('td');
341dom.th = dom.createElemFactoryWithOptionalParent('th');
342dom.fieldset = function(legendText){
343const fs = this.create('fieldset');
344if(legendText){
345this.append(
346fs,
347(legendText instanceof HTMLElement)
348? legendText
349: this.append(this.legend(legendText))
350);
351}
352return fs;
353};
354dom.legend = function(legendText){
355const rc = this.create('legend');
356if(legendText) this.append(rc, legendText);
357return rc;
358};
359dom.append = function f(parent){
360const a = argsToArray(arguments);
361a.shift();
362for(let i in a) {
363var e = a[i];
364if(isArray(e) || e.forEach){
365e.forEach((x)=>f.call(this, parent,x));
366continue;
367}
368if('string'===typeof e
369|| 'number'===typeof e
370|| 'boolean'===typeof e
371|| e instanceof Error) e = this.text(e);
372parent.appendChild(e);
373}
374return parent;
375};
376dom.input = function(type){
377return this.attr(this.create('input'), 'type', type);
378};
379dom.checkbox = function(value, checked){
380const rc = this.input('checkbox');
381if(1===arguments.length && 'boolean'===typeof value){
382checked = !!value;
383value = undefined;
384}
385if(undefined !== value) rc.value = value;
386if(!!checked) rc.checked = true;
387return rc;
388};
389dom.radio = function(){
390const rc = this.input('radio');
391let name, value, checked;
392if(1===arguments.length && 'boolean'===typeof name){
393checked = arguments[0];
394name = value = undefined;
395}else if(2===arguments.length){
396name = arguments[0];
397if('boolean'===typeof arguments[1]){
398checked = arguments[1];
399}else{
400value = arguments[1];
401checked = undefined;
402}
403}else if(arguments.length){
404name = arguments[0];
405value = arguments[1];
406checked = arguments[2];
407}
408if(name) this.attr(rc, 'name', name);
409if(undefined!==value) rc.value = value;
410if(!!checked) rc.checked = true;
411return rc;
412};
413const domAddRemoveClass = function f(action,e){
414if(!f.rxSPlus){
415f.rxSPlus = /\s+/;
416f.applyAction = function(e,a,v){
417if(!e || !v
418) return;
419else if(e.forEach){
420e.forEach((E)=>E.classList[a](v));
421}else{
422e.classList[a](v);
423}
424};
425}
426var i = 2, n = arguments.length;
427for( ; i < n; ++i ){
428let c = arguments[i];
429if(!c) continue;
430else if(isArray(c) ||
431('string'===typeof c
432&& c.indexOf(' ')>=0
433&& (c = c.split(f.rxSPlus)))
434|| c.forEach
435){
436c.forEach((k)=>k ? f.applyAction(e, action, k) : false);
437}else if(c){
438f.applyAction(e, action, c);
439}
440}
441return e;
442};
443dom.addClass = function(e,c){
444const a = argsToArray(arguments);
445a.unshift('add');
446return domAddRemoveClass.apply(this, a);
447};
448dom.removeClass = function(e,c){
449const a = argsToArray(arguments);
450a.unshift('remove');
451return domAddRemoveClass.apply(this, a);
452};
453dom.toggleClass = function f(e,c){
454if(e.forEach){
455e.forEach((x)=>x.classList.toggle(c));
456}else{
457e.classList.toggle(c);
458}
459return e;
460};
461dom.hasClass = function(e,c){
462return (e && e.classList) ? e.classList.contains(c) : false;
463};
464dom.moveTo = function(dest,e){
465const n = arguments.length;
466var i = 1;
467const self = this;
468for( ; i < n; ++i ){
469e = arguments[i];
470this.append(dest, e);
471}
472return dest;
473};
474dom.moveChildrenTo = function f(dest,e){
475if(!f.mv){
476f.mv = function(d,v){
477if(d instanceof Array){
478d.push(v);
479if(v.parentNode) v.parentNode.removeChild(v);
480}
481else d.appendChild(v);
482};
483}
484const n = arguments.length;
485var i = 1;
486for( ; i < n; ++i ){
487e = arguments[i];
488if(!e){
489console.warn("Achtung: dom.moveChildrenTo() passed a falsy value at argument",i,"of",
490arguments,arguments[i]);
491continue;
492}
493if(e.forEach){
494e.forEach((x)=>f.mv(dest, x));
495}else{
496while(e.firstChild){
497f.mv(dest, e.firstChild);
498}
499}
500}
501return dest;
502};
503dom.replaceNode = function f(old,nu){
504var i = 1, n = arguments.length;
505++f.counter;
506try {
507for( ; i < n; ++i ){
508const e = arguments[i];
509if(e.forEach){
510e.forEach((x)=>f.call(this,old,e));
511continue;
512}
513old.parentNode.insertBefore(e, old);
514}
515}
516finally{
517--f.counter;
518}
519if(!f.counter){
520old.parentNode.removeChild(old);
521}
522};
523dom.replaceNode.counter = 0;
524dom.attr = function f(e){
525if(2===arguments.length) return e.getAttribute(arguments[1]);
526const a = argsToArray(arguments);
527if(e.forEach){
528e.forEach(function(x){
529a[0] = x;
530f.apply(f,a);
531});
532return e;
533}
534a.shift();
535while(a.length){
536const key = a.shift(), val = a.shift();
537if(null===val || undefined===val){
538e.removeAttribute(key);
539}else{
540e.setAttribute(key,val);
541}
542}
543return e;
544};
545const enableDisable = function f(enable){
546var i = 1, n = arguments.length;
547for( ; i < n; ++i ){
548let e = arguments[i];
549if(e.forEach){
550e.forEach((x)=>f(enable,x));
551}else{
552e.disabled = !enable;
553}
554}
555return arguments[1];
556};
557dom.enable = function(e){
558const args = argsToArray(arguments);
559args.unshift(true);
560return enableDisable.apply(this,args);
561};
562dom.disable = function(e){
563const args = argsToArray(arguments);
564args.unshift(false);
565return enableDisable.apply(this,args);
566};
567dom.selectOne = function(x,origin){
568var src = origin || document,
569e = src.querySelector(x);
570if(!e){
571e = new Error("Cannot find DOM element: "+x);
572console.error(e, src);
573throw e;
574}
575return e;
576};
577dom.flashOnce = function f(e,howLongMs,afterFlashCallback){
578if(e.dataset.isBlinking){
579return;
580}
581if(2===arguments.length && 'function' ===typeof howLongMs){
582afterFlashCallback = howLongMs;
583howLongMs = f.defaultTimeMs;
584}
585if(!howLongMs || 'number'!==typeof howLongMs){
586howLongMs = f.defaultTimeMs;
587}
588e.dataset.isBlinking = true;
589const transition = e.style.transition;
590e.style.transition = "opacity "+howLongMs+"ms ease-in-out";
591const opacity = e.style.opacity;
592e.style.opacity = 0;
593setTimeout(function(){
594e.style.transition = transition;
595e.style.opacity = opacity;
596delete e.dataset.isBlinking;
597if(afterFlashCallback) afterFlashCallback();
598}, howLongMs);
599return e;
600};
601dom.flashOnce.defaultTimeMs = 400;
602dom.flashOnce.eventHandler = (event)=>dom.flashOnce(event.target)
603dom.flashNTimes = function(e,n,howLongMs,afterFlashCallback){
604const args = argsToArray(arguments);
605args.splice(1,1);
606if(arguments.length===3 && 'function'===typeof howLongMs){
607afterFlashCallback = howLongMs;
608howLongMs = args[1] = this.flashOnce.defaultTimeMs;
609}else if(arguments.length<3){
610args[1] = this.flashOnce.defaultTimeMs;
611}
612n = +n;
613const self = this;
614const cb = args[2] = function f(){
615if(--n){
616setTimeout(()=>self.flashOnce(e, howLongMs, f),
617howLongMs+(howLongMs*0.1));
618}else if(afterFlashCallback){
619afterFlashCallback();
620}
621};
622this.flashOnce.apply(this, args);
623return this;
624};
625dom.addClassBriefly = function f(e, className, howLongMs, afterCallback){
626if(arguments.length<4 && 'function'===typeof howLongMs){
627afterCallback = howLongMs;
628howLongMs = f.defaultTimeMs;
629}else if(arguments.length<3 || !+howLongMs){
630howLongMs = f.defaultTimeMs;
631}
632this.addClass(e, className);
633setTimeout(function(){
634dom.removeClass(e, className);
635if(afterCallback) afterCallback();
636}, howLongMs);
637return this;
638};
639dom.addClassBriefly.defaultTimeMs = 1000;
640dom.copyTextToClipboard = function(text){
641if( window.clipboardData && window.clipboardData.setData ){
642window.clipboardData.setData('Text',text);
643return true;
644}else{
645const x = document.createElement("textarea");
646x.style.position = 'fixed';
647x.value = text;
648document.body.appendChild(x);
649x.select();
650var rc;
651try{
652document.execCommand('copy');
653rc = true;
654}catch(err){
655rc = false;
656}finally{
657document.body.removeChild(x);
658}
659return rc;
660}
661};
662dom.copyStyle = function f(e, style){
663if(e.forEach){
664e.forEach((x)=>f(x, style));
665return e;
666}
667if(style){
668let k;
669for(k in style){
670if(style.hasOwnProperty(k)) e.style[k] = style[k];
671}
672}
673return e;
674};
675dom.effectiveHeight = function f(e){
676if(!e) return 0;
677if(!f.measure){
678f.measure = function callee(e, depth){
679if(!e) return;
680const m = e.getBoundingClientRect();
681if(0===depth){
682callee.top = m.top;
683callee.bottom = m.bottom;
684}else{
685callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
686callee.bottom = Math.max(callee.bottom, m.bottom);
687}
688Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
689if(0===depth){
690f.extra += callee.bottom - callee.top;
691}
692return f.extra;
693};
694}
695f.extra = 0;
696f.measure(e,0);
697return f.extra;
698};
699dom.parseHtml = function(){
700let childs, string, tgt;
701if(1===arguments.length){
702string = arguments[0];
703}else if(2==arguments.length){
704tgt = arguments[0];
705string  = arguments[1];
706}
707if(string){
708const newNode = new DOMParser().parseFromString(string, 'text/html');
709childs = newNode.documentElement.querySelector('body');
710childs = childs ? Array.prototype.slice.call(childs.childNodes, 0) : [];
711}else{
712childs = [];
713}
714return tgt ? this.moveTo(tgt, childs) : childs;
715};
716F.connectPagePreviewers = function f(selector,methodNamespace){
717if('string'===typeof selector){
718selector = document.querySelectorAll(selector);
719}else if(!selector.forEach){
720selector = [selector];
721}
722if(!methodNamespace){
723methodNamespace = F.page;
724}
725selector.forEach(function(e){
726e.addEventListener(
727'click', function(r){
728const eTo = '#'===e.dataset.fPreviewTo[0]
729? document.querySelector(e.dataset.fPreviewTo)
730: methodNamespace[e.dataset.fPreviewTo],
731eFrom = '#'===e.dataset.fPreviewFrom[0]
732? document.querySelector(e.dataset.fPreviewFrom)
733: methodNamespace[e.dataset.fPreviewFrom],
734asText = +(e.dataset.fPreviewAsText || 0);
735eTo.textContent = "Fetching preview...";
736methodNamespace[e.dataset.fPreviewVia](
737(eFrom instanceof Function ? eFrom.call(methodNamespace) : eFrom.value),
738function(r){
739if(eTo instanceof Function) eTo.call(methodNamespace, r||'');
740else if(!r){
741dom.clearElement(eTo);
742}else if(asText){
743eTo.textContent = r;
744}else{
745dom.parseHtml(dom.clearElement(eTo), r);
746}
747}
748);
749}, false
750);
751});
752return this;
753};
754return F.dom = dom;
755})(window.fossil);
756/* fossil.attach.js */
757"use strict";
758(function(namespace){
759"use strict";
760const F = namespace, D = F.dom;
761let idCounter = 0;
762class Attacher {
763#opt;
764#rows = [];
765#e = Object.create(null);
766#events = new EventTarget();
767constructor(opt){
768this.#opt = opt = F.nu({
769addButtonLabel: false,
770startWith: 0,
771limit: 0,
772dryRun: undefined,
773description: true,
774reverse: false
775}, opt);
776this.#e.body = D.addClass(D.div(), 'Attacher');
777if( opt.reverse ) this.#e.body.classList.add('reverse');
778const eBtnAdd = this.#e.btnAdd = D.addClass(
779D.button(this.#opt.addButtonLabel || 'Add attachment',
780()=>this.#addRow()),
781'attach-add-button'
782);
783eBtnAdd.type = 'button';
784opt.ownsAddButton = true;
785this.#e.err = D.addClass(D.div(), 'error', 'hidden');
786this.#e.body.append(this.#e.err);
787this.#e.err.addEventListener('dblclick',()=>this.reportError());
788const eControls = this.#e.controls =
789D.addClass(D.div(), 'attach-controls');
790eControls.append(eBtnAdd);
791if( opt.container ){
792opt.container.appendChild(this.#e.body);
793}
794this.#e.body.appendChild(eControls);
795if( opt.listener ){
796const doCb = (eventType, key)=>{
797const f = (opt.listener instanceof Function)
798? opt.listener
799: (opt.listener[key] || opt.listener.all);
800if( f instanceof Function ){
801this.addEventListener(eventType, f);
802}
803};
804doCb('entry-added', 'add');
805doCb('entry-removed', 'remove');
806doCb('entry-populated', 'populate');
807}
808if( opt.dryRun ){
809const eLbl = D.label(false, "Dry-run?");
810const eCb = D.checkbox(true);
811eLbl.append(eCb);
812eControls.append(eLbl);
813eCb.checked = opt.dryRun = true;
814eCb.addEventListener('change',()=>opt.dryRun=eCb.checked);
815}
816if( Array.isArray(opt.controls) ){
817eControls.append(...opt.controls);
818}
819if( opt.startWith > 0 ){
820for(let i = 0; i < opt.startWith; ++i ){
821this.#addRow();
822}
823}else{
824this.#updateControls();
825}
826}
827get widget(){
828return this.#e.body;
829}
830addEventListener(...args){
831return this.#events.addEventListener(...args);
832}
833removeEventListener(...args){
834return this.#events.removeEventListener(...args);
835}
836get isPopulated(){
837for(let r of this.#rows){
838if( r.file ) return true;
839}
840return false;
841}
842get isDryRun(){
843return !!this.#opt.dryRun;
844}
845get controlsElement(){
846return this.#e.controls;
847}
848reportError(...msg){
849const e = this.#e.err;
850D.clearElement(e);
851if( msg.length ){
852e.classList.remove('hidden');
853e.append(...msg);
854}else{
855e.classList.add('hidden');
856}
857}
858#removeRow(rowObj){
859const er = rowObj.e.row;
860if( er.parentNode ){
861this.#rows = this.#rows.filter(v=>v!==rowObj);
862this.#updateControls();
863er.classList.add('animate-exit');
864er.addEventListener('animationend', ()=>er.remove(), {once: true});
865this.#events.dispatchEvent(
866new CustomEvent('entry-removed',{
867detail: F.nu({
868type: 'entry-removed',
869row: rowObj,
870attacher: this
871})
872})
873);
874}
875}
876clear(){
877for(const r of [...this.#rows]){
878this.#removeRow(r);
879}
880this.reportError();
881}
882#updateControls(){
883const b = this.#e.btnAdd;
884if( this.#opt.limit>0 && this.#rows.length >= this.#opt.limit ){
885b.classList.add('hidden');
886D.disable(b);
887}else{
888b.classList.remove('hidden');
889D.enable(b);
890if( this.#opt.ownsAddButton ){
891this.#e.body.append(this.#e.controls);
892}
893}
894}
895takeAddButton(){
896if( this.#opt.ownsAddButton ){
897this.#opt.ownsAddButton;
898}
899return this.#e.btnAdd;
900}
901#rowError(rowObj,...msg){
902let e = rowObj.e.err;
903if( e ){
904D.clearElement(e);
905}else{
906if( !msg.length ) return;
907e = rowObj.e.err = D.addClass(D.span(), 'error');
908rowObj.e.info.append(e);
909}
910if( msg.length ){
911e.append(...msg);
912e.classList.remove('hidden');
913}else{
914e.classList.add('hidden');
915}
916}
917#addRow(){
918const id = ++idCounter;
919const rowObj = F.nu({
920id, file: null, mimeType: ''
921});
922const eRow = D.addClass(D.div(), 'attach-row');
923const eDropzone = D.addClass(D.div(), 'attach-dropzone');
924const eFile = D.addClass(
925D.input('file'), 'attach-file-input', 'hidden'
926);
927const eInfo = D.addClass(D.span(), 'attach-row-info');
928const eFilename = D.append(
929D.addClass(D.span(), 'attach-filename'),
930"Select/drop file or click the outer border and tap your "+
931"platform's conventional <paste> keyboard shortcut."
932);
933const eSize = D.addClass(D.span(), 'attach-size');
934eInfo.append(eFilename, eSize);
935const eDesc = this.#opt.description
936? D.addClass(
937D.attr(D.textarea(), 'placeholder',
938'Optional description...'),
939'attach-desc'
940)
941: undefined;
942const eRemove = D.addClass(
943D.button('X', (ev)=>{
944ev.stopPropagation();
945this.#removeRow(rowObj);
946}),
947'attach-row-remove'
948);
949eRemove.setAttribute('title', 'Remove this attachment.');
950eRemove.type = 'button';
951D.append(eDropzone, eInfo, eFile, eRemove);
952eDropzone.addEventListener('click', ()=>eFile.click());
953eFile.addEventListener('change', (ev)=>{
954if( ev.target.files.length ){
955this.#injestBlob(rowObj, ev.target.files[0]);
956}
957});
958eDropzone.addEventListener('dragover', (ev)=>{
959ev.preventDefault();
960eDropzone.classList.add('dragover');
961});
962eDropzone.addEventListener('dragleave', (ev)=>{
963eDropzone.classList.remove('dragover');
964});
965const handleDrop = (ev, theRealRowObj)=>{
966ev.preventDefault();
967eDropzone.classList.remove('dragover');
968if( ev.dataTransfer.files.length ){
969const r = theRealRowObj || rowObj;
970this.#injestBlob(r, ev.dataTransfer.files[0]);
971}
972};
973eDropzone.addEventListener('drop', handleDrop);
974const pasteImage = (event, item)=>{
975if( item.type.indexOf('image') === 0 ) {
976event.preventDefault();
977const blob = item.getAsFile();
978if( blob.name === 'image.png' ){
979rowObj.overrideName = `pasted-image-${Date.now()}.png`;
980}
981this.#injestBlob(rowObj, blob);
982return true;
983}
984return false;
985};
986const pasteThing = (event, thing)=>{
987if( pasteImage(event, thing) ) return true;
988if( 'file' === thing.kind ){
989event.preventDefault();
990const blob = thing.getAsFile();
991if( blob ){
992this.#injestBlob(rowObj, blob);
993return true;
994}
995}
996return false;
997};
998eDesc?.addEventListener?.('paste', (e) => {
999e.stopPropagation();
1000const items = (e.clipboardData || e.originalEvent.clipboardData)?.items;
1001if( !items ) return;
1002for( let i = 0; i < items.length; ++i ){
1003const item = items[i];
1004if( pasteThing(e, item) ){
1005break;
1006}
1007}
1008});
1009eRow.addEventListener('paste', (e) => {
1010const items = (e.clipboardData || e.originalEvent.clipboardData)?.items;
1011if( !items ) return;
1012for( let i = 0; i < items.length; ++i ){
1013const item = items[i];
1014if( item.type === 'text/plain' ){
1015e.preventDefault();
1016item.getAsString((text) => {
1017rowObj.overrideName = `pasted-text-${Date.now()}.txt`;
1018const blob = new File([text], rowObj.overrideName,
1019{type: 'text/plain'});
1020this.#injestBlob(rowObj, blob);
1021});
1022break;
1023}else if( pasteThing(e, item) ){
1024break;
1025}
1026}
1027});
1028eRow.append(eDropzone);
1029if( eDesc ) eRow.append(eDesc);
1030rowObj.e = F.nu({
1031dropzone: eDropzone,
1032info: eInfo,
1033filename: eFilename,
1034size: eSize,
1035desc: eDesc,
1036row: eRow,
1037remove: eRemove
1038});
1039this.#e.body.append(eRow);
1040eRow.classList.add('animate-entrance');
1041requestAnimationFrame(() => {
1042eRow.scrollIntoView({
1043behavior: 'smooth',
1044block: 'nearest',
1045inline: 'nearest'
1046});
1047});
1048this.#rows.push( rowObj );
1049this.#updateControls();
1050this.#events.dispatchEvent(
1051new CustomEvent('entry-added',{
1052detail: F.nu({
1053type: 'entry-added',
1054row: rowObj,
1055attacher: this
1056})
1057})
1058);
1059if( 0 ){
1060D.attr(eRow, 'tabindex', '-1');
1061eRow.focus();
1062}
1063}
1064#rowMatchingName(name){
1065for(let r of this.#rows){
1066if( r.file?.name===name ) return r;
1067}
1068}
1069#injestBlob(rowObj, file){
1070if( !file ) return;
1071const old = this.#rowMatchingName(file.name);
1072if( rowObj.overrideName ){
1073if( rowObj.overrideName !== file.name ){
1074file = new File([file], rowObj.overrideName, {type: file.type});
1075}
1076rowObj.overrideName = undefined;
1077}
1078if( old && rowObj !== old ){
1079this.#rowError(old);
1080this.#removeRow(rowObj);
1081rowObj = old;
1082}
1083let szLbl;
1084if( file.size < 500000 ){
1085szLbl = file.size + ' bytes';
1086}else if( file.size < 1000000 ){
1087szLbl = (file.size / 1024).toFixed(2)+' KB';
1088}else{
1089szLbl = (file.size / (1024 * 1024)).toFixed(2)+' MB';
1090}
1091this.#rowError(rowObj);
1092rowObj.file = file;
1093rowObj.mimeType = file.type || 'application/octet-stream';
1094D.clearElement(rowObj.e.filename).append(file.name || 'Pasted Content');
1095D.clearElement(rowObj.e.size).append(szLbl, ' ', rowObj.mimeType || '');
1096rowObj.e.dropzone.classList.add('populated');
1097if( rowObj.e.desc ){
1098rowObj.e.desc.classList.remove('hidden');
1099}
1100if( rowObj.e.thumbnail ){
1101rowObj.e.thumbnail.remove();
1102rowObj.e.thumbnail = undefined;
1103}
1104if( file.type?.startsWith?.('image/') || file.type==='BITMAP' ){
1105const img = rowObj.e.thumbnail = D.img();
1106rowObj.e.dropzone.insertBefore(img, rowObj.e.remove);
1107img.classList.add('thumbnail');
1108const reader = new FileReader();
1109reader.onload = (e)=>img.setAttribute('src', e.target.result);
1110reader.readAsDataURL(file);
1111}
1112if( F.config.attachmentSizeLimit>0
1113&& file.size>F.config.attachmentSizeLimit ){
1114const eLink = D.a(F.repoUrl('help/attachment-size-limit'),'limit');
1115eLink.addEventListener('click', ev=>ev.stopPropagation());
1116this.#rowError(rowObj, "Too large: ", eLink,
1117" is ",F.config.attachmentSizeLimit," bytes");
1118rowObj.ok = false;
1119}else if( !file.size ){
1120this.#rowError(rowObj, "Cannot attach zero-byte files.");
1121rowObj.ok = false;
1122}else{
1123rowObj.ok = true;
1124}
1125this.#events.dispatchEvent(
1126new CustomEvent('entry-populated',{
1127detail: F.nu({
1128type: 'entry-populated',
1129row: rowObj,
1130attacher: this
1131})
1132})
1133);
1134}
1135collectState(){
1136const rv = [];
1137for(let r of this.#rows){
1138if( !r.e.dropzone?.classList?.contains?.('populated') ){
1139continue;
1140}
1141rv.push(F.nu({
1142name: r.name || r.file.name,
1143content: r.file,
1144description: r.e.desc?.value ?? '',
1145mimeType: r.mimeType
1146}));
1147}
1148return rv;
1149}
1150populateFormData(fd, namePrefix='file'){
1151const st = this.collectState();
1152let i = 0;
1153for( ; i < st.length; ++i){
1154const s = st[i];
1155const suffix = i+1;
1156fd.append(`${namePrefix}${suffix}`, s.content, s.name);
1157const d = s.description?.trim?.();
1158if( d ){
1159fd.append(`${namePrefix}${suffix}_desc`, d);
1160}
1161}
1162return i;
1163}
1164};
1165F.Attacher = Attacher;
1166F.onPageLoad(function(){
1167const eAttachWrapper = document.querySelector('#attachadd-form-wrapper');
1168if( eAttachWrapper ){
1169document.body.querySelectorAll('#attachadd-legacy-form').forEach(e=>e.remove());
1170eAttachWrapper.classList.remove('hidden');
1171const urlArgs = new URLSearchParams(window.location.search);
1172let zTarget = urlArgs.get('target');
1173let zTo = urlArgs.get('to') || urlArgs.get('from');
1174const eBtnSubmit = D.button("Submit");
1175eBtnSubmit.type = 'button';
1176const updateBtnSubmit = (attacher)=>{
1177if( attacher.isPopulated ){
1178eBtnSubmit.removeAttribute('disabled');
1179}else{
1180eBtnSubmit.setAttribute('disabled', '');
1181}
1182};
1183const cbAttacherChange = (ev)=>{
1184const a = ev.detail.attacher;
1185updateBtnSubmit(a);
1186};
1187const att = new Attacher({
1188container: eAttachWrapper,
1189startWith: 1,
1190listener: cbAttacherChange,
1191controls: [eBtnSubmit],
1192description: true
1193});
1194eBtnSubmit.addEventListener('click', async (ev)=>{
1195att.reportError();
1196const li = att.collectState();
1197if( !li.length ) return;
1198if( eBtnSubmit.dataset.submitted ) return;
1199eBtnSubmit.dataset.submitted = 1;
1200D.disable(eBtnSubmit);
1201const fd = new FormData();
1202att.populateFormData(fd);
1203for( const eIn of eAttachWrapper.querySelectorAll(
1204'input[type="hidden"]'
1205) ){
1206if( eIn.name==='target' ){
1207zTarget = eIn.value;
1208}else if( eIn.name==='to' || (eIn.name==='from' && !zTo) ){
1209zTo = eIn.value;
1210}
1211fd.append(eIn.name, eIn.value)
1212}
1213if( att.isDryRun ){
1214fd.append('dryrun', '1');
1215}
1216let err;
1217const resp = await window.fetch(F.repoUrl('attachadd_ajax_post'), {
1218method: 'POST',
1219body: fd
1220}).catch((e)=>{
1221err = e;
1222});
1223D.enable(eBtnSubmit);
1224delete eBtnSubmit.dataset.submitted;
1225const jr = err ? undefined : await resp.json().catch(()=>{});
1226if( err || jr?.error || !resp.ok ){
1227const msg = err ? err.message : (jr?.error || resp.statusText);
1228att.reportError("Attaching failed: ", msg);
1229}else{
1230att.clear();
1231let to = zTo || jr?.redirect;
1232if( to ){
1233if( '/'!==to[0] ){
1234to = F.repoUrl(to);
1235}
1236window.location = to;
1237}else if( zTarget ){
1238window.location = '?target='+zTarget+'&'+Date.now();
1239}
1240}
1241});
1242updateBtnSubmit(att);
1243F.page.attacher = att;
1244}
1245});
1246})(window.fossil);
1247/* fossil.confirmer.js */
1248"use strict";
1249(function(F){
1250F.confirmer = function f(elem,opt){
1251const dbg = opt.debug
1252? function(){console.debug.apply(console,arguments)}
1253: function(){};
1254dbg("confirmer opt =",opt);
1255if(!f.Holder){
1256f.isInput = (e)=>/^(input|textarea)$/i.test(e.nodeName);
1257f.Holder = function(target,opt){
1258const self = this;
1259this.target = target;
1260this.opt = opt;
1261this.timerID = undefined;
1262this.state = this.states.initial;
1263const isInput = f.isInput(target);
1264const updateText = function(msg){
1265if(isInput) target.value = msg;
1266else{
1267const newNode = new DOMParser().parseFromString(msg, 'text/html');
1268let childs = newNode.documentElement.querySelector('body');
1269childs = childs ? Array.prototype.slice.call(childs.childNodes, 0) : [];
1270target.innerText = '';
1271childs.forEach((e)=>target.appendChild(e));
1272}
1273}
1274const formatCountdown = (txt, number) => txt + " ["+number+"]";
1275if(opt.pinSize && opt.confirmText){
1276const digits = (''+(opt.timeout/1000 || opt.ticks)).length;
1277const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits+1));
1278const w1 = parseInt(target.getBoundingClientRect().width);
1279updateText(lblLong);
1280const w2 = parseInt(target.getBoundingClientRect().width);
1281if(w1 || w2){
1282target.style.minWidth = target.style.maxWidth = (w1>w2 ? w1 : w2)+"px";
1283}
1284}
1285updateText(this.opt.initialText);
1286if(this.opt.ticks && !this.opt.ontick){
1287this.opt.ontick = function(tick){
1288updateText(formatCountdown(self.opt.confirmText,tick));
1289};
1290}
1291this.setClasses(false);
1292this.doTimeout = function() {
1293if(this.timerID){
1294clearTimeout( this.timerID );
1295delete this.timerID;
1296}
1297if( this.state != this.states.waiting ) {
1298return;
1299}
1300this.setClasses( false );
1301this.state = this.states.initial;
1302dbg("Timeout triggered.");
1303if( this.opt.ontick ){
1304try{this.opt.ontick.call(this.target, 0)}
1305catch(e){dbg("ontick EXCEPTION:",e)}
1306}
1307if( this.opt.ontimeout ) {
1308try{this.opt.ontimeout.call(this.target)}
1309catch(e){dbg("ontimeout EXCEPTION:",e)}
1310}
1311updateText(this.opt.initialText);
1312};
1313target.addEventListener(
1314'click', function(){
1315switch( self.state ) {
1316case( self.states.waiting ):
1317if( undefined !== self.timerID ){
1318clearTimeout( self.timerID );
1319delete self.timerID;
1320}
1321self.state = self.states.initial;
1322self.setClasses( false );
1323dbg("Confirmed");
1324if( self.opt.ontick ){
1325try{self.opt.ontick.call(self.target,0)}
1326catch(e){dbg("ontick EXCEPTION:",e)}
1327}
1328if( self.opt.onconfirm ){
1329try{self.opt.onconfirm.call(self.target)}
1330catch(e){dbg("onconfirm EXCEPTION:",e)}
1331}
1332updateText(self.opt.initialText);
1333break;
1334case( self.states.initial ):
1335if(self.opt.ticks) self.opt.currentTick = self.opt.ticks;
1336self.setClasses( true );
1337self.state = self.states.waiting;
1338updateText( self.opt.confirmText );
1339if( self.opt.onactivate ) self.opt.onactivate.call( self.target );
1340if( self.opt.ontick ) self.opt.ontick.call(self.target, self.opt.currentTick);
1341if(self.opt.timeout){
1342dbg("Waiting "+self.opt.timeout+"ms on confirmation...");
1343self.timerID =
1344setTimeout(()=>self.doTimeout(),self.opt.timeout );
1345}else if(self.opt.ticks){
1346dbg("Waiting on confirmation for "+self.opt.ticks
1347+" ticks of "+self.opt.ticktime+"ms each...");
1348self.timerID =
1349setInterval(function(){
1350if(0===--self.opt.currentTick) self.doTimeout();
1351else{
1352try{self.opt.ontick.call(self.target,
1353self.opt.currentTick)}
1354catch(e){dbg("ontick EXCEPTION:",e)}
1355}
1356},self.opt.ticktime);
1357}
1358break;
1359default:
1360break;
1361}
1362}, false
1363);
1364};
1365f.Holder.prototype = {
1366states:{initial: 0, waiting: 1},
1367setClasses: function(activated) {
1368if(activated) {
1369if( this.opt.classWaiting ) {
1370this.target.classList.add( this.opt.classWaiting );
1371}
1372if( this.opt.classInitial ) {
1373this.target.classList.remove( this.opt.classInitial );
1374}
1375}else{
1376if( this.opt.classInitial ) {
1377this.target.classList.add( this.opt.classInitial );
1378}
1379if( this.opt.classWaiting ) {
1380this.target.classList.remove( this.opt.classWaiting );
1381}
1382}
1383}
1384};
1385}
1386opt = F.mergeLastWins(f.defaultOpts,{
1387initialText: (
1388f.isInput(elem) ? elem.value : elem.innerHTML
1389) || "PLEASE SET .initialText"
1390},opt);
1391if(!opt.confirmText){
1392opt.confirmText = "Confirm: "+opt.initialText;
1393}
1394if(opt.ticks){
1395delete opt.timeout;
1396opt.ticks = 0 | opt.ticks;
1397if(opt.ticks<=0){
1398throw new Error("ticks must be >0");
1399}
1400if(opt.ticktime <= 0) opt.ticktime = 1000;
1401}else{
1402delete opt.ontick;
1403delete opt.ticks;
1404}
1405new f.Holder(elem,opt);
1406return this;
1407};
1408F.confirmer.defaultOpts = {
1409timeout:undefined,
1410ticks: 3,
1411ticktime: 998,
1412onconfirm: undefined,
1413ontimeout: undefined,
1414onactivate: undefined,
1415classInitial: '',
1416classWaiting: '',
1417debug: false
1418};
1419})(window.fossil);
1420/* fossil.copybutton.js */
1421(function(F){
1422const D = F.dom;
1423F.copyButton = function f(e, opt){
1424if('string'===typeof e){
1425e = document.querySelector(e);
1426}
1427opt = F.mergeLastWins(f.defaultOptions, opt);
1428if(opt.cssClass){
1429D.addClass(e, opt.cssClass);
1430}
1431var srcId, srcElem;
1432if(opt.copyFromElement){
1433srcElem = opt.copyFromElement;
1434}else if((srcId = opt.copyFromId || e.dataset.copyFromId)){
1435srcElem = document.querySelector('#'+srcId);
1436}
1437const extract = opt.extractText || (
1438undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value
1439);
1440D.copyStyle(e, opt.style);
1441e.addEventListener(
1442'click',
1443function(ev){
1444ev.preventDefault();
1445ev.stopPropagation();
1446if(e.disabled) return;
1447const txt = extract.call(opt);
1448if(txt && D.copyTextToClipboard(txt)){
1449e.dispatchEvent(new CustomEvent('text-copied',{
1450detail: {text: txt}
1451}));
1452}
1453},
1454false
1455);
1456if('function' === typeof opt.oncopy){
1457e.addEventListener('text-copied', opt.oncopy, false);
1458}
1459if(e.childElementCount!=1 || e.firstChild.tagName!='SPAN'){
1460D.append(D.clearElement(e), D.span());
1461}
1462return e;
1463};
1464F.copyButton.defaultOptions = {
1465cssClass: 'copy-button',
1466oncopy: undefined,
1467style: {}
1468};
1469})(window.fossil);
1470/* fossil.fetch.js */
1471"use strict";
1472(function(namespace){
1473const fossil = namespace;
1474fossil.fetch = function f(uri,opt){
1475const F = fossil;
1476if(!f.onload){
1477f.onload = (r)=>console.debug('fossil.fetch() XHR response:',r);
1478}
1479if(!f.onerror){
1480f.onerror = function(e){
1481console.error("fossil.fetch() XHR error:",e);
1482if(e instanceof Error) F.error('Exception:',e);
1483else F.error("Unknown error in handling of XHR request.");
1484};
1485}
1486if(!f.parseResponseHeaders){
1487f.parseResponseHeaders = function(h){
1488const rc = {};
1489if(!h) return rc;
1490const ar = h.trim().split(/[\r\n]+/);
1491ar.forEach(function(line) {
1492const parts = line.split(': ');
1493const header = parts.shift();
1494const value = parts.join(': ');
1495rc[header.toLowerCase()] = value;
1496});
1497return rc;
1498};
1499}
1500if('/'===uri[0]) uri = uri.substr(1);
1501if(!opt) opt = {};
1502else if('function'===typeof opt) opt={onload:opt};
1503if(!opt.onload) opt.onload = f.onload;
1504if(!opt.onerror) opt.onerror = f.onerror;
1505if(!opt.beforesend) opt.beforesend = f.beforesend;
1506if(!opt.aftersend) opt.aftersend = f.aftersend;
1507let payload = opt.payload, jsonResponse = false;
1508if(undefined!==payload){
1509opt.method = 'POST';
1510if(!(payload instanceof FormData)
1511&& !(payload instanceof Document)
1512&& !(payload instanceof Blob)
1513&& !(payload instanceof File)
1514&& !(payload instanceof ArrayBuffer)
1515&& ('object'===typeof payload
1516|| payload instanceof Array)){
1517payload = JSON.stringify(payload);
1518opt.contentType = 'application/json';
1519}
1520}
1521const url=[f.urlTransform(uri,opt.urlParams)],
1522x=new XMLHttpRequest();
1523if('json'===opt.responseType){
1524jsonResponse = true;
1525x.responseType = 'text';
1526}else{
1527x.responseType = opt.responseType||'text';
1528}
1529x.ontimeout = function(ev){
1530try{opt.aftersend()}catch(e){}
1531const err = new Error("XHR timeout of "+x.timeout+"ms expired.");
1532err.status = x.status;
1533err.name = 'timeout';
1534try{
1535(opt.ontimeout || opt.onerror)(err);
1536}catch(e){
1537console.error("fossil.fetch()'s ontimeout() handler threw",e);
1538}
1539};
1540const origOnError = opt.onerror;
1541opt.onerror = (arg)=>{
1542try{ origOnError.call(this, arg) }
1543catch(e){
1544console.error("fossil.fetch()'s onerror() threw",e);
1545}
1546};
1547x.onreadystatechange = function(ev){
1548if(XMLHttpRequest.DONE !== x.readyState) return;
1549try{opt.aftersend()}catch(e){}
1550if(false && 0===x.status){
1551return;
1552}
1553if(200!==x.status){
1554let err;
1555try{
1556const j = JSON.parse(x.response);
1557if(j.error){
1558err = new Error(j.error);
1559err.name = 'json.error';
1560}
1561}catch(ex){}
1562if( !err ){
1563err = new Error("HTTP response status "+x.status+".")
1564err.name = 'http';
1565}
1566err.status = x.status;
1567opt.onerror(err);
1568return;
1569}
1570const orh = opt.responseHeaders;
1571let head;
1572if(true===orh){
1573head = f.parseResponseHeaders(x.getAllResponseHeaders());
1574}else if('string'===typeof orh){
1575head = x.getResponseHeader(orh);
1576}else if(orh instanceof Array){
1577head = {};
1578orh.forEach((s)=>{
1579if('string' === typeof s) head[s.toLowerCase()] = x.getResponseHeader(s);
1580});
1581}
1582try{
1583const args = [(jsonResponse && x.response)
1584? JSON.parse(x.response) : x.response];
1585if(head) args.push(head);
1586opt.onload.apply(opt, args);
1587}catch(err){
1588opt.onerror(err);
1589}
1590};
1591try{opt.beforesend()}
1592catch(err){
1593opt.onerror(err);
1594return;
1595}
1596x.open(opt.method||'GET', url.join(''), true);
1597if('POST'===opt.method && 'string'===typeof opt.contentType){
1598x.setRequestHeader('Content-Type',opt.contentType);
1599}
1600x.timeout = +opt.timeout || f.timeout;
1601if(undefined!==payload) x.send(payload);
1602else x.send();
1603return this;
1604};
1605fossil.fetch.urlTransform = (u,p)=>fossil.repoUrl(u,p);
1606fossil.fetch.beforesend = function(){};
1607fossil.fetch.aftersend = function(){};
1608fossil.fetch.timeout = 15000;
1609})(window.fossil);
1610/* fossil.storage.js */
1611(function(F){
1612const tryStorage = function f(obj){
1613if(!f.key) f.key = 'fossil.access.check';
1614try{
1615obj.setItem(f.key, 'f');
1616const x = obj.getItem(f.key);
1617obj.removeItem(f.key);
1618if(x!=='f') throw new Error(f.key+" failed")
1619return obj;
1620}catch(e){
1621return undefined;
1622}
1623};
1624const $storage =
1625tryStorage(window.localStorage)
1626|| tryStorage(window.sessionStorage)
1627|| tryStorage({
1628$$$:{},
1629setItem: function(k,v){this.$$$[k]=v},
1630getItem: function(k){
1631return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined;
1632},
1633removeItem: function(k){delete this.$$$[k]},
1634clear: function(){this.$$$={}}
1635});
1636const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage;
1637const storageKeyPrefix = (
1638$storageHolder===$storage
1639? (
1640F.config.projectCode || F.config.projectName
1641|| F.config.shortProjectName || window.location.pathname
1642)+'::' : (
1643''
1644)
1645);
1646let events;
1647F.storage = {
1648storageKeyPrefix: storageKeyPrefix,
1649addEventListener(...args){
1650events ??= new EventTarget()
1651return events.addEventListener(...args);
1652},
1653removeEventListener(...args){
1654events ??= new EventTarget()
1655return events.removeEventListener(...args);
1656},
1657set: (k,v)=>{
1658$storage.setItem(storageKeyPrefix+k,v);
1659if( events ){
1660events.dispatchEvent(
1661new CustomEvent('set',{
1662detail: F.nu({
1663key: k, value: v
1664})
1665})
1666);
1667}
1668},
1669setJSON: function(k,v){
1670return this.set(k,JSON.stringify(v));
1671},
1672get: (k,dflt)=>$storageHolder.hasOwnProperty(
1673storageKeyPrefix+k
1674) ? $storage.getItem(storageKeyPrefix+k) : dflt,
1675getBool: function(k,dflt){
1676return 'true'===this.get(k,''+(!!dflt));
1677},
1678getJSON: function f(k,dflt){
1679try {
1680const x = this.get(k,f);
1681return x===f ? dflt : JSON.parse(x);
1682}
1683catch(e){return dflt}
1684},
1685contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k),
1686remove: function(k){
1687const kk = storageKeyPrefix+k;
1688if( events ){
1689const had = $storageHolder.hasOwnProperty(kk)
1690$storage.removeItem(kk);
1691if( had ){
1692events.dispatchEvent(
1693new CustomEvent('remove',{
1694detail: F.nu({key: k})
1695})
1696);
1697}
1698}else{
1699$storage.removeItem(kk);
1700}
1701return this;
1702},
1703clear: function(){
1704this.keys().forEach((k)=>$storage.removeItem(k));
1705return this;
1706},
1707keys: function(full=true){
1708const li = Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix));
1709if( full ) return li;
1710const n = this.storageKeyPrefix.length;
1711return li.map(v=>v.substring(n));
1712},
1713isTransient: ()=>$storageHolder!==$storage,
1714storageImplName: function(){
1715if($storage===window.localStorage) return 'localStorage';
1716else if($storage===window.sessionStorage) return 'sessionStorage';
1717else return 'transient';
1718},
1719storageHelpDescription: function(){
1720return {
1721localStorage: "Browser-local persistent storage with an "+
1722"unspecified long-term lifetime (survives closing the browser, "+
1723"but maybe not a browser upgrade).",
1724sessionStorage: "Storage local to this browser tab, "+
1725"lost if this tab is closed.",
1726"transient": "Transient storage local to this invocation of this page."
1727}[this.storageImplName()];
1728}
1729};
1730})(window.fossil);
1731/* fossil.diff.js */
1732"use strict";
1733window.fossil.onPageLoad(function(){
1734const potentialParents = window.fossil.page.diffControlContainers = [
1735 'body.cpage-fileedit #fileedit-tab-diff-buttons',
1736 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
1737 'body.fdiff form div.submenu',
1738 'body.vdiff form div.submenu',
1739 'body.vinfo div.sectionmenu.info-changes-menu'
1740];
1741window.fossil.page.diffControlContainer = undefined;
1742while( potentialParents.length ){
1743if( (window.fossil.page.diffControlContainer
1744= document.querySelector(potentialParents.pop())) ){
1745break;
1746}
1747}
1748});
1749window.fossil.onPageLoad(function(){
1750if( !window.fossil.page.diffControlContainer ){
1751return;
1752}
1753const D = window.fossil.dom;
1754const allToggles = [];
1755let checkedCount =
17560;
1757let btnAll;
1758const addToggle = function(diffElem){
1759const sib = diffElem.previousElementSibling,
1760ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
1761if(!sib) return;
1762const lblToggle = D.label();
1763D.append(lblToggle, ckbox, D.text(" show/hide "));
1764allToggles.push(ckbox);
1765++checkedCount;
1766lblToggle.style.flexGrow = 1;
1767lblToggle.style.textAlign = 'right';
1768D.append(sib, lblToggle);
1769ckbox.addEventListener('change', function(){
1770diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
1771if(btnAll){
1772checkedCount += (this.checked ? 1 : -1);
1773btnAll.innerText = (checkedCount < allToggles.length)
1774? "Show diffs" : "Hide diffs";
1775}
1776}, false);
1777sib.firstElementChild.addEventListener('click', (event)=>{
1778if( event.target===sib.firstElementChild ){
1779ckbox.click();;
1780}
1781}, false);
1782};
1783if( !document.querySelector('body.fdiff') ){
1784document.querySelectorAll('table.diff').forEach(addToggle);
1785}
1786const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
1787if(icm) {
1788btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
1789D.append( icm, btnAll );
1790btnAll.addEventListener('click', function(ev){
1791ev.preventDefault();
1792ev.stopPropagation();
1793const show = checkedCount < allToggles.length;
1794for( const ckbox of allToggles ){
1795if(ckbox.checked!==show) ckbox.click();
1796}
1797}, false);
1798}
1799});
1800window.fossil.onPageLoad(function(){
1801const F = window.fossil, D = F.dom;
1802const Diff = F.diff = {
1803e:{},
1804config: {
1805chunkLoadLines: (
1806F.config.diffContextLines * 3
1807) || 20,
1808chunkFetch: {
1809beforesend: function(){},
1810aftersend: function(){},
1811onerror: function(e){
1812console.error("XHR error: ",e);
1813}
1814}
1815}
1816};
1817Diff.fetchArtifactChunk = function(fetchOpt){
1818if(!fetchOpt.beforesend) fetchOpt.beforesend = Diff.config.chunkFetch.beforesend;
1819if(!fetchOpt.aftersend) fetchOpt.aftersend = Diff.config.chunkFetch.aftersend;
1820if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror;
1821fetchOpt.responseType = 'json';
1822return F.fetch('jchunk', fetchOpt);
1823};
1824const extractLineNo = function f(getLHS, getStart, tr, isSplit){
1825if(!f.rx){
1826f.rx = {
1827start: /^\s*(\d+)/,
1828end: /(\d+)\n?$/
1829}
1830}
1831const td = tr.querySelector('td:nth-child('+(
1832getLHS ? 1 : (isSplit ? 4 : 2)
1833)+')');
1834const m = f.rx[getStart ? 'start' : 'end'].exec(td.innerText);
1835return m ? +m[1] : undefined;
1836};
1837const ChunkLoadControls = function(tr){
1838this.$fetchQueue = [];
1839this.e = {
1840tr: tr,
1841table: tr.parentElement.parentElement
1842};
1843this.isSplit = this.e.table.classList.contains('splitdiff');
1844this.fileHash = this.e.table.dataset.lefthash;
1845tr.$chunker = this;
1846this.pos = {
1847startLhs: +tr.dataset.startln,
1848endLhs: +tr.dataset.endln
1849};
1850D.clearElement(tr);
1851this.e.td = D.addClass(
1852D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4),
1853'chunkctrl'
1854);
1855this.e.msgWidget = D.addClass(D.span(), 'hidden');
1856this.e.btnWrapper = D.div();
1857D.append(this.e.td, this.e.btnWrapper);
1858if(tr.nextElementSibling){
1859this.pos.next = {
1860startLhs: extractLineNo(true, true, tr.nextElementSibling, this.isSplit),
1861startRhs: extractLineNo(false, true, tr.nextElementSibling, this.isSplit)
1862};
1863}
1864if(tr.previousElementSibling){
1865this.pos.prev = {
1866endLhs: extractLineNo(true, false, tr.previousElementSibling, this.isSplit),
1867endRhs: extractLineNo(false, false, tr.previousElementSibling, this.isSplit)
1868};
1869}
1870let btnUp = false, btnDown = false;
1871if(this.pos.prev && this.pos.next
1872&& ((this.pos.endLhs - this.pos.startLhs)
1873<= Diff.config.chunkLoadLines)){
1874btnDown = false;
1875btnUp = this.createButton(this.FetchType.FillGap);
1876}else{
1877if(this.pos.prev){
1878btnDown = this.createButton(this.FetchType.PrevDown);
1879}
1880if(this.pos.next){
1881btnUp = this.createButton(this.FetchType.NextUp);
1882}
1883}
1884if(btnUp) D.append(this.e.btnWrapper, btnUp);
1885if(btnDown) D.append(this.e.btnWrapper, btnDown);
1886D.append(this.e.btnWrapper, this.e.msgWidget);
1887this.e.posState = D.span();
1888D.append(this.e.btnWrapper, this.e.posState);
1889this.updatePosDebug();
1890};
1891ChunkLoadControls.prototype = {
1892FetchType:{
1893PrevDown: 1,
1894FillGap: 0,
1895NextUp: -1,
1896ProcessQueue: 0x7fffffff
1897},
1898createButton: function(fetchType){
1899let b;
1900switch(fetchType){
1901case this.FetchType.PrevDown:
1902b = D.append(
1903D.addClass(D.span(), 'down'),
1904D.span()
1905);
1906break;
1907case this.FetchType.FillGap:
1908b = D.append(
1909D.addClass(D.span(), 'up', 'down'),
1910D.span()
1911);
1912break;
1913case this.FetchType.NextUp:
1914b = D.append(
1915D.addClass(D.span(), 'up'),
1916D.span()
1917);
1918break;
1919default:
1920throw new Error("Internal API misuse: unexpected fetchType value "+fetchType);
1921}
1922D.addClass(b, 'jcbutton');
1923b.addEventListener('click', ()=>this.fetchChunk(fetchType),false);
1924return b;
1925},
1926updatePosDebug: function(){
1927if(this.e.posState){
1928D.clearElement(this.e.posState);
1929}
1930return this;
1931},
1932destroy: function(){
1933delete this.$fetchQueue;
1934D.remove(this.e.tr);
1935delete this.e.tr.$chunker;
1936delete this.e.tr;
1937delete this.e;
1938delete this.pos;
1939},
1940maybeReplaceButtons: function(){
1941if(this.pos.next && this.pos.prev
1942&& (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){
1943D.clearElement(this.e.btnWrapper);
1944D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap));
1945if( this.$fetchQueue && this.$fetchQueue.length>1 ){
1946this.$fetchQueue[1] = this.FetchType.FillGap;
1947this.$fetchQueue.length = 2;
1948}
1949}
1950return this;
1951},
1952injectResponse: function f(fetchType,
1953urlParam,
1954lines){
1955if(!lines.length){
1956this.destroy();
1957return this;
1958}
1959this.msg(false);
1960const lineno = [],
1961trPrev = this.e.tr.previousElementSibling,
1962trNext = this.e.tr.nextElementSibling,
1963doAppend = (
1964!!trPrev && fetchType>=this.FetchType.FillGap
1965);
1966const tr = doAppend ? trPrev : trNext;
1967const joinTr = (
1968this.FetchType.FillGap===fetchType && trPrev && trNext
1969) ? trNext : false
1970;
1971let i, td;
1972if(!f.convertLines){
1973f.rx = [[/&/g, '&amp;'], [/</g, '&lt;']];
1974f.convertLines = function(li){
1975var s = li.join('\n');
1976f.rx.forEach((a)=>s=s.replace(a[0],a[1]));
1977return s + '\n';
1978};
1979}
1980if(1){
1981const selector = '.difflnl > pre';
1982td = tr.querySelector(selector);
1983const lnTo = Math.min(urlParam.to,
1984urlParam.from +
1985lines.length - 1);
1986for( i = urlParam.from; i <= lnTo; ++i ){
1987lineno.push(i);
1988}
1989const lineNoTxt = lineno.join('\n')+'\n';
1990const content = [td.innerHTML];
1991if(doAppend) content.push(lineNoTxt);
1992else content.unshift(lineNoTxt);
1993if(joinTr){
1994content.push(trNext.querySelector(selector).innerHTML);
1995}
1996td.innerHTML = content.join('');
1997}
1998if(1){
1999const selector = '.difftxt > pre';
2000td = tr.querySelectorAll(selector);
2001const code = f.convertLines(lines);
2002let joinNdx = 0;
2003td.forEach(function(e){
2004const content = [e.innerHTML];
2005if(doAppend) content.push(code);
2006else content.unshift(code);
2007if(joinTr){
2008content.push(trNext.querySelectorAll(selector)[joinNdx++].innerHTML)
2009}
2010e.innerHTML = content.join('');
2011});
2012}
2013if(1){
2014const selector = '.diffsep > pre';
2015td = tr.querySelector(selector);
2016for(i = 0; i < lineno.length; ++i) lineno[i] = '';
2017const blanks = lineno.join('\n')+'\n';
2018const content = [td.innerHTML];
2019if(doAppend) content.push(blanks);
2020else content.unshift(blanks);
2021if(joinTr){
2022content.push(trNext.querySelector(selector).innerHTML);
2023}
2024td.innerHTML = content.join('');
2025}
2026if(this.FetchType.FillGap===fetchType){
2027let startLnR = this.pos.prev
2028? this.pos.prev.endRhs+1
2029: this.pos.next.startRhs - lines.length;
2030lineno.length = lines.length;
2031for( i = startLnR; i < startLnR + lines.length; ++i ){
2032lineno[i-startLnR] = i;
2033}
2034const selector = '.difflnr > pre';
2035td = tr.querySelector(selector);
2036const lineNoTxt = lineno.join('\n')+'\n';
2037lineno.length = 0;
2038const content = [td.innerHTML];
2039if(doAppend) content.push(lineNoTxt);
2040else content.unshift(lineNoTxt);
2041if(joinTr){
2042content.push(trNext.querySelector(selector).innerHTML);
2043}
2044td.innerHTML = content.join('');
2045if(joinTr) D.remove(joinTr);
2046this.destroy();
2047return this;
2048}else if(this.FetchType.PrevDown===fetchType){
2049let startLnR = this.pos.prev.endRhs+1;
2050lineno.length = lines.length;
2051for( i = startLnR; i < startLnR + lines.length; ++i ){
2052lineno[i-startLnR] = i;
2053}
2054this.pos.startLhs += lines.length;
2055this.pos.prev.endRhs += lines.length;
2056this.pos.prev.endLhs += lines.length;
2057const selector = '.difflnr > pre';
2058td = tr.querySelector(selector);
2059const lineNoTxt = lineno.join('\n')+'\n';
2060lineno.length = 0;
2061const content = [td.innerHTML];
2062if(doAppend) content.push(lineNoTxt);
2063else content.unshift(lineNoTxt);
2064td.innerHTML = content.join('');
2065if(lines.length < (urlParam.to - urlParam.from)){
2066this.destroy();
2067}else{
2068this.maybeReplaceButtons();
2069this.updatePosDebug();
2070}
2071return this;
2072}else if(this.FetchType.NextUp===fetchType){
2073if(doAppend){
2074throw new Error("Internal precondition violation: doAppend is true.");
2075}
2076let startLnR = this.pos.next.startRhs - lines.length;
2077lineno.length = lines.length;
2078for( i = startLnR; i < startLnR + lines.length; ++i ){
2079lineno[i-startLnR] = i;
2080}
2081this.pos.endLhs -= lines.length;
2082this.pos.next.startRhs -= lines.length;
2083this.pos.next.startLhs -= lines.length;
2084const selector = '.difflnr > pre';
2085td = tr.querySelector(selector);
2086const lineNoTxt = lineno.join('\n')+'\n';
2087lineno.length = 0;
2088td.innerHTML = lineNoTxt + td.innerHTML;
2089if(this.pos.endLhs<1
2090|| lines.length < (urlParam.to - urlParam.from)){
2091this.destroy();
2092}else{
2093this.maybeReplaceButtons();
2094this.updatePosDebug();
2095}
2096return this;
2097}else{
2098throw new Error("Unexpected 'fetchType' value.");
2099}
2100},
2101msg: function(isError,txt){
2102if(txt){
2103if(isError) D.addClass(this.e.msgWidget, 'error');
2104else D.removeClass(this.e.msgWidget, 'error');
2105D.append(
2106D.removeClass(D.clearElement(this.e.msgWidget), 'hidden'),
2107txt);
2108}else{
2109D.addClass(D.clearElement(this.e.msgWidget), 'hidden');
2110}
2111return this;
2112},
2113fetchChunk: function(fetchType){
2114if( !this.$fetchQueue ) return this;
2115if( fetchType==this.FetchType.ProcessQueue ){
2116this.$fetchQueue.shift();
2117if( this.$fetchQueue.length==0 ) return this;
2118}
2119else{
2120this.$fetchQueue.push(fetchType);
2121if( this.$fetchQueue.length!=1 ) return this;
2122}
2123fetchType = this.$fetchQueue[0];
2124if( fetchType==this.FetchType.ProcessQueue ){
2125this.$fetchQueue.length = 0;
2126return this;
2127}
2128if(fetchType===this.FetchType.NextUp && !this.pos.next
2129|| fetchType===this.FetchType.PrevDown && !this.pos.prev){
2130console.error("Attempt to fetch diff lines but don't have any.");
2131return this;
2132}
2133this.msg(false,"Fetching diff chunk...");
2134const self = this;
2135const fOpt = {
2136urlParams:{
2137name: this.fileHash, from: 0, to: 0
2138},
2139aftersend: ()=>this.msg(false),
2140onload: function(list){
2141self.injectResponse(fetchType,up,list);
2142if( !self.$fetchQueue || self.$fetchQueue.length==0 ) return;
2143self.$fetchQueue[0] = self.FetchType.ProcessQueue;
2144setTimeout(self.fetchChunk.bind(self,self.FetchType.ProcessQueue));
2145}
2146};
2147const up = fOpt.urlParams;
2148if(fetchType===this.FetchType.FillGap){
2149up.from = this.pos.startLhs;
2150up.to = this.pos.endLhs;
2151}else if(this.FetchType.PrevDown===fetchType){
2152if(!this.pos.prev){
2153console.error("Attempt to fetch next diff lines but don't have any.");
2154return this;
2155}
2156up.from = this.pos.prev.endLhs + 1;
2157up.to = up.from +
2158Diff.config.chunkLoadLines - 1;
2159if( this.pos.next && this.pos.next.startLhs <= up.to ){
2160up.to = this.pos.next.startLhs - 1;
2161fetchType = this.FetchType.FillGap;
2162}
2163}else{
2164if(!this.pos.next){
2165console.error("Attempt to fetch previous diff lines but don't have any.");
2166return this;
2167}
2168up.to = this.pos.next.startLhs - 1;
2169up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1);
2170if( this.pos.prev && this.pos.prev.endLhs >= up.from ){
2171up.from = this.pos.prev.endLhs + 1;
2172fetchType = this.FetchType.FillGap;
2173}
2174}
2175fOpt.onerror = function(err){
2176if(self.e){
2177self.msg(true,err.message);
2178self.$fetchQueue.length = 0;
2179}else{
2180Diff.config.chunkFetch.onerror.call(this,err);
2181}
2182};
2183Diff.fetchArtifactChunk(fOpt);
2184return this;
2185}
2186};
2187Diff.setupDiffContextLoad = function(tables){
2188if('string'===typeof tables){
2189tables = document.querySelectorAll(tables);
2190}else if(!tables){
2191tables = document.querySelectorAll('table.diff[data-lefthash]:not(.diffskipped)');
2192}
2193tables.forEach(function(table){
2194if(table.classList.contains('diffskipped') || !table.dataset.lefthash) return;
2195D.addClass(table, 'diffskipped');
2196table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){
2197new ChunkLoadControls(D.addClass(tr, 'jchunk'));
2198});
2199});
2200return F;
2201};
2202Diff.setupDiffContextLoad();
2203});
2204window.fossil.onPageLoad(function(){
2205const F = window.fossil, D = F.dom, Diff = F.diff;
2206let cbSync;
2207let eToggleParent =
2208document.querySelector('table.diff.splitdiff')
2209? window.fossil.page.diffControlContainer
2210: undefined;
2211const keySbsScroll = 'sync-diff-scroll';
2212if( eToggleParent ){
2213cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
2214D.append(eToggleParent, D.append(
2215D.addClass(D.create('span'), 'input-with-label'),
2216D.append(D.create('label'),
2217cbSync, "Scroll Sync")
2218));
2219cbSync.addEventListener('change', function(e){
2220F.storage.set(keySbsScroll, e.target.checked);
2221});
2222}
2223const useSync = cbSync ? ()=>cbSync.checked : ()=>F.storage.getBool(keySbsScroll,true);
2224const scrollLeft = function(event){
2225const table = this.parentElement.parentElement.
2226parentElement.parentElement;
2227if( useSync() ){
2228table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
2229}
2230return false;
2231};
2232const SCROLL_LEN = 64;
2233const keycodes = Object.assign(Object.create(null),{
223437: -SCROLL_LEN, 39: SCROLL_LEN
2235});
2236const handleDiffKeydown = function(e){
2237const len = keycodes[e.keyCode];
2238if( !len ) return false;
2239if( useSync() ){
2240this.$txtPres[0].scrollLeft += len;
2241}else if( this.$preCurrent ){
2242this.$preCurrent.scrollLeft += len;
2243}
2244return false;
2245};
2246const initTableDiff = function f(diff, unifiedDiffs){
2247if(!diff){
2248let i, diffs;
2249diffs = document.querySelectorAll('table.splitdiff');
2250for(i=0; i<diffs.length; ++i){
2251f.call(this, diffs[i], false);
2252}
2253diffs = document.querySelectorAll('table.udiff');
2254for(i=0; i<diffs.length; ++i){
2255f.call(this, diffs[i], true);
2256}
2257return this;
2258}
2259diff.$txtCols = diff.querySelectorAll('td.difftxt');
2260diff.$txtPres = diff.querySelectorAll('td.difftxt pre');
2261if(!unifiedDiffs){
2262diff.$txtPres.forEach(function(e){
2263if(!e.classList.contains('scroller')){
2264D.addClass(e, 'scroller');
2265e.addEventListener('scroll', scrollLeft, false);
2266}
2267});
2268diff.tabIndex = 0;
2269if(!diff.classList.contains('scroller')){
2270D.addClass(diff, 'scroller');
2271diff.addEventListener('keydown', handleDiffKeydown, false);
2272const handleLRClick = function(ev){
2273diff.$preCurrent = this;
2274};
2275diff.$txtPres.forEach((e)=>e.addEventListener('click', handleLRClick, false));
2276}
2277}
2278return this;
2279}
2280window.fossil.page.tweakSbsDiffs = function(){
2281document.querySelectorAll('table.splitdiff').forEach((e)=>initTableDiff(e));
2282};
2283initTableDiff();
2284}, false);
2285/* fossil.popupwidget.js */
2286(function(F){
2287const D = F.dom;
2288F.PopupWidget = function f(opt){
2289opt = F.mergeLastWins(f.defaultOptions,opt);
2290this.options = opt;
2291const e = this.e = D.addClass(D.div(), opt.cssClass,
2292"fossil-PopupWidget");
2293this.show(false);
2294if(opt.style){
2295let k;
2296for(k in opt.style){
2297if(opt.style.hasOwnProperty(k)) e.style[k] = opt.style[k];
2298}
2299}
2300D.append(document.body, e);
2301D.copyStyle(e, opt.style);
2302if(opt.init){
2303opt.init.call(this);
2304delete opt.init;
2305}
2306};
2307F.PopupWidget.defaultOptions = {
2308cssClass: 'fossil-tooltip',
2309style: undefined,
2310adjustX: (x)=>x,
2311adjustY: (y)=>y,
2312refresh: function(){},
2313init: undefined
2314};
2315F.PopupWidget.prototype = {
2316isShown: function(){return !this.e.classList.contains('hidden')},
2317refresh: function(){
2318if(this.options.refresh){
2319this.options.refresh.call(this);
2320}
2321return this;
2322},
2323show: function(){
2324let x = undefined, y = undefined, showIt,
2325wasShown = !this.e.classList.contains('hidden');
2326if(2===arguments.length){
2327x = arguments[0];
2328y = arguments[1];
2329showIt = true;
2330}else if(1===arguments.length){
2331if(arguments[0] instanceof HTMLElement){
2332const p = arguments[0];
2333const r = p.getBoundingClientRect();
2334x = r.x + r.x/5;
2335y = r.y - r.height/2;
2336showIt = true;
2337}else{
2338showIt = !!arguments[0];
2339}
2340}
2341if(showIt){
2342if(!wasShown) this.refresh();
2343x = this.options.adjustX.call(this,x);
2344y = this.options.adjustY.call(this,y);
2345x += window.pageXOffset;
2346y += window.pageYOffset;
2347}
2348if(showIt){
2349if('number'===typeof x && 'number'===typeof y){
2350this.e.style.left = x+"px";
2351this.e.style.top = y+"px";
2352}
2353D.removeClass(this.e, 'hidden');
2354}else{
2355D.addClass(this.e, 'hidden');
2356this.e.style.removeProperty('left');
2357this.e.style.removeProperty('top');
2358}
2359return this;
2360},
2361hide: function(){return this.show(false)},
2362installHideHandlers: function f(onClickSelf, onClickOther, onEsc){
2363if(!arguments.length) onClickSelf = onClickOther = onEsc = true;
2364else if(1===arguments.length) onClickOther = onEsc = true;
2365else if(2===arguments.length) onEsc = true;
2366if(onClickSelf) this.e.addEventListener('click', ()=>this.hide(), false);
2367if(onClickOther) document.body.addEventListener('click', ()=>this.hide(), true);
2368if(onEsc){
2369const self = this;
2370document.body.addEventListener('keydown', function(ev){
2371if(self.isShown() && 27===ev.which) self.hide();
2372}, true);
2373}
2374return this;
2375}
2376};
2377const toastImpl = function f(cssClass, durationMult, argsObject){
2378if(!f.toaster){
2379f.toaster = new F.PopupWidget({
2380cssClass: 'fossil-toast-message'
2381});
2382D.attr(f.toaster.e, 'role', 'alert');
2383}
2384const T = f.toaster;
2385if(f._timer) clearTimeout(f._timer);
2386D.clearElement(T.e);
2387if(f._prevCssClass) T.e.classList.remove(f._prevCssClass);
2388if(cssClass) T.e.classList.add(cssClass);
2389f._prevCssClass = cssClass;
2390D.append(T.e, Array.prototype.slice.call(argsObject,0));
2391T.show(F.toast.config.position.x, F.toast.config.position.y);
2392f._timer = setTimeout(
2393()=>T.hide(),
2394F.toast.config.displayTimeMs * durationMult
2395);
2396return F.toast;
2397};
2398F.toast = {
2399config: {
2400position: { x: 5, y: 5 },
2401displayTimeMs: 5000
2402},
2403message: function(){
2404return toastImpl(false,1, arguments);
2405},
2406warning: function(){
2407return toastImpl('warning',1.5,arguments);
2408},
2409error: function(){
2410return toastImpl('error',2,arguments);
2411}
2412};
2413F.helpButtonlets = {
2414setup: function f(){
2415if(!f.hasOwnProperty('clickHandler')){
2416f.clickHandler = function fch(ev){
2417ev.preventDefault();
2418ev.stopPropagation();
2419if(!fch.popup){
2420fch.popup = new F.PopupWidget({
2421cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
2422refresh: function(){
2423}
2424});
2425fch.popup.e.style.maxWidth = '80%';
2426fch.popup.installHideHandlers();
2427}
2428D.append(D.clearElement(fch.popup.e), ev.target.$helpContent);
2429let popupRect, rectElem = ev.target;
2430while(rectElem){
2431popupRect = rectElem.getClientRects()[0];
2432if(popupRect) break;
2433rectElem = rectElem.parentNode;
2434}
2435if(!popupRect) popupRect = {x:0, y:0, left:0, right:0};
2436let x = popupRect.left, y = popupRect.top;
2437if(x<0) x = 0;
2438if(y<0) y = 0;
2439if(rectElem){
2440const rz = window.getComputedStyle(rectElem).zIndex;
2441let myZ;
2442if(rz && !isNaN(+rz)){
2443myZ = +rz + 1;
2444}else{
2445myZ = 10000;
2446}
2447fch.popup.e.style.zIndex = myZ;
2448}
2449fch.popup.show(x, y);
2450x = popupRect.left, y = popupRect.top;
2451popupRect = fch.popup.e.getBoundingClientRect();
2452const rectBody = document.body.getClientRects()[0];
2453if(popupRect.right > rectBody.right){
2454x -= (popupRect.right - rectBody.right);
2455}
2456if(x + popupRect.width > rectBody.right){
2457x = rectBody.x + (rectBody.width*0.1);
2458fch.popup.e.style.minWidth = '70%';
2459}else{
2460fch.popup.e.style.removeProperty('min-width');
2461x -= popupRect.width/2;
2462}
2463if(x<0) x = 0;
2464fch.popup.show(x, y);
2465return false;
2466};
2467f.foreachElement = function(e){
2468if(e.classList.contains('processed')) return;
2469e.classList.add('processed', 'help-buttonlet');
2470e.$helpContent = [];
2471e.childNodes.forEach((ch)=>e.$helpContent.push(ch));
2472e.$helpContent.forEach((ch)=>ch.remove());
2473e.addEventListener('click', f.clickHandler, false);
2474};
2475}
2476let elems;
2477if(!arguments.length){
2478arguments[0] = '.help-buttonlet:not(.processed)';
2479arguments.length = 1;
2480}
2481if(arguments.length){
2482if('string'===typeof arguments[0]){
2483elems = document.querySelectorAll(arguments[0]);
2484}else if(arguments[0] instanceof HTMLElement){
2485elems = [arguments[0]];
2486}else if(arguments[0].forEach){
2487elems = arguments[0];
2488}
2489}
2490if(elems) elems.forEach(f.foreachElement);
2491},
2492create: function(elem){
2493D.addClass(elem, 'help-buttonlet');
2494if(arguments.length>1){
2495const args = Array.prototype.slice.call(arguments,1);
2496D.append(elem, args);
2497}
2498this.setup(elem);
2499return elem;
2500}
2501};
2502F.onDOMContentLoaded( ()=>F.helpButtonlets.setup() );
2503})(window.fossil);
2504/* fossil.numbered-lines.js */
2505(function callee(arg){
2506var tbl = arg || document.querySelectorAll('table.numbered-lines');
2507if(tbl && !arg){
2508if(tbl.length>1){
2509tbl.forEach( (t)=>callee(t) );
2510return;
2511}else{
2512tbl = tbl[0];
2513}
2514}
2515if(!tbl) return;
2516const F = window.fossil, D = F.dom;
2517const tdLn = tbl.querySelector('td.line-numbers');
2518const urlArgsRaw = (window.location.search||'?')
2519.replace(/&?\budc=[^&]*/,'')
2520.replace(/&?\bln=[^&]*/,'')
2521.replace('?&','?');
2522const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 };
2523const lineTip = new F.PopupWidget({
2524refresh: function(){
2525const link = this.state.link;
2526D.clearElement(link);
2527if(lineState.start){
2528const ls = [lineState.start];
2529if(lineState.end) ls.push(lineState.end);
2530link.dataset.url = (
2531window.location.toString().split('?')[0]
2532+ lineState.urlArgs + '&ln='+ls.join('-')
2533);
2534D.append(
2535D.clearElement(link),
2536' Copy link to '+(
2537ls.length===1 ? 'line ' : 'lines '
2538)+ls.join('-')
2539);
2540}else{
2541D.append(link, "No lines selected.");
2542}
2543},
2544init: function(){
2545const e = this.e;
2546const btnCopy = D.attr(D.button(), 'id', 'linenum-copy-button');
2547link = D.label('linenum-copy-button');
2548this.state = {link};
2549F.copyButton(btnCopy,{
2550copyFromElement: link,
2551extractText: ()=>link.dataset.url,
2552oncopy: (ev)=>{
2553setTimeout(()=>lineTip.hide(), 400);
2554}
2555});
2556D.append(this.e, btnCopy, link);
2557}
2558});
2559tbl.addEventListener('click', ()=>lineTip.hide(), true);
2560tdLn.addEventListener('click', function f(ev){
2561if('SPAN'!==ev.target.tagName) return;
2562else if('number' !== typeof f.mode){
2563f.mode = 0;
2564f.spans = tdLn.querySelectorAll('span');
2565f.selected = tdLn.querySelectorAll('span.selected-line');
2566f.unselect = (e)=>D.removeClass(e, 'selected-line','start','end');
2567}
2568ev.stopPropagation();
2569const ln = +ev.target.innerText;
2570if(2===f.mode){
2571f.mode = 0;
2572}
2573if(0===f.mode){
2574lineState.end = 0;
2575lineState.start = ln;
2576f.mode = 1;
2577}else if(1===f.mode){
2578if(ln === lineState.start){
2579lineState.start = 0;
2580f.mode = 0;
2581}else{
2582if(ln<lineState.start){
2583lineState.end = lineState.start;
2584lineState.start = ln;
2585}else{
2586lineState.end = ln;
2587}
2588f.mode = 2;
2589}
2590}
2591if(f.selected){
2592f.selected.forEach(f.unselect);
2593f.selected = undefined;
2594}
2595if(0===f.mode){
2596lineTip.hide();
2597}else{
2598const rect = ev.target.getBoundingClientRect();
2599f.selected = [];
2600if(f.spans.length>=lineState.start){
2601let i = lineState.start, end = lineState.end || lineState.start, span = f.spans[i-1];
2602for( ; i<=end && span; span = f.spans[i++] ){
2603span.classList.add('selected-line');
2604f.selected.push(span);
2605if(i===lineState.start) span.classList.add('start');
2606if(i===end) span.classList.add('end');
2607}
2608}
2609lineTip.refresh().show(rect.right+3, rect.top-4);
2610}
2611}, false);
2612})();
2613/* fossil.pikchr.js */
2614(function(F){
2615"use strict";
2616const D = F.dom, P = F.pikchr = {};
2617P.addSrcView = function f(svg){
2618if(!f.hasOwnProperty('parentClick')){
2619f.parentClick = function(ev){
2620if(ev.altKey || ev.metaKey || ev.ctrlKey
2621|| this.classList.contains('toggle')){
2622this.classList.toggle('source');
2623ev.stopPropagation();
2624ev.preventDefault();
2625}
2626};
2627f.clickPikchrShow = function(ev){
2628const pId = this.dataset['pikchrid'];
2629if(!pId) return;
2630const ePikchr = this.parentNode.parentNode.querySelector('#'+pId);
2631if(!ePikchr) return;
2632ev.stopPropagation();
2633window.sessionStorage.setItem('pikchr-xfer', ePikchr.innerText);
2634};
2635};
2636if(!svg) svg = 'svg.pikchr';
2637if('string' === typeof svg){
2638document.querySelectorAll(svg).forEach((e)=>f.call(this, e));
2639return this;
2640}else if(svg.forEach){
2641svg.forEach((e)=>f.call(this, e));
2642return this;
2643}
2644if(svg.dataset.pikchrProcessed){
2645return this;
2646}
2647svg.dataset.pikchrProcessed = 1;
2648const parent = svg.parentNode.parentNode;
2649const srcView = parent ? svg.parentNode.nextElementSibling : undefined;
2650if(srcView && srcView.classList.contains('pikchr-src')){
2651parent.addEventListener('click', f.parentClick, false);
2652const eSpan = window.sessionStorage
2653? srcView.querySelector('span')
2654: undefined;
2655if(eSpan){
2656const openLink = eSpan.querySelector('a');
2657if(openLink){
2658openLink.addEventListener('click', f.clickPikchrShow, false);
2659eSpan.classList.remove('hidden');
2660}
2661}
2662}
2663return this;
2664};
2665})(window.fossil);
2666/* fossil.tabs.js */
2667"use strict";
2668(function(F){
2669const E = (s)=>document.querySelector(s),
2670EA = (s)=>document.querySelectorAll(s),
2671D = F.dom;
2672const TabManager = function(domElem, options){
2673this.e = {};
2674this.options = F.mergeLastWins(TabManager.defaultOptions , options);
2675if(domElem) this.init(domElem);
2676};
2677TabManager.defaultOptions = {
2678tabAccessKeys: true
2679};
2680const tabArg = function(arg,tabMgr){
2681if('string'===typeof arg) arg = E(arg);
2682else if(tabMgr && 'number'===typeof arg && arg>=0){
2683arg = tabMgr.e.tabs.childNodes[arg];
2684}
2685return arg;
2686};
2687const setVisible = function(e,yes){
2688D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
2689};
2690TabManager.prototype = {
2691init: function(container){
2692container = tabArg(container);
2693const cID = container.getAttribute('id');
2694if(!cID){
2695throw new Error("Tab container element is missing 'id' attribute.");
2696}
2697const c = this.e.container = container;
2698this.e.tabBar = D.addClass(D.div(),'tab-bar');
2699this.e.tabs = D.addClass(D.div(),'tabs');
2700D.append(c, this.e.tabBar, this.e.tabs);
2701let selectIndex = 0;
2702EA('[data-tab-parent='+cID+']').forEach((c,n)=>{
2703if(+c.dataset.tabSelect) selectIndex=n;
2704this.addTab(c);
2705});
2706return this.switchToTab(selectIndex);
2707},
2708getButtonForTab: function(tab){
2709tab = tabArg(tab,this);
2710var i = -1;
2711this.e.tabs.childNodes.forEach(function(e,n){
2712if(e===tab) i = n;
2713});
2714return i>=0 ? this.e.tabBar.childNodes[i] : undefined;
2715},
2716addTab: function f(tab){
2717if(!f.click){
2718f.click = function(e){
2719e.target.$manager.switchToTab(e.target.$tab);
2720};
2721}
2722tab = tabArg(tab);
2723tab.remove();
2724tab.classList.add('hidden');
2725D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
2726const tabCount = this.e.tabBar.childNodes.length+1;
2727const lbl = tab.dataset.tabLabel || 'Tab #'+tabCount;
2728const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
2729D.append(this.e.tabBar,btn);
2730btn.$manager = this;
2731btn.$tab = tab;
2732if(this.options.tabAccessKeys){
2733D.attr(btn, 'accesskey', tabCount);
2734}
2735btn.addEventListener('click', f.click, false);
2736return this;
2737},
2738_dispatchEvent: function(name, detail){
2739try{
2740this.e.container.dispatchEvent(
2741new CustomEvent(name, {detail: detail})
2742);
2743}catch(e){
2744}
2745return this;
2746},
2747addEventListener: function(eventName, callback){
2748this.e.container.addEventListener(eventName, callback, false);
2749return this;
2750},
2751addCustomWidget: function(e){
2752this.e.container.insertBefore(e, this.e.tabs);
2753return this;
2754},
2755switchToTab: function(tab){
2756tab = tabArg(tab,this);
2757const self = this;
2758if(tab===this._currentTab) return this;
2759else if(this._currentTab){
2760this._dispatchEvent('before-switch-from', this._currentTab);
2761}
2762delete this._currentTab;
2763this.e.tabs.childNodes.forEach((e,ndx)=>{
2764const btn = this.e.tabBar.childNodes[ndx];
2765if(e===tab){
2766if(D.hasClass(e,'selected')){
2767return;
2768}
2769self._dispatchEvent('before-switch-to',tab);
2770setVisible(e, true);
2771this._currentTab = e;
2772D.addClass(btn,'selected');
2773self._dispatchEvent('after-switch-to',tab);
2774}else{
2775if(D.hasClass(e,'selected')){
2776return;
2777}
2778setVisible(e, false);
2779D.removeClass(btn,'selected');
2780}
2781});
2782return this;
2783}
2784};
2785F.TabManager = TabManager;
2786})(window.fossil);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.