Fossil

Check-in [564a64027a]
Login

Check-in [564a64027a]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | diff-scroll-sync
Files: files | file ages | folders
SHA3-256: 564a64027a12de7142e01b9b868aede4959dbff580161e0e3301f3cf699ca470
User & Date: stephan 2024-09-03 11:16:25.468
Context
2024-09-03
11:45
Correct handling of keyboard-based diff scrolling. ... (Closed-Leaf check-in: 0ef89983b8 user: stephan tags: diff-scroll-sync)
11:16
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. ... (check-in: 564a64027a user: stephan tags: diff-scroll-sync)
2024-08-31
20:26
Re-integrate side-by-side diff sync scrolling in the /wikiedit and /fileedit diff views. ... (check-in: 3765b46475 user: stephan tags: diff-scroll-sync)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
1349
1350
1351
1352
1353
1354
1355
1356




1357
1358



1359
1360
1361
1362
1363
1364
1365

1366
1367
1368
1369
1370
1371
1372
   (e.g. DIV) so that certain nesting constructs are legal.
*/
.input-with-label {
  border: 1px inset rgba(128, 128, 128, 0.5);
  border-radius: 0.25em;
  padding: 0.1em;
  margin: 0 0.5em;
  display: inline-block;




  cursor: default;
  white-space: nowrap;



}
.input-with-label > * {
  vertical-align: middle;
}
.input-with-label > label {
  display: inline; /* some skins set label display to block! */
  cursor: pointer;

}
.input-with-label > input {
  margin: 0;
}
.input-with-label > button {
  margin: 0;
}







|
>
>
>
>


>
>
>







>







1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
   (e.g. DIV) so that certain nesting constructs are legal.
*/
.input-with-label {
  border: 1px inset rgba(128, 128, 128, 0.5);
  border-radius: 0.25em;
  padding: 0.1em;
  margin: 0 0.5em;
  display: inline-block
           /* We would really like flex layout but changing that
              currently introduces a good deal of UI breakage
              to chase down. The advantage would be better alignment
              of the contained elements. */;
  cursor: default;
  white-space: nowrap;
}
.submenu .input-with-label {
  border: none;
}
.input-with-label > * {
  vertical-align: middle;
}
.input-with-label > label {
  display: inline; /* some skins set label display to block! */
  cursor: pointer;
  white-space: nowrap;
}
.input-with-label > input {
  margin: 0;
}
.input-with-label > button {
  margin: 0;
}
Changes to src/fossil.diff.js.
623
624
625
626
627
628
629
630
631








































632
633
634

635

636
637

638
639
640














641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
  Diff.setupDiffContextLoad();
});
/*
** For a side-by-side diff, ensure that horizontal scrolling of either
** side of the diff is synchronized with the other side.
*/
window.fossil.onPageLoad(function(){
  const SCROLL_LEN = 25;
  const F = window.fossil, D = F.dom, Diff = F.diff;








































  const scrollLeft = function(event){
    const table = this.parentElement/*TD*/.parentElement/*TR*/.
      parentElement/*TBODY*/.parentElement/*TABLE*/;

    table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));

    return false;
  };

  const keycodes = Object.assign(Object.create(null),{
    37: -SCROLL_LEN, 39: SCROLL_LEN
  });














  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.$txtPres.forEach(function(e){
      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){
          const len = keycodes[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.initTableDiff();
}, false);







<

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>
|
>


>

|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
|














<
<
<
<












|








|

|

623
624
625
626
627
628
629

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711




712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
  Diff.setupDiffContextLoad();
});
/*
** For a side-by-side diff, ensure that horizontal scrolling of either
** side of the diff is synchronized with the other side.
*/
window.fossil.onPageLoad(function(){

  const F = window.fossil, D = F.dom, Diff = F.diff;

  /* Look for a parent element to hold the sbs-sync-scroll toggle
     checkbox.  This differs per page. If we don't find one, simply
     elide that toggle and use whatever preference the user last
     specified (defaulting to on). */
  let cbSync /* scroll-sync checkbox */;
  let eToggleParent /* element to put the sync-scroll checkbox in */;
  const potentialParents = [ /* possible parents for the checkbox */
    /* Put the most likely pages at the end, as array.pop() is more
       efficient than array.shift() (see loop below). */
    /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
    /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
    /* /vdiff */ 'body.vdiff form div.submenu',
    /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu'
  ];
  while( potentialParents.length ){
    if( (eToggleParent = document.querySelector(potentialParents.pop())) ){
      break;
    }
  }
  const keySbsScroll = 'sync-diff-scroll' /* F.storage key */;
  if( eToggleParent ){
    /* Add a checkbox to toggle sbs scroll sync. Remember that in
       order to be UI-consistent in the /vdiff page we have to ensure
       that the checkbox is to the LEFT of of its label. We store the
       sync-scroll preference in F.storage (not a cookie) so that it
       persists across page loads and different apps. */
    cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
    D.append(eToggleParent, D.append(
      D.addClass(D.create('span'), 'input-with-label'),
      D.append(D.create('label'),
               cbSync, "Sync side-by-side scrolling?")
    ));
    cbSync.addEventListener('change', function(e){
      F.storage.set(keySbsScroll, e.target.checked);
    });
  }
  const useSync = cbSync ? ()=>cbSync.checked : ()=>F.storage.getBool(keySbsScroll,true);

  /* Now set up the events to enable syncronized scrolling... */
  const scrollLeft = function(event){
    const table = this.parentElement/*TD*/.parentElement/*TR*/.
      parentElement/*TBODY*/.parentElement/*TABLE*/;
    if( useSync() ){
      table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
    }
    return false;
  };
  const SCROLL_LEN = 64/* pixels to scroll for keyboard events */;
  const keycodes = Object.assign(Object.create(null),{
    37/*cursor left*/: -SCROLL_LEN, 39/*cursor right*/: SCROLL_LEN
  });
  /**
     Sets up synchronized scrolling of table.splitdiff element
     `diff`. If passed no argument, it scans the dom for elements to
     initialize. The second argument is for this function's own
     internal use.

     It's okay (but wasteful) to pass the same element to this
     function multiple times: it will only be set up for sync
     scrolling the first time it's passed to this function.

     Note that this setup is ignorant of the cbSync toggle: the toggle
     is checked when scrolling, not when initializing the sync-scroll
     capability.
  */
  const 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');




    diff.$txtPres.forEach(function(e){
      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){
          const len = keycodes[e.keyCode];
          if( !len ) return false;
          this.$txtPres[0].scrollLeft += len;
          return false;
        }, false);
      }
    }
    return this;
  }
  window.fossil.page.tweakSbsDiffs = function(){
    document.querySelectorAll('table.splitdiff').forEach((e)=>initTableDiff(e));
  };
  initTableDiff();
}, false);
Changes to src/info.c.
889
890
891
892
893
894
895
896

897
898
899
900
901
902
903
  }
  render_backlink_graph(zUuid,
       "<div class=\"section accordion\">References</div>\n");
  @ <div class="section accordion">Context</div><div class="accordion_panel">
  render_checkin_context(rid, 0, 0, 0);
  @ </div><div class="section accordion">Changes</div>
  @ <div class="accordion_panel">
  @ <div class="sectionmenu">

  pCfg = construct_diff_flags(diffType, &DCfg);
  DCfg.pRe = pRe;
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=0 ){
    @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
    @ Hide&nbsp;Diffs</a>
  }







|
>







889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
  }
  render_backlink_graph(zUuid,
       "<div class=\"section accordion\">References</div>\n");
  @ <div class="section accordion">Context</div><div class="accordion_panel">
  render_checkin_context(rid, 0, 0, 0);
  @ </div><div class="section accordion">Changes</div>
  @ <div class="accordion_panel">
  @ <div class="sectionmenu info-changes-menu">
  /* ^^^ .info-changes-menu is used by diff scroll sync */
  pCfg = construct_diff_flags(diffType, &DCfg);
  DCfg.pRe = pRe;
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=0 ){
    @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
    @ Hide&nbsp;Diffs</a>
  }