Fossil SCM
Implemented "downwards" loading of diff context (appending to previous chunk). Fixed HTML escaping of loaded lines so that it works together with the existing colorized diff content.
Commit
621ef5b7e8a33cb29e256c2efe7c32cfbaeafccccc6d30f8d0394cae80dce546
Parent
bc5dc16e55134b0…
1 file changed
+65
-12
+65
-12
| --- src/fossil.diff.js | ||
| +++ src/fossil.diff.js | ||
| @@ -23,11 +23,11 @@ | ||
| 23 | 23 | window.fossil.onPageLoad(function(){ |
| 24 | 24 | const F = window.fossil, D = F.dom; |
| 25 | 25 | const Diff = F.diff = { |
| 26 | 26 | e:{/*certain cached DOM elements*/}, |
| 27 | 27 | config: { |
| 28 | - chunkLoadLines: 20, | |
| 28 | + chunkLoadLines: 30, | |
| 29 | 29 | chunkFetch: { |
| 30 | 30 | /* Default callack handlers for Diff.fetchArtifactChunk(), |
| 31 | 31 | unless overridden by options passeed to that function. */ |
| 32 | 32 | beforesend: function(){}, |
| 33 | 33 | aftersend: function(){}, |
| @@ -282,10 +282,12 @@ | ||
| 282 | 282 | } |
| 283 | 283 | if(this.pos.next){ |
| 284 | 284 | btnUp = this.createButton(-1); |
| 285 | 285 | } |
| 286 | 286 | } |
| 287 | + //this.e.btnUp = btnUp; | |
| 288 | + //this.e.btnDown = btnDown; | |
| 287 | 289 | if(btnDown) D.append(this.e.btnWrapper, btnDown); |
| 288 | 290 | if(btnUp) D.append(this.e.btnWrapper, btnUp); |
| 289 | 291 | /* For debugging only... */ |
| 290 | 292 | this.e.posState = D.span(); |
| 291 | 293 | D.append(this.e.btnWrapper, this.e.posState); |
| @@ -347,34 +349,50 @@ | ||
| 347 | 349 | delete this.e.tr; |
| 348 | 350 | delete this.e; |
| 349 | 351 | delete this.pos; |
| 350 | 352 | }, |
| 351 | 353 | |
| 352 | - injectResponse: function(direction/*as for fetchChunk()*/, | |
| 353 | - urlParam/*from fetchChunk()*/, | |
| 354 | - lines/*response lines*/){ | |
| 354 | + injectResponse: function f(direction/*as for fetchChunk()*/, | |
| 355 | + urlParam/*from fetchChunk()*/, | |
| 356 | + lines/*response lines*/){ | |
| 357 | + if(!lines.length){ | |
| 358 | + /* No more data to load */ | |
| 359 | + this.destroy(); | |
| 360 | + return this; | |
| 361 | + } | |
| 355 | 362 | console.debug("Loading line range ",urlParam.from,"-",urlParam.to); |
| 356 | 363 | const lineno = [], |
| 357 | 364 | trPrev = this.e.tr.previousElementSibling, |
| 358 | 365 | trNext = this.e.tr.nextElementSibling, |
| 359 | - doAppend = !!trPrev /* true to append to previous TR, else prepend to NEXT TR */; | |
| 366 | + doAppend = (!!trPrev && direction>=0) /* true to append to previous TR, else prepend to NEXT TR */; | |
| 360 | 367 | const tr = trPrev || trNext; |
| 361 | - if(0!==direction){ | |
| 368 | + if(direction<0){ | |
| 362 | 369 | console.error("this case is not yet handled",arguments); |
| 363 | 370 | return this; |
| 364 | 371 | } |
| 365 | 372 | const joinTr = ( |
| 366 | 373 | 0===direction && trPrev && trNext |
| 367 | 374 | ) ? trNext : false |
| 368 | 375 | /* Truthy if we want to combine trPrev, the new content, and |
| 369 | 376 | trNext into trPrev and remove trNext. */; |
| 370 | 377 | let i, td; |
| 378 | + | |
| 379 | + if(!f.convertLines){ | |
| 380 | + f.convertLines = function(li){ | |
| 381 | + return li.join('\n') | |
| 382 | + .replaceAll('&','&') | |
| 383 | + .replaceAll('<','<')+'\n'; | |
| 384 | + }; | |
| 385 | + } | |
| 371 | 386 | |
| 372 | 387 | if(1){ // LHS line numbers... |
| 373 | 388 | const selector = '.difflnl > pre'; |
| 374 | 389 | td = tr.querySelector(selector); |
| 375 | - for( i = urlParam.from; i <= urlParam.to; ++i ){ | |
| 390 | + const lnTo = Math.min(urlParam.to, | |
| 391 | + urlParam.from + | |
| 392 | + lines.length - 1/*b/c request range is inclusive*/); | |
| 393 | + for( i = urlParam.from; i <= lnTo; ++i ){ | |
| 376 | 394 | lineno.push(i); |
| 377 | 395 | } |
| 378 | 396 | const lineNoTxt = lineno.join('\n')+'\n'; |
| 379 | 397 | const content = [td.innerHTML]; |
| 380 | 398 | if(doAppend) content.push(lineNoTxt); |
| @@ -386,11 +404,11 @@ | ||
| 386 | 404 | } |
| 387 | 405 | |
| 388 | 406 | if(1){// code block(s)... |
| 389 | 407 | const selector = '.difftxt > pre'; |
| 390 | 408 | td = tr.querySelectorAll(selector); |
| 391 | - const code = D.append(D.div(),lines.join('\n')+'\n').innerText; | |
| 409 | + const code = f.convertLines(lines); | |
| 392 | 410 | let joinNdx = 0; |
| 393 | 411 | td.forEach(function(e){ |
| 394 | 412 | const content = [e.innerHTML]; |
| 395 | 413 | if(doAppend) content.push(code); |
| 396 | 414 | else content.unshift(code); |
| @@ -441,10 +459,39 @@ | ||
| 441 | 459 | td.innerHTML = content.join(''); |
| 442 | 460 | if(joinTr) D.remove(joinTr); |
| 443 | 461 | Diff.checkTableWidth(true); |
| 444 | 462 | this.destroy(); |
| 445 | 463 | return this; |
| 464 | + }else if(1===direction){ | |
| 465 | + /* Expanding previous TR downwards. */ | |
| 466 | + // RHS line numbers... | |
| 467 | + let startLnR = this.pos.prev.endRhs+1; | |
| 468 | + lineno.length = lines.length; | |
| 469 | + for( i = startLnR; i < startLnR + lines.length; ++i ){ | |
| 470 | + lineno[i-startLnR] = i; | |
| 471 | + } | |
| 472 | + console.debug("lineno.length =",lineno.length); | |
| 473 | + console.debug("lines.length =",lineno.length); | |
| 474 | + this.pos.startLhs += lines.length; | |
| 475 | + this.pos.prev.endRhs += lines.length; | |
| 476 | + this.pos.prev.endLhs += lines.length; | |
| 477 | + const selector = '.difflnr > pre'; | |
| 478 | + td = tr.querySelector(selector); | |
| 479 | + const lineNoTxt = lineno.join('\n')+'\n'; | |
| 480 | + lineno.length = 0; | |
| 481 | + const content = [td.innerHTML]; | |
| 482 | + if(doAppend) content.push(lineNoTxt); | |
| 483 | + else content.unshift(lineNoTxt); | |
| 484 | + td.innerHTML = content.join(''); | |
| 485 | + if(lines.length < (urlParam.to - urlParam.from)){ | |
| 486 | + /* No more data. */ | |
| 487 | + this.destroy(); | |
| 488 | + }else{ | |
| 489 | + this.updatePosDebug(); | |
| 490 | + } | |
| 491 | + Diff.checkTableWidth(true); | |
| 492 | + return this; | |
| 446 | 493 | }else{ |
| 447 | 494 | console.debug("TODO: handle load of partial next/prev"); |
| 448 | 495 | this.updatePosDebug(); |
| 449 | 496 | } |
| 450 | 497 | }, |
| @@ -487,27 +534,33 @@ | ||
| 487 | 534 | aftersend: ()=>delete this.$isFetching, |
| 488 | 535 | onload: (list)=>this.injectResponse(direction,up,list) |
| 489 | 536 | }; |
| 490 | 537 | const up = fOpt.urlParams; |
| 491 | 538 | if(direction===0){ |
| 539 | + /* Easiest case: filling a whole gap. */ | |
| 492 | 540 | up.from = this.pos.startLhs; |
| 493 | 541 | up.to = this.pos.endLhs; |
| 494 | 542 | }else if(1===direction){ |
| 495 | 543 | /* Expand previous TR downwards. */ |
| 496 | 544 | if(!this.pos.prev){ |
| 497 | 545 | console.error("Attempt to fetch next diff lines but don't have any."); |
| 498 | 546 | return this; |
| 499 | 547 | } |
| 500 | - console.debug("fetchChunk(",direction,")"); | |
| 501 | - return this; | |
| 548 | + up.from = this.pos.prev.endLhs + 1; | |
| 549 | + up.to = up.from + | |
| 550 | + Diff.config.chunkLoadLines - 1/*b/c request range is inclusive*/; | |
| 551 | + if( this.pos.next && this.pos.next.startLhs <= up.to ){ | |
| 552 | + up.to = this.pos.next.startLhs - 1; | |
| 553 | + direction = 0; | |
| 554 | + } | |
| 502 | 555 | }else{ |
| 503 | 556 | /* Expand next TR upwards */ |
| 504 | - console.debug("fetchChunk(",direction,")"); | |
| 557 | + console.debug("fetchChunk(",direction,")",up); | |
| 505 | 558 | return this; |
| 506 | 559 | } |
| 507 | - | |
| 508 | 560 | this.$isFetching = true; |
| 561 | + console.debug("fetchChunk(",direction,")",up); | |
| 509 | 562 | Diff.fetchArtifactChunk(fOpt); |
| 510 | 563 | return this; |
| 511 | 564 | } |
| 512 | 565 | }; |
| 513 | 566 | |
| 514 | 567 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -23,11 +23,11 @@ | |
| 23 | window.fossil.onPageLoad(function(){ |
| 24 | const F = window.fossil, D = F.dom; |
| 25 | const Diff = F.diff = { |
| 26 | e:{/*certain cached DOM elements*/}, |
| 27 | config: { |
| 28 | chunkLoadLines: 20, |
| 29 | chunkFetch: { |
| 30 | /* Default callack handlers for Diff.fetchArtifactChunk(), |
| 31 | unless overridden by options passeed to that function. */ |
| 32 | beforesend: function(){}, |
| 33 | aftersend: function(){}, |
| @@ -282,10 +282,12 @@ | |
| 282 | } |
| 283 | if(this.pos.next){ |
| 284 | btnUp = this.createButton(-1); |
| 285 | } |
| 286 | } |
| 287 | if(btnDown) D.append(this.e.btnWrapper, btnDown); |
| 288 | if(btnUp) D.append(this.e.btnWrapper, btnUp); |
| 289 | /* For debugging only... */ |
| 290 | this.e.posState = D.span(); |
| 291 | D.append(this.e.btnWrapper, this.e.posState); |
| @@ -347,34 +349,50 @@ | |
| 347 | delete this.e.tr; |
| 348 | delete this.e; |
| 349 | delete this.pos; |
| 350 | }, |
| 351 | |
| 352 | injectResponse: function(direction/*as for fetchChunk()*/, |
| 353 | urlParam/*from fetchChunk()*/, |
| 354 | lines/*response lines*/){ |
| 355 | console.debug("Loading line range ",urlParam.from,"-",urlParam.to); |
| 356 | const lineno = [], |
| 357 | trPrev = this.e.tr.previousElementSibling, |
| 358 | trNext = this.e.tr.nextElementSibling, |
| 359 | doAppend = !!trPrev /* true to append to previous TR, else prepend to NEXT TR */; |
| 360 | const tr = trPrev || trNext; |
| 361 | if(0!==direction){ |
| 362 | console.error("this case is not yet handled",arguments); |
| 363 | return this; |
| 364 | } |
| 365 | const joinTr = ( |
| 366 | 0===direction && trPrev && trNext |
| 367 | ) ? trNext : false |
| 368 | /* Truthy if we want to combine trPrev, the new content, and |
| 369 | trNext into trPrev and remove trNext. */; |
| 370 | let i, td; |
| 371 | |
| 372 | if(1){ // LHS line numbers... |
| 373 | const selector = '.difflnl > pre'; |
| 374 | td = tr.querySelector(selector); |
| 375 | for( i = urlParam.from; i <= urlParam.to; ++i ){ |
| 376 | lineno.push(i); |
| 377 | } |
| 378 | const lineNoTxt = lineno.join('\n')+'\n'; |
| 379 | const content = [td.innerHTML]; |
| 380 | if(doAppend) content.push(lineNoTxt); |
| @@ -386,11 +404,11 @@ | |
| 386 | } |
| 387 | |
| 388 | if(1){// code block(s)... |
| 389 | const selector = '.difftxt > pre'; |
| 390 | td = tr.querySelectorAll(selector); |
| 391 | const code = D.append(D.div(),lines.join('\n')+'\n').innerText; |
| 392 | let joinNdx = 0; |
| 393 | td.forEach(function(e){ |
| 394 | const content = [e.innerHTML]; |
| 395 | if(doAppend) content.push(code); |
| 396 | else content.unshift(code); |
| @@ -441,10 +459,39 @@ | |
| 441 | td.innerHTML = content.join(''); |
| 442 | if(joinTr) D.remove(joinTr); |
| 443 | Diff.checkTableWidth(true); |
| 444 | this.destroy(); |
| 445 | return this; |
| 446 | }else{ |
| 447 | console.debug("TODO: handle load of partial next/prev"); |
| 448 | this.updatePosDebug(); |
| 449 | } |
| 450 | }, |
| @@ -487,27 +534,33 @@ | |
| 487 | aftersend: ()=>delete this.$isFetching, |
| 488 | onload: (list)=>this.injectResponse(direction,up,list) |
| 489 | }; |
| 490 | const up = fOpt.urlParams; |
| 491 | if(direction===0){ |
| 492 | up.from = this.pos.startLhs; |
| 493 | up.to = this.pos.endLhs; |
| 494 | }else if(1===direction){ |
| 495 | /* Expand previous TR downwards. */ |
| 496 | if(!this.pos.prev){ |
| 497 | console.error("Attempt to fetch next diff lines but don't have any."); |
| 498 | return this; |
| 499 | } |
| 500 | console.debug("fetchChunk(",direction,")"); |
| 501 | return this; |
| 502 | }else{ |
| 503 | /* Expand next TR upwards */ |
| 504 | console.debug("fetchChunk(",direction,")"); |
| 505 | return this; |
| 506 | } |
| 507 | |
| 508 | this.$isFetching = true; |
| 509 | Diff.fetchArtifactChunk(fOpt); |
| 510 | return this; |
| 511 | } |
| 512 | }; |
| 513 | |
| 514 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -23,11 +23,11 @@ | |
| 23 | window.fossil.onPageLoad(function(){ |
| 24 | const F = window.fossil, D = F.dom; |
| 25 | const Diff = F.diff = { |
| 26 | e:{/*certain cached DOM elements*/}, |
| 27 | config: { |
| 28 | chunkLoadLines: 30, |
| 29 | chunkFetch: { |
| 30 | /* Default callack handlers for Diff.fetchArtifactChunk(), |
| 31 | unless overridden by options passeed to that function. */ |
| 32 | beforesend: function(){}, |
| 33 | aftersend: function(){}, |
| @@ -282,10 +282,12 @@ | |
| 282 | } |
| 283 | if(this.pos.next){ |
| 284 | btnUp = this.createButton(-1); |
| 285 | } |
| 286 | } |
| 287 | //this.e.btnUp = btnUp; |
| 288 | //this.e.btnDown = btnDown; |
| 289 | if(btnDown) D.append(this.e.btnWrapper, btnDown); |
| 290 | if(btnUp) D.append(this.e.btnWrapper, btnUp); |
| 291 | /* For debugging only... */ |
| 292 | this.e.posState = D.span(); |
| 293 | D.append(this.e.btnWrapper, this.e.posState); |
| @@ -347,34 +349,50 @@ | |
| 349 | delete this.e.tr; |
| 350 | delete this.e; |
| 351 | delete this.pos; |
| 352 | }, |
| 353 | |
| 354 | injectResponse: function f(direction/*as for fetchChunk()*/, |
| 355 | urlParam/*from fetchChunk()*/, |
| 356 | lines/*response lines*/){ |
| 357 | if(!lines.length){ |
| 358 | /* No more data to load */ |
| 359 | this.destroy(); |
| 360 | return this; |
| 361 | } |
| 362 | console.debug("Loading line range ",urlParam.from,"-",urlParam.to); |
| 363 | const lineno = [], |
| 364 | trPrev = this.e.tr.previousElementSibling, |
| 365 | trNext = this.e.tr.nextElementSibling, |
| 366 | doAppend = (!!trPrev && direction>=0) /* true to append to previous TR, else prepend to NEXT TR */; |
| 367 | const tr = trPrev || trNext; |
| 368 | if(direction<0){ |
| 369 | console.error("this case is not yet handled",arguments); |
| 370 | return this; |
| 371 | } |
| 372 | const joinTr = ( |
| 373 | 0===direction && trPrev && trNext |
| 374 | ) ? trNext : false |
| 375 | /* Truthy if we want to combine trPrev, the new content, and |
| 376 | trNext into trPrev and remove trNext. */; |
| 377 | let i, td; |
| 378 | |
| 379 | if(!f.convertLines){ |
| 380 | f.convertLines = function(li){ |
| 381 | return li.join('\n') |
| 382 | .replaceAll('&','&') |
| 383 | .replaceAll('<','<')+'\n'; |
| 384 | }; |
| 385 | } |
| 386 | |
| 387 | if(1){ // LHS line numbers... |
| 388 | const selector = '.difflnl > pre'; |
| 389 | td = tr.querySelector(selector); |
| 390 | const lnTo = Math.min(urlParam.to, |
| 391 | urlParam.from + |
| 392 | lines.length - 1/*b/c request range is inclusive*/); |
| 393 | for( i = urlParam.from; i <= lnTo; ++i ){ |
| 394 | lineno.push(i); |
| 395 | } |
| 396 | const lineNoTxt = lineno.join('\n')+'\n'; |
| 397 | const content = [td.innerHTML]; |
| 398 | if(doAppend) content.push(lineNoTxt); |
| @@ -386,11 +404,11 @@ | |
| 404 | } |
| 405 | |
| 406 | if(1){// code block(s)... |
| 407 | const selector = '.difftxt > pre'; |
| 408 | td = tr.querySelectorAll(selector); |
| 409 | const code = f.convertLines(lines); |
| 410 | let joinNdx = 0; |
| 411 | td.forEach(function(e){ |
| 412 | const content = [e.innerHTML]; |
| 413 | if(doAppend) content.push(code); |
| 414 | else content.unshift(code); |
| @@ -441,10 +459,39 @@ | |
| 459 | td.innerHTML = content.join(''); |
| 460 | if(joinTr) D.remove(joinTr); |
| 461 | Diff.checkTableWidth(true); |
| 462 | this.destroy(); |
| 463 | return this; |
| 464 | }else if(1===direction){ |
| 465 | /* Expanding previous TR downwards. */ |
| 466 | // RHS line numbers... |
| 467 | let startLnR = this.pos.prev.endRhs+1; |
| 468 | lineno.length = lines.length; |
| 469 | for( i = startLnR; i < startLnR + lines.length; ++i ){ |
| 470 | lineno[i-startLnR] = i; |
| 471 | } |
| 472 | console.debug("lineno.length =",lineno.length); |
| 473 | console.debug("lines.length =",lineno.length); |
| 474 | this.pos.startLhs += lines.length; |
| 475 | this.pos.prev.endRhs += lines.length; |
| 476 | this.pos.prev.endLhs += lines.length; |
| 477 | const selector = '.difflnr > pre'; |
| 478 | td = tr.querySelector(selector); |
| 479 | const lineNoTxt = lineno.join('\n')+'\n'; |
| 480 | lineno.length = 0; |
| 481 | const content = [td.innerHTML]; |
| 482 | if(doAppend) content.push(lineNoTxt); |
| 483 | else content.unshift(lineNoTxt); |
| 484 | td.innerHTML = content.join(''); |
| 485 | if(lines.length < (urlParam.to - urlParam.from)){ |
| 486 | /* No more data. */ |
| 487 | this.destroy(); |
| 488 | }else{ |
| 489 | this.updatePosDebug(); |
| 490 | } |
| 491 | Diff.checkTableWidth(true); |
| 492 | return this; |
| 493 | }else{ |
| 494 | console.debug("TODO: handle load of partial next/prev"); |
| 495 | this.updatePosDebug(); |
| 496 | } |
| 497 | }, |
| @@ -487,27 +534,33 @@ | |
| 534 | aftersend: ()=>delete this.$isFetching, |
| 535 | onload: (list)=>this.injectResponse(direction,up,list) |
| 536 | }; |
| 537 | const up = fOpt.urlParams; |
| 538 | if(direction===0){ |
| 539 | /* Easiest case: filling a whole gap. */ |
| 540 | up.from = this.pos.startLhs; |
| 541 | up.to = this.pos.endLhs; |
| 542 | }else if(1===direction){ |
| 543 | /* Expand previous TR downwards. */ |
| 544 | if(!this.pos.prev){ |
| 545 | console.error("Attempt to fetch next diff lines but don't have any."); |
| 546 | return this; |
| 547 | } |
| 548 | up.from = this.pos.prev.endLhs + 1; |
| 549 | up.to = up.from + |
| 550 | Diff.config.chunkLoadLines - 1/*b/c request range is inclusive*/; |
| 551 | if( this.pos.next && this.pos.next.startLhs <= up.to ){ |
| 552 | up.to = this.pos.next.startLhs - 1; |
| 553 | direction = 0; |
| 554 | } |
| 555 | }else{ |
| 556 | /* Expand next TR upwards */ |
| 557 | console.debug("fetchChunk(",direction,")",up); |
| 558 | return this; |
| 559 | } |
| 560 | this.$isFetching = true; |
| 561 | console.debug("fetchChunk(",direction,")",up); |
| 562 | Diff.fetchArtifactChunk(fOpt); |
| 563 | return this; |
| 564 | } |
| 565 | }; |
| 566 | |
| 567 |