"use strict";
window.fossil.onPageLoad(function(){
const D = window.fossil.dom;
const addToggle = function(diffElem){
const sib = diffElem.previousElementSibling,
btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
if(!sib) return;
D.append(sib,btn);
btn.addEventListener('click', function(){
diffElem.classList.toggle('hidden');
}, false);
};
document.querySelectorAll('table.diff').forEach(addToggle);
});
window.fossil.onPageLoad(function(){
const F = window.fossil, D = F.dom;
const Diff = F.diff = {
e:{},
config: {
chunkLoadLines: (
F.config.diffContextLines * 3
) || 20,
chunkFetch: {
beforesend: function(){},
aftersend: function(){},
onerror: function(e){
console.error("XHR error: ",e);
}
}
}
};
Diff.fetchArtifactChunk = function(fetchOpt){
if(!fetchOpt.beforesend) fetchOpt.beforesend = Diff.config.chunkFetch.beforesend;
if(!fetchOpt.aftersend) fetchOpt.aftersend = Diff.config.chunkFetch.aftersend;
if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror;
fetchOpt.responseType = 'json';
return F.fetch('jchunk', fetchOpt);
};
const extractLineNo = function f(getLHS, getStart, tr, isSplit){
if(!f.rx){
f.rx = {
start: /^\s*(\d+)/,
end: /(\d+)\n?$/
}
}
const td = tr.querySelector('td:nth-child('+(
getLHS ? 1 : (isSplit ? 4 : 2)
)+')');
const m = f.rx[getStart ? 'start' : 'end'].exec(td.innerText);
return m ? +m[1] : undefined;
};
const ChunkLoadControls = function(tr){
this.$fetchQueue = [];
this.e = {
tr: tr,
table: tr.parentElement.parentElement
};
this.isSplit = this.e.table.classList.contains('splitdiff');
this.fileHash = this.e.table.dataset.lefthash;
tr.$chunker = this;
this.pos = {
startLhs: +tr.dataset.startln,
endLhs: +tr.dataset.endln
};
D.clearElement(tr);
this.e.td = D.addClass(
D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4),
'chunkctrl'
);
this.e.msgWidget = D.addClass(D.span(), 'hidden');
this.e.btnWrapper = D.div();
D.append(this.e.td, this.e.btnWrapper);
if(tr.nextElementSibling){
this.pos.next = {
startLhs: extractLineNo(true, true, tr.nextElementSibling, this.isSplit),
startRhs: extractLineNo(false, true, tr.nextElementSibling, this.isSplit)
};
}
if(tr.previousElementSibling){
this.pos.prev = {
endLhs: extractLineNo(true, false, tr.previousElementSibling, this.isSplit),
endRhs: extractLineNo(false, false, tr.previousElementSibling, this.isSplit)
};
}
let btnUp = false, btnDown = false;
if(this.pos.prev && this.pos.next
&& ((this.pos.endLhs - this.pos.startLhs)
<= Diff.config.chunkLoadLines)){
btnDown = false;
btnUp = this.createButton(this.FetchType.FillGap);
}else{
if(this.pos.prev){
btnDown = this.createButton(this.FetchType.PrevDown);
}
if(this.pos.next){
btnUp = this.createButton(this.FetchType.NextUp);
}
}
if(btnUp) D.append(this.e.btnWrapper, btnUp);
if(btnDown) D.append(this.e.btnWrapper, btnDown);
D.append(this.e.btnWrapper, this.e.msgWidget);
this.e.posState = D.span();
D.append(this.e.btnWrapper, this.e.posState);
this.updatePosDebug();
};
ChunkLoadControls.prototype = {
FetchType:{
PrevDown: 1,
FillGap: 0,
NextUp: -1,
ProcessQueue: 0x7fffffff
},
createButton: function(fetchType){
let b;
switch(fetchType){
case this.FetchType.PrevDown:
b = D.append(
D.addClass(D.span(), 'down'),
D.span()
);
break;
case this.FetchType.FillGap:
b = D.append(
D.addClass(D.span(), 'up', 'down'),
D.span()
);
break;
case this.FetchType.NextUp:
b = D.append(
D.addClass(D.span(), 'up'),
D.span()
);
break;
default:
throw new Error("Internal API misuse: unexpected fetchType value "+fetchType);
}
D.addClass(b, 'jcbutton');
b.addEventListener('click', ()=>this.fetchChunk(fetchType),false);
return b;
},
updatePosDebug: function(){
if(this.e.posState){
D.clearElement(this.e.posState);
}
return this;
},
destroy: function(){
delete this.$fetchQueue;
D.remove(this.e.tr);
delete this.e.tr.$chunker;
delete this.e.tr;
delete this.e;
delete this.pos;
},
maybeReplaceButtons: function(){
if(this.pos.next && this.pos.prev
&& (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){
D.clearElement(this.e.btnWrapper);
D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap));
if( this.$fetchQueue && this.$fetchQueue.length>1 ){
this.$fetchQueue[1] = this.FetchType.FillGap;
this.$fetchQueue.length = 2;
}
}
return this;
},
injectResponse: function f(fetchType,
urlParam,
lines){
if(!lines.length){
this.destroy();
return this;
}
this.msg(false);
const lineno = [],
trPrev = this.e.tr.previousElementSibling,
trNext = this.e.tr.nextElementSibling,
doAppend = (
!!trPrev && fetchType>=this.FetchType.FillGap
);
const tr = doAppend ? trPrev : trNext;
const joinTr = (
this.FetchType.FillGap===fetchType && trPrev && trNext
) ? trNext : false
;
let i, td;
if(!f.convertLines){
f.rx = [[/&/g, '&amp;'], [/</g, '&lt;']];
f.convertLines = function(li){
var s = li.join('\n');
f.rx.forEach((a)=>s=s.replace(a[0],a[1]));
return s + '\n';
};
}
if(1){
const selector = '.difflnl > pre';
td = tr.querySelector(selector);
const lnTo = Math.min(urlParam.to,
urlParam.from +
lines.length - 1);
for( i = urlParam.from; i <= lnTo; ++i ){
lineno.push(i);
}
const lineNoTxt = lineno.join('\n')+'\n';
const content = [td.innerHTML];
if(doAppend) content.push(lineNoTxt);
else content.unshift(lineNoTxt);
if(joinTr){
content.push(trNext.querySelector(selector).innerHTML);
}
td.innerHTML = content.join('');
}
if(1){
const selector = '.difftxt > pre';
td = tr.querySelectorAll(selector);
const code = f.convertLines(lines);
let joinNdx = 0;
td.forEach(function(e){
const content = [e.innerHTML];
if(doAppend) content.push(code);
else content.unshift(code);
if(joinTr){
content.push(trNext.querySelectorAll(selector)[joinNdx++].innerHTML)
}
e.innerHTML = content.join('');
});
}
if(1){
const selector = '.diffsep > pre';
td = tr.querySelector(selector);
for(i = 0; i < lineno.length; ++i) lineno[i] = '';
const blanks = lineno.join('\n')+'\n';
const content = [td.innerHTML];
if(doAppend) content.push(blanks);
else content.unshift(blanks);
if(joinTr){
content.push(trNext.querySelector(selector).innerHTML);
}
td.innerHTML = content.join('');
}
if(this.FetchType.FillGap===fetchType){
let startLnR = this.pos.prev
? this.pos.prev.endRhs+1
: this.pos.next.startRhs - lines.length;
lineno.length = lines.length;
for( i = startLnR; i < startLnR + lines.length; ++i ){
lineno[i-startLnR] = i;
}
const selector = '.difflnr > pre';
td = tr.querySelector(selector);
const lineNoTxt = lineno.join('\n')+'\n';
lineno.length = 0;
const content = [td.innerHTML];
if(doAppend) content.push(lineNoTxt);
else content.unshift(lineNoTxt);
if(joinTr){
content.push(trNext.querySelector(selector).innerHTML);
}
td.innerHTML = content.join('');
if(joinTr) D.remove(joinTr);
Diff.checkTableWidth(true);
this.destroy();
return this;
}else if(this.FetchType.PrevDown===fetchType){
let startLnR = this.pos.prev.endRhs+1;
lineno.length = lines.length;
for( i = startLnR; i < startLnR + lines.length; ++i ){
lineno[i-startLnR] = i;
}
this.pos.startLhs += lines.length;
this.pos.prev.endRhs += lines.length;
this.pos.prev.endLhs += lines.length;
const selector = '.difflnr > pre';
td = tr.querySelector(selector);
const lineNoTxt = lineno.join('\n')+'\n';
lineno.length = 0;
const content = [td.innerHTML];
if(doAppend) content.push(lineNoTxt);
else content.unshift(lineNoTxt);
td.innerHTML = content.join('');
if(lines.length < (urlParam.to - urlParam.from)){
this.destroy();
}else{
this.maybeReplaceButtons();
this.updatePosDebug();
}
Diff.checkTableWidth(true);
return this;
}else if(this.FetchType.NextUp===fetchType){
if(doAppend){
throw new Error("Internal precondition violation: doAppend is true.");
}
let startLnR = this.pos.next.startRhs - lines.length;
lineno.length = lines.length;
for( i = startLnR; i < startLnR + lines.length; ++i ){
lineno[i-startLnR] = i;
}
this.pos.endLhs -= lines.length;
this.pos.next.startRhs -= lines.length;
this.pos.next.startLhs -= lines.length;
const selector = '.difflnr > pre';
td = tr.querySelector(selector);
const lineNoTxt = lineno.join('\n')+'\n';
lineno.length = 0;
td.innerHTML = lineNoTxt + td.innerHTML;
if(this.pos.endLhs<1
|| lines.length < (urlParam.to - urlParam.from)){
this.destroy();
}else{
this.maybeReplaceButtons();
this.updatePosDebug();
}
Diff.checkTableWidth(true);
return this;
}else{
throw new Error("Unexpected 'fetchType' value.");
}
},
msg: function(isError,txt){
if(txt){
if(isError) D.addClass(this.e.msgWidget, 'error');
else D.removeClass(this.e.msgWidget, 'error');
D.append(
D.removeClass(D.clearElement(this.e.msgWidget), 'hidden'),
txt);
}else{
D.addClass(D.clearElement(this.e.msgWidget), 'hidden');
}
return this;
},
fetchChunk: function(fetchType){
if( !this.$fetchQueue ) return this;
if( fetchType==this.FetchType.ProcessQueue ){
this.$fetchQueue.shift();
if( this.$fetchQueue.length==0 ) return this;
}
else{
this.$fetchQueue.push(fetchType);
if( this.$fetchQueue.length!=1 ) return this;
}
fetchType = this.$fetchQueue[0];
if( fetchType==this.FetchType.ProcessQueue ){
this.$fetchQueue.length = 0;
return this;
}
if(fetchType===this.FetchType.NextUp && !this.pos.next
|| fetchType===this.FetchType.PrevDown && !this.pos.prev){
console.error("Attempt to fetch diff lines but don't have any.");
return this;
}
this.msg(false,"Fetching diff chunk...");
const self = this;
const fOpt = {
urlParams:{
name: this.fileHash, from: 0, to: 0
},
aftersend: ()=>this.msg(false),
onload: function(list){
self.injectResponse(fetchType,up,list);
if( !self.$fetchQueue || self.$fetchQueue.length==0 ) return;
self.$fetchQueue[0] = self.FetchType.ProcessQueue;
setTimeout(self.fetchChunk.bind(self,self.FetchType.ProcessQueue));
}
};
const up = fOpt.urlParams;
if(fetchType===this.FetchType.FillGap){
up.from = this.pos.startLhs;
up.to = this.pos.endLhs;
}else if(this.FetchType.PrevDown===fetchType){
if(!this.pos.prev){
console.error("Attempt to fetch next diff lines but don't have any.");
return this;
}
up.from = this.pos.prev.endLhs + 1;
up.to = up.from +
Diff.config.chunkLoadLines - 1;
if( this.pos.next && this.pos.next.startLhs <= up.to ){
up.to = this.pos.next.startLhs - 1;
fetchType = this.FetchType.FillGap;
}
}else{
if(!this.pos.next){
console.error("Attempt to fetch previous diff lines but don't have any.");
return this;
}
up.to = this.pos.next.startLhs - 1;
up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1);
if( this.pos.prev && this.pos.prev.endLhs >= up.from ){
up.from = this.pos.prev.endLhs + 1;
fetchType = this.FetchType.FillGap;
}
}
fOpt.onerror = function(err){
if(self.e){
self.msg(true,err.message);
self.$fetchQueue.length = 0;
}else{
Diff.config.chunkFetch.onerror.call(this,err);
}
};
Diff.fetchArtifactChunk(fOpt);
return this;
}
};
Diff.setupDiffContextLoad = function(tables){
if('string'===typeof tables){
tables = document.querySelectorAll(tables);
}else if(!tables){
tables = document.querySelectorAll('table.diff[data-lefthash]:not(.diffskipped)');
}
tables.forEach(function(table){
if(table.classList.contains('diffskipped') || !table.dataset.lefthash) return;
D.addClass(table, 'diffskipped');
table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){
new ChunkLoadControls(D.addClass(tr, 'jchunk'));
});
});
return F;
};
Diff.setupDiffContextLoad();
});
window.fossil.onPageLoad(function(){
const SCROLL_LEN = 25;
const F = window.fossil, D = F.dom, Diff = F.diff;
var lastWidth;
Diff.checkTableWidth = function f(force){
if(undefined === f.contentNode){
f.contentNode = document.querySelector('div.content');
}
force = true;
const parentCS = window.getComputedStyle(f.contentNode);
const parentWidth = (
f.contentNode.clientWidth
- parseFloat(parentCS.marginLeft) - parseFloat(parentCS.marginRight)
);
if( !force && parentWidth===lastWidth ) return this;
lastWidth = parentWidth;
let w = lastWidth*0.5 - 100;
if(force || !f.colsL){
f.colsL = document.querySelectorAll('td.difftxtl pre');
}
f.colsL.forEach(function(e){
e.style.width = w + "px";
e.style.maxWidth = w + "px";
});
if(force || !f.colsR){
f.colsR = document.querySelectorAll('td.difftxtr pre');
}
f.colsR.forEach(function(e){
e.style.width = w + "px";
e.style.maxWidth = w + "px";
});
if(force || !f.colsU){
f.colsU = document.querySelectorAll('td.difftxtu pre');
}
f.colsU.forEach(function(e){
w = lastWidth - 3;
var k = e.parentElement;
while(k = k.previousElementSibling) w -= k.scrollWidth;
e.style.width = w + "px";
e.style.maxWidth = w + "px";
});
if(0){
if(!f.allDiffs){
f.allDiffs = document.querySelectorAll('table.diff');
}
w = lastWidth;
f.allDiffs.forEach(function f(e){
if(0 && !f.$){
f.$ = e.getClientRects()[0];
console.debug("diff table w =",w," f.$x",f.$);
w - 2*f.$.x;
}
e.style.maxWidth = w + "px";
});
}
return this;
};
const scrollLeft = function(event){
const table = this.parentElement.parentElement.
parentElement.parentElement;
table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
return false;
};
Diff.initTableDiff = function f(diff, unifiedDiffs){
if(!diff){
let i, diffs;
diffs = document.querySelectorAll('table.splitdiff');
for(i=0; i<diffs.length; ++i){
f.call(this, diffs[i], false);
}
diffs = document.querySelectorAll('table.udiff');
for(i=0; i<diffs.length; ++i){
f.call(this, diffs[i], true);
}
return this;
}
diff.$txtCols = diff.querySelectorAll('td.difftxt');
diff.$txtPres = diff.querySelectorAll('td.difftxt pre');
var width = 0;
diff.$txtPres.forEach(function(e){
if(width < e.scrollWidth) width = e.scrollWidth;
});
diff.$txtCols.forEach((e)=>e.style.width = width + 'px');
diff.$txtPres.forEach(function(e){
e.style.maxWidth = width + 'px';
e.style.width = width + 'px';
if(!unifiedDiffs && !e.classList.contains('scroller')){
D.addClass(e, 'scroller');
e.addEventListener('scroll', scrollLeft, false);
}
});
if(!unifiedDiffs){
diff.tabIndex = 0;
if(!diff.classList.contains('scroller')){
D.addClass(diff, 'scroller');
diff.addEventListener('keydown', function(e){
e = e || event;
const len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
if( !len ) return;
this.$txtPres[0].scrollLeft += len;
return false;
}, false);
}
}
return this;
}
window.fossil.page.tweakSbsDiffs = function(){
document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff(e));
Diff.checkTableWidth();
};
Diff.initTableDiff().checkTableWidth();
window.addEventListener('resize', F.debounce(()=>Diff.checkTableWidth()));
}, false);