Fossil SCM

Integrate checkbox to toggle side-by-side sync scrolling and persist the setting across pages/apps using localStorage/sessionStorage. Currently applies to /info, /vinfo, /vdiff, /wikiedit, /fileedit. The alignment of the toggle isn't _quite_ right on the /*edit pages but that's difficult to fix without using flex layout, which introduces a rat's tail of further fixes.

stephan 2024-09-03 11:16 diff-scroll-sync
Commit 564a64027a12de7142e01b9b868aede4959dbff580161e0e3301f3cf699ca470
+9 -1
--- src/default.css
+++ src/default.css
@@ -1351,20 +1351,28 @@
13511351
.input-with-label {
13521352
border: 1px inset rgba(128, 128, 128, 0.5);
13531353
border-radius: 0.25em;
13541354
padding: 0.1em;
13551355
margin: 0 0.5em;
1356
- display: inline-block;
1356
+ display: inline-block
1357
+ /* We would really like flex layout but changing that
1358
+ currently introduces a good deal of UI breakage
1359
+ to chase down. The advantage would be better alignment
1360
+ of the contained elements. */;
13571361
cursor: default;
13581362
white-space: nowrap;
1363
+}
1364
+.submenu .input-with-label {
1365
+ border: none;
13591366
}
13601367
.input-with-label > * {
13611368
vertical-align: middle;
13621369
}
13631370
.input-with-label > label {
13641371
display: inline; /* some skins set label display to block! */
13651372
cursor: pointer;
1373
+ white-space: nowrap;
13661374
}
13671375
.input-with-label > input {
13681376
margin: 0;
13691377
}
13701378
.input-with-label > button {
13711379
--- src/default.css
+++ src/default.css
@@ -1351,20 +1351,28 @@
1351 .input-with-label {
1352 border: 1px inset rgba(128, 128, 128, 0.5);
1353 border-radius: 0.25em;
1354 padding: 0.1em;
1355 margin: 0 0.5em;
1356 display: inline-block;
 
 
 
 
1357 cursor: default;
1358 white-space: nowrap;
 
 
 
1359 }
1360 .input-with-label > * {
1361 vertical-align: middle;
1362 }
1363 .input-with-label > label {
1364 display: inline; /* some skins set label display to block! */
1365 cursor: pointer;
 
1366 }
1367 .input-with-label > input {
1368 margin: 0;
1369 }
1370 .input-with-label > button {
1371
--- src/default.css
+++ src/default.css
@@ -1351,20 +1351,28 @@
1351 .input-with-label {
1352 border: 1px inset rgba(128, 128, 128, 0.5);
1353 border-radius: 0.25em;
1354 padding: 0.1em;
1355 margin: 0 0.5em;
1356 display: inline-block
1357 /* We would really like flex layout but changing that
1358 currently introduces a good deal of UI breakage
1359 to chase down. The advantage would be better alignment
1360 of the contained elements. */;
1361 cursor: default;
1362 white-space: nowrap;
1363 }
1364 .submenu .input-with-label {
1365 border: none;
1366 }
1367 .input-with-label > * {
1368 vertical-align: middle;
1369 }
1370 .input-with-label > label {
1371 display: inline; /* some skins set label display to block! */
1372 cursor: pointer;
1373 white-space: nowrap;
1374 }
1375 .input-with-label > input {
1376 margin: 0;
1377 }
1378 .input-with-label > button {
1379
+63 -11
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -625,22 +625,78 @@
625625
/*
626626
** For a side-by-side diff, ensure that horizontal scrolling of either
627627
** side of the diff is synchronized with the other side.
628628
*/
629629
window.fossil.onPageLoad(function(){
630
- const SCROLL_LEN = 25;
631630
const F = window.fossil, D = F.dom, Diff = F.diff;
631
+
632
+ /* Look for a parent element to hold the sbs-sync-scroll toggle
633
+ checkbox. This differs per page. If we don't find one, simply
634
+ elide that toggle and use whatever preference the user last
635
+ specified (defaulting to on). */
636
+ let cbSync /* scroll-sync checkbox */;
637
+ let eToggleParent /* element to put the sync-scroll checkbox in */;
638
+ const potentialParents = [ /* possible parents for the checkbox */
639
+ /* Put the most likely pages at the end, as array.pop() is more
640
+ efficient than array.shift() (see loop below). */
641
+ /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
642
+ /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
643
+ /* /vdiff */ 'body.vdiff form div.submenu',
644
+ /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu'
645
+ ];
646
+ while( potentialParents.length ){
647
+ if( (eToggleParent = document.querySelector(potentialParents.pop())) ){
648
+ break;
649
+ }
650
+ }
651
+ const keySbsScroll = 'sync-diff-scroll' /* F.storage key */;
652
+ if( eToggleParent ){
653
+ /* Add a checkbox to toggle sbs scroll sync. Remember that in
654
+ order to be UI-consistent in the /vdiff page we have to ensure
655
+ that the checkbox is to the LEFT of of its label. We store the
656
+ sync-scroll preference in F.storage (not a cookie) so that it
657
+ persists across page loads and different apps. */
658
+ cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
659
+ D.append(eToggleParent, D.append(
660
+ D.addClass(D.create('span'), 'input-with-label'),
661
+ D.append(D.create('label'),
662
+ cbSync, "Sync side-by-side scrolling?")
663
+ ));
664
+ cbSync.addEventListener('change', function(e){
665
+ F.storage.set(keySbsScroll, e.target.checked);
666
+ });
667
+ }
668
+ const useSync = cbSync ? ()=>cbSync.checked : ()=>F.storage.getBool(keySbsScroll,true);
669
+
670
+ /* Now set up the events to enable syncronized scrolling... */
632671
const scrollLeft = function(event){
633672
const table = this.parentElement/*TD*/.parentElement/*TR*/.
634673
parentElement/*TBODY*/.parentElement/*TABLE*/;
635
- table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
674
+ if( useSync() ){
675
+ table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
676
+ }
636677
return false;
637678
};
679
+ const SCROLL_LEN = 64/* pixels to scroll for keyboard events */;
638680
const keycodes = Object.assign(Object.create(null),{
639
- 37: -SCROLL_LEN, 39: SCROLL_LEN
681
+ 37/*cursor left*/: -SCROLL_LEN, 39/*cursor right*/: SCROLL_LEN
640682
});
641
- Diff.initTableDiff = function f(diff, unifiedDiffs){
683
+ /**
684
+ Sets up synchronized scrolling of table.splitdiff element
685
+ `diff`. If passed no argument, it scans the dom for elements to
686
+ initialize. The second argument is for this function's own
687
+ internal use.
688
+
689
+ It's okay (but wasteful) to pass the same element to this
690
+ function multiple times: it will only be set up for sync
691
+ scrolling the first time it's passed to this function.
692
+
693
+ Note that this setup is ignorant of the cbSync toggle: the toggle
694
+ is checked when scrolling, not when initializing the sync-scroll
695
+ capability.
696
+ */
697
+ const initTableDiff = function f(diff, unifiedDiffs){
642698
if(!diff){
643699
let i, diffs;
644700
diffs = document.querySelectorAll('table.splitdiff');
645701
for(i=0; i<diffs.length; ++i){
646702
f.call(this, diffs[i], false);
@@ -651,14 +707,10 @@
651707
}
652708
return this;
653709
}
654710
diff.$txtCols = diff.querySelectorAll('td.difftxt');
655711
diff.$txtPres = diff.querySelectorAll('td.difftxt pre');
656
- var width = 0;
657
- diff.$txtPres.forEach(function(e){
658
- if(width < e.scrollWidth) width = e.scrollWidth;
659
- });
660712
diff.$txtPres.forEach(function(e){
661713
if(!unifiedDiffs && !e.classList.contains('scroller')){
662714
D.addClass(e, 'scroller');
663715
e.addEventListener('scroll', scrollLeft, false);
664716
}
@@ -667,18 +719,18 @@
667719
diff.tabIndex = 0;
668720
if(!diff.classList.contains('scroller')){
669721
D.addClass(diff, 'scroller');
670722
diff.addEventListener('keydown', function(e){
671723
const len = keycodes[e.keyCode];
672
- if( !len ) return;
724
+ if( !len ) return false;
673725
this.$txtPres[0].scrollLeft += len;
674726
return false;
675727
}, false);
676728
}
677729
}
678730
return this;
679731
}
680732
window.fossil.page.tweakSbsDiffs = function(){
681
- document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff(e));
733
+ document.querySelectorAll('table.splitdiff').forEach((e)=>initTableDiff(e));
682734
};
683
- Diff.initTableDiff();
735
+ initTableDiff();
684736
}, false);
685737
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -625,22 +625,78 @@
625 /*
626 ** For a side-by-side diff, ensure that horizontal scrolling of either
627 ** side of the diff is synchronized with the other side.
628 */
629 window.fossil.onPageLoad(function(){
630 const SCROLL_LEN = 25;
631 const F = window.fossil, D = F.dom, Diff = F.diff;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632 const scrollLeft = function(event){
633 const table = this.parentElement/*TD*/.parentElement/*TR*/.
634 parentElement/*TBODY*/.parentElement/*TABLE*/;
635 table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
 
 
636 return false;
637 };
 
638 const keycodes = Object.assign(Object.create(null),{
639 37: -SCROLL_LEN, 39: SCROLL_LEN
640 });
641 Diff.initTableDiff = function f(diff, unifiedDiffs){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642 if(!diff){
643 let i, diffs;
644 diffs = document.querySelectorAll('table.splitdiff');
645 for(i=0; i<diffs.length; ++i){
646 f.call(this, diffs[i], false);
@@ -651,14 +707,10 @@
651 }
652 return this;
653 }
654 diff.$txtCols = diff.querySelectorAll('td.difftxt');
655 diff.$txtPres = diff.querySelectorAll('td.difftxt pre');
656 var width = 0;
657 diff.$txtPres.forEach(function(e){
658 if(width < e.scrollWidth) width = e.scrollWidth;
659 });
660 diff.$txtPres.forEach(function(e){
661 if(!unifiedDiffs && !e.classList.contains('scroller')){
662 D.addClass(e, 'scroller');
663 e.addEventListener('scroll', scrollLeft, false);
664 }
@@ -667,18 +719,18 @@
667 diff.tabIndex = 0;
668 if(!diff.classList.contains('scroller')){
669 D.addClass(diff, 'scroller');
670 diff.addEventListener('keydown', function(e){
671 const len = keycodes[e.keyCode];
672 if( !len ) return;
673 this.$txtPres[0].scrollLeft += len;
674 return false;
675 }, false);
676 }
677 }
678 return this;
679 }
680 window.fossil.page.tweakSbsDiffs = function(){
681 document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff(e));
682 };
683 Diff.initTableDiff();
684 }, false);
685
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -625,22 +625,78 @@
625 /*
626 ** For a side-by-side diff, ensure that horizontal scrolling of either
627 ** side of the diff is synchronized with the other side.
628 */
629 window.fossil.onPageLoad(function(){
 
630 const F = window.fossil, D = F.dom, Diff = F.diff;
631
632 /* Look for a parent element to hold the sbs-sync-scroll toggle
633 checkbox. This differs per page. If we don't find one, simply
634 elide that toggle and use whatever preference the user last
635 specified (defaulting to on). */
636 let cbSync /* scroll-sync checkbox */;
637 let eToggleParent /* element to put the sync-scroll checkbox in */;
638 const potentialParents = [ /* possible parents for the checkbox */
639 /* Put the most likely pages at the end, as array.pop() is more
640 efficient than array.shift() (see loop below). */
641 /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
642 /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
643 /* /vdiff */ 'body.vdiff form div.submenu',
644 /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu'
645 ];
646 while( potentialParents.length ){
647 if( (eToggleParent = document.querySelector(potentialParents.pop())) ){
648 break;
649 }
650 }
651 const keySbsScroll = 'sync-diff-scroll' /* F.storage key */;
652 if( eToggleParent ){
653 /* Add a checkbox to toggle sbs scroll sync. Remember that in
654 order to be UI-consistent in the /vdiff page we have to ensure
655 that the checkbox is to the LEFT of of its label. We store the
656 sync-scroll preference in F.storage (not a cookie) so that it
657 persists across page loads and different apps. */
658 cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
659 D.append(eToggleParent, D.append(
660 D.addClass(D.create('span'), 'input-with-label'),
661 D.append(D.create('label'),
662 cbSync, "Sync side-by-side scrolling?")
663 ));
664 cbSync.addEventListener('change', function(e){
665 F.storage.set(keySbsScroll, e.target.checked);
666 });
667 }
668 const useSync = cbSync ? ()=>cbSync.checked : ()=>F.storage.getBool(keySbsScroll,true);
669
670 /* Now set up the events to enable syncronized scrolling... */
671 const scrollLeft = function(event){
672 const table = this.parentElement/*TD*/.parentElement/*TR*/.
673 parentElement/*TBODY*/.parentElement/*TABLE*/;
674 if( useSync() ){
675 table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
676 }
677 return false;
678 };
679 const SCROLL_LEN = 64/* pixels to scroll for keyboard events */;
680 const keycodes = Object.assign(Object.create(null),{
681 37/*cursor left*/: -SCROLL_LEN, 39/*cursor right*/: SCROLL_LEN
682 });
683 /**
684 Sets up synchronized scrolling of table.splitdiff element
685 `diff`. If passed no argument, it scans the dom for elements to
686 initialize. The second argument is for this function's own
687 internal use.
688
689 It's okay (but wasteful) to pass the same element to this
690 function multiple times: it will only be set up for sync
691 scrolling the first time it's passed to this function.
692
693 Note that this setup is ignorant of the cbSync toggle: the toggle
694 is checked when scrolling, not when initializing the sync-scroll
695 capability.
696 */
697 const initTableDiff = function f(diff, unifiedDiffs){
698 if(!diff){
699 let i, diffs;
700 diffs = document.querySelectorAll('table.splitdiff');
701 for(i=0; i<diffs.length; ++i){
702 f.call(this, diffs[i], false);
@@ -651,14 +707,10 @@
707 }
708 return this;
709 }
710 diff.$txtCols = diff.querySelectorAll('td.difftxt');
711 diff.$txtPres = diff.querySelectorAll('td.difftxt pre');
 
 
 
 
712 diff.$txtPres.forEach(function(e){
713 if(!unifiedDiffs && !e.classList.contains('scroller')){
714 D.addClass(e, 'scroller');
715 e.addEventListener('scroll', scrollLeft, false);
716 }
@@ -667,18 +719,18 @@
719 diff.tabIndex = 0;
720 if(!diff.classList.contains('scroller')){
721 D.addClass(diff, 'scroller');
722 diff.addEventListener('keydown', function(e){
723 const len = keycodes[e.keyCode];
724 if( !len ) return false;
725 this.$txtPres[0].scrollLeft += len;
726 return false;
727 }, false);
728 }
729 }
730 return this;
731 }
732 window.fossil.page.tweakSbsDiffs = function(){
733 document.querySelectorAll('table.splitdiff').forEach((e)=>initTableDiff(e));
734 };
735 initTableDiff();
736 }, false);
737
+2 -1
--- src/info.c
+++ src/info.c
@@ -891,11 +891,12 @@
891891
"<div class=\"section accordion\">References</div>\n");
892892
@ <div class="section accordion">Context</div><div class="accordion_panel">
893893
render_checkin_context(rid, 0, 0, 0);
894894
@ </div><div class="section accordion">Changes</div>
895895
@ <div class="accordion_panel">
896
- @ <div class="sectionmenu">
896
+ @ <div class="sectionmenu info-changes-menu">
897
+ /* ^^^ .info-changes-menu is used by diff scroll sync */
897898
pCfg = construct_diff_flags(diffType, &DCfg);
898899
DCfg.pRe = pRe;
899900
zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
900901
if( diffType!=0 ){
901902
@ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
902903
--- src/info.c
+++ src/info.c
@@ -891,11 +891,12 @@
891 "<div class=\"section accordion\">References</div>\n");
892 @ <div class="section accordion">Context</div><div class="accordion_panel">
893 render_checkin_context(rid, 0, 0, 0);
894 @ </div><div class="section accordion">Changes</div>
895 @ <div class="accordion_panel">
896 @ <div class="sectionmenu">
 
897 pCfg = construct_diff_flags(diffType, &DCfg);
898 DCfg.pRe = pRe;
899 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
900 if( diffType!=0 ){
901 @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
902
--- src/info.c
+++ src/info.c
@@ -891,11 +891,12 @@
891 "<div class=\"section accordion\">References</div>\n");
892 @ <div class="section accordion">Context</div><div class="accordion_panel">
893 render_checkin_context(rid, 0, 0, 0);
894 @ </div><div class="section accordion">Changes</div>
895 @ <div class="accordion_panel">
896 @ <div class="sectionmenu info-changes-menu">
897 /* ^^^ .info-changes-menu is used by diff scroll sync */
898 pCfg = construct_diff_flags(diffType, &DCfg);
899 DCfg.pRe = pRe;
900 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
901 if( diffType!=0 ){
902 @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
903

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button