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, '&'], [/</g, '<']]; 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.