Fossil

Check-in [c03ce0f4d4]
Login

Check-in [c03ce0f4d4]

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

Overview
Comment:Re-enable side-by-side diff sync scrolling and add a client-side persistent toggle for the preference, accessible as a checkbox in various pages which render sbs diffs.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: c03ce0f4d43197a3932123d7b7bbe225e2da6e3a56ff13cbe38ec8ff64fe7d7d
User & Date: stephan 2024-09-03 11:48:40.364
References
2024-09-06
14:37
Add fossil.storage to the fossil.diff JS bundle dependencies, which should have been part of [c03ce0f4d43197a]. Resolves the report in [forum:3e1b1f82d1 | forum post 3e1b1f82d1] but does not explain why it worked for me locally before this fix (it should not have). Edit: it worked locally because my server was using jsmode:bundled. This fix makes it also work with jsmode:separate or jsmode:inline. ... (check-in: 59b383ae14 user: stephan tags: trunk)
Context
2024-09-03
11:55
Reuse scroll-related callbacks where possible instead of redefining them on a per-element basis. ... (check-in: ddeba72d13 user: stephan tags: trunk)
11:48
Re-enable side-by-side diff sync scrolling and add a client-side persistent toggle for the preference, accessible as a checkbox in various pages which render sbs diffs. ... (check-in: c03ce0f4d4 user: stephan tags: trunk)
11:45
Correct handling of keyboard-based diff scrolling. ... (Closed-Leaf check-in: 0ef89983b8 user: stephan tags: diff-scroll-sync)
09:47
In /setup_uinfo, correct the URL parameter passed from the 'edit' link to /setup_uedit: id instead of uid. ... (check-in: a84b669e52 user: stephan tags: trunk)
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.bootstrap.js.
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
  };
  /**
     repoUrl( repoRelativePath [,urlParams] )

     Creates a URL by prepending this.rootPath to the given path
     (which must be relative from the top of the site, without a
     leading slash). If urlParams is a string, it must be
     paramters encoded in the form "key=val&key2=val2..." WITHOUT
     a leading '?'. If it's an object, all of its properties get
     appended to the URL in that form.
  */
  F.repoUrl = function(path,urlParams){
    if(!urlParams) return this.rootPath+path;
    const url=[this.rootPath,path];
    url.push('?');







|







135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
  };
  /**
     repoUrl( repoRelativePath [,urlParams] )

     Creates a URL by prepending this.rootPath to the given path
     (which must be relative from the top of the site, without a
     leading slash). If urlParams is a string, it must be
     parameters encoded in the form "key=val&key2=val2..." WITHOUT
     a leading '?'. If it's an object, all of its properties get
     appended to the URL in that form.
  */
  F.repoUrl = function(path,urlParams){
    if(!urlParams) return this.rootPath+path;
    const url=[this.rootPath,path];
    url.push('?');
Changes to src/fossil.diff.js.
618
619
620
621
622
623
624




























































































































        new ChunkLoadControls(D.addClass(tr, 'jchunk'));
      });
    });
    return F;
  };
  Diff.setupDiffContextLoad();
});



































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
618
619
620
621
622
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
737
738
739
740
741
742
743
744
745
746
747
748
        new ChunkLoadControls(D.addClass(tr, 'jchunk'));
      });
    });
    return F;
  };
  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');
    if(!unifiedDiffs){
      diff.$txtPres.forEach(function(e){
        if(!e.classList.contains('scroller')){
          D.addClass(e, 'scroller');
          e.addEventListener('scroll', scrollLeft, false);
        }
      });
      diff.tabIndex = 0;
      if(!diff.classList.contains('scroller')){
        /* Keyboard-based scrolling requires special-case handling to
           ensure that we scroll the proper side of the diff when sync
           is off. */
        D.addClass(diff, 'scroller');
        diff.addEventListener('keydown', function(e){
          const len = keycodes[e.keyCode];
          if( !len ) return false;
          if( useSync() ){
            this.$txtPres[0].scrollLeft += len;
          }else if( diff.$preCurrent ){
            this.$preCurrent.scrollLeft += len;
          }
          return false;
        }, false);
        diff.$txtPres.forEach((e)=>{
          e.addEventListener('click', function(ev){
            diff.$preCurrent = this /* NOT ev.target, which is probably a child element */;
          }, false);
        })
      }
    }
    return this;
  }
  window.fossil.page.tweakSbsDiffs = function(){
    document.querySelectorAll('table.splitdiff').forEach((e)=>initTableDiff(e));
  };
  initTableDiff();
}, false);
Changes to src/fossil.page.fileedit.js.
1243
1244
1245
1246
1247
1248
1249

1250
1251
1252
1253
1254
1255
1256
        D.parseHtml(D.clearElement(target),[
          "<div>Diff <code>[",
          self.finfo.checkin,
          "]</code> &rarr; Local Edits</div>",
          c||'No changes.'
        ].join(''));
        F.diff.setupDiffContextLoad();

        F.message('Updated diff.');
        self.tabs.switchToTab(self.e.tabs.diff);
      }
    });
    return this;
  };








>







1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
        D.parseHtml(D.clearElement(target),[
          "<div>Diff <code>[",
          self.finfo.checkin,
          "]</code> &rarr; Local Edits</div>",
          c||'No changes.'
        ].join(''));
        F.diff.setupDiffContextLoad();
        if(sbs) P.tweakSbsDiffs();
        F.message('Updated diff.');
        self.tabs.switchToTab(self.e.tabs.diff);
      }
    });
    return this;
  };

Changes to src/fossil.page.wikiedit.js.
1543
1544
1545
1546
1547
1548
1549

1550
1551
1552
1553
1554
1555
1556
        D.parseHtml(D.clearElement(target), [
          "<div>Diff <code>[",
          self.winfo.name,
          "]</code> &rarr; Local Edits</div>",
          c||'No changes.'
        ].join(''));
        F.diff.setupDiffContextLoad();

        F.message('Updated diff.');
        self.tabs.switchToTab(self.e.tabs.diff);
      }
    });
    return this;
  };








>







1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
        D.parseHtml(D.clearElement(target), [
          "<div>Diff <code>[",
          self.winfo.name,
          "]</code> &rarr; Local Edits</div>",
          c||'No changes.'
        ].join(''));
        F.diff.setupDiffContextLoad();
        if(sbs) P.tweakSbsDiffs();
        F.message('Updated diff.');
        self.tabs.switchToTab(self.e.tabs.diff);
      }
    });
    return this;
  };

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>
  }