Fossil

Check-in [3f08a9d200]
Login

Check-in [3f08a9d200]

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

Overview
Comment:Lots of tweaking to the "help buttonlet" popup position. Something to improve some rainy day.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | misc-js-experiments
Files: files | file ages | folders
SHA3-256: 3f08a9d200fb9c8b035a96dac2eecb8d61c8585d3c9a8515a5c7183f7562b864
User & Date: stephan 2020-08-25 06:18:18.189
Context
2020-08-25
07:00
The fossil.XYZ.js-using pages now include all of those APIs when running in bundled JS mode, as that provides far lower aggregate over-the-wire and HTTP request counts. Added ? popup help buttons in wikiedit/fileedit to replace title-attribute hoverhelp (popup positioning can still be improved, though). ... (check-in: 34f7fd72c6 user: stephan tags: trunk)
06:18
Lots of tweaking to the "help buttonlet" popup position. Something to improve some rainy day. ... (Closed-Leaf check-in: 3f08a9d200 user: stephan tags: misc-js-experiments)
2020-08-24
22:46
Improved cross-page caching of wikiedit/fileedit bundle, reducing those pages to a single request of 10-13k once cache is warm. Fixed non-bundled JS distribution of fileedit and wikiedit. ... (check-in: 20c50cd1e5 user: stephan tags: misc-js-experiments)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
1344
1345
1346
1347
1348
1349
1350

1351
   selector here to be certain that this class's style overrides
   that of fossil-tooltip.
*/
.fossil-tooltip.help-buttonlet-content {
  cursor: default;
  text-align: left;
  border-style: outset;

}







>

1344
1345
1346
1347
1348
1349
1350
1351
1352
   selector here to be certain that this class's style overrides
   that of fossil-tooltip.
*/
.fossil-tooltip.help-buttonlet-content {
  cursor: default;
  text-align: left;
  border-style: outset;
  box-shadow: 0em 0em 0.2em 0.2em rgba(0,0,0,0.40);
}
Changes to src/fossil.page.fileedit.js.
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
      const sel = this.e.select = D.select();
      const btnClear = this.e.btnClear = D.button("Discard Edits"),
            btnHelp = D.append(
              D.addClass(D.div(), "help-buttonlet"),
              'Locally-edited files. Timestamps are the last local edit time. ',
              'Only the ',P.config.defaultMaxStashSize,' most recent files ',
              'are retained. Saving or reloading a file removes it from this list. ',
              D.append(D.code(),'localStorage'),' uses browser-local persistent storage. ',
              D.append(D.code(),'sessionStorage'),' uses storage local to this browser tab.'
            );

      D.append(wrapper, "Local edits (",
               D.append(D.code(),
                        F.storage.storageImplName()),
               "):",
               btnHelp, sel, btnClear);







|
|







468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
      const sel = this.e.select = D.select();
      const btnClear = this.e.btnClear = D.button("Discard Edits"),
            btnHelp = D.append(
              D.addClass(D.div(), "help-buttonlet"),
              'Locally-edited files. Timestamps are the last local edit time. ',
              'Only the ',P.config.defaultMaxStashSize,' most recent files ',
              'are retained. Saving or reloading a file removes it from this list. ',
              D.append(D.code(),F.storage.storageImplName()),
              ' = ',F.storage.storageHelpDescription()
            );

      D.append(wrapper, "Local edits (",
               D.append(D.code(),
                        F.storage.storageImplName()),
               "):",
               btnHelp, sel, btnClear);
Changes to src/fossil.page.wikiedit.js.
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
      const sel = this.e.select = D.select(),
            btnClear = this.e.btnClear = D.button("Discard Edits"),
            btnHelp = D.append(
              D.addClass(D.div(), "help-buttonlet"),
              'Locally-edited wiki pages. Timestamps are the last local edit time. ',
              'Only the ',P.config.defaultMaxStashSize,' most recent pages ',
              'are retained. Saving or reloading a file removes it from this list. ',
              D.append(D.code(),'localStorage'),' uses browser-local persistent storage. ',
              D.append(D.code(),'sessionStorage'),' uses storage local to this browser tab.'
            );
      D.append(wrapper, "Local edits (",
               D.append(D.code(),
                        F.storage.storageImplName()),
               "):",
               btnHelp, sel, btnClear);
      F.helpButtonlets.setup(btnHelp);







|
|







692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
      const sel = this.e.select = D.select(),
            btnClear = this.e.btnClear = D.button("Discard Edits"),
            btnHelp = D.append(
              D.addClass(D.div(), "help-buttonlet"),
              'Locally-edited wiki pages. Timestamps are the last local edit time. ',
              'Only the ',P.config.defaultMaxStashSize,' most recent pages ',
              'are retained. Saving or reloading a file removes it from this list. ',
              D.append(D.code(),F.storage.storageImplName()),
              ' = ',F.storage.storageHelpDescription()
            );
      D.append(wrapper, "Local edits (",
               D.append(D.code(),
                        F.storage.storageImplName()),
               "):",
               btnHelp, sel, btnClear);
      F.helpButtonlets.setup(btnHelp);
Changes to src/fossil.popupwidget.js.
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317










318


319








320

321

322
323











324

325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

       All child nodes of a help buttonlet are removed from the button
       during initialization and stashed away for use in a PopupWidget
       when the botton is clicked.

    */
    setup: function f(){
      if(!f.clickHandler){
        f.clickHandler = function fch(ev){
          if(!fch.popup){
            fch.popup = new F.PopupWidget({
              cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
              refresh: function(){
              }
            });

            const hide = ()=>fch.popup.hide();
            fch.popup.e.addEventListener('click', hide, false);
            document.body.addEventListener('click', hide, true);
            document.body.addEventListener('keydown', function(ev){
              if(fch.popup.isShown() && 27===ev.which){
                fch.popup.hide();
              }
            }, true);
          }
          D.append(D.clearElement(fch.popup.e), ev.target.$helpContent);
          const rect1 = ev.target.getClientRects()[0];
          var x = rect1.left, y = rect1.top;
          if(x<0) x = 0;
          if(y<0) y = 0;
          /* shift help to the left 1/2 the width of fch.popup.e. However,
             fch.popup.e.getClientRects() is empty until the popup is shown,
             so we have to show it, calculate that size, then move it. */










          fch.popup.show(x, y);


          const rect2 = fch.popup.e.getClientRects()[0];








          x -= rect2.width/2;

          if(x<0) x = 0;

          fch.popup.show(x, y);
        };











      }

      var elems;
      if(!arguments.length){
        arguments[0] = '.help-buttonlet:not(.processed)';
        arguments.length = 1;
      }
      if(arguments.length){
        if('string'===typeof arguments[0]){
          elems = document.querySelectorAll(arguments[0]);
        }else if(arguments[0] instanceof HTMLElement){
          elems = [arguments[0]];
        }else{/* assume DOM element list or array */
          elems = arguments[0];
        }
      }
      if(!elems) return;
      elems.forEach(function(e){
        if(e.classList.contains('processed')) return;
        e.classList.add('processed');
        e.$helpContent = [];
        /* We have to move all child nodes out of the way because we
           cannot hide TEXT nodes via CSS (which cannot select TEXT
           nodes). We have to do it in two steps to avoid invaliding
           the list during traversal. */
        e.childNodes.forEach((ch)=>e.$helpContent.push(ch));
        e.$helpContent.forEach((ch)=>ch.remove());
        e.addEventListener('click', f.clickHandler, false);
      });
    },
    
    /**
       Sets up the given element as a "help buttonlet", adding the CSS
       class help-buttonlet to it. Any (optional) arguments after the
       first are appended to the element using fossil.dom.append(), so
       that they become the content for the buttonlet's popup help.







|







>










|
|


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

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

>


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










|



<
|
<
<
<
<
<
<
<
<
<
<
<







286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

374











375
376
377
378
379
380
381

       All child nodes of a help buttonlet are removed from the button
       during initialization and stashed away for use in a PopupWidget
       when the botton is clicked.

    */
    setup: function f(){
      if(!f.hasOwnProperty('clickHandler')){
        f.clickHandler = function fch(ev){
          if(!fch.popup){
            fch.popup = new F.PopupWidget({
              cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
              refresh: function(){
              }
            });
            fch.popup.e.style.maxWidth = '80%'/*of body*/;
            const hide = ()=>fch.popup.hide();
            fch.popup.e.addEventListener('click', hide, false);
            document.body.addEventListener('click', hide, true);
            document.body.addEventListener('keydown', function(ev){
              if(fch.popup.isShown() && 27===ev.which){
                fch.popup.hide();
              }
            }, true);
          }
          D.append(D.clearElement(fch.popup.e), ev.target.$helpContent);
          var popupRect = ev.target.getClientRects()[0];
          var x = popupRect.left, y = popupRect.top;
          if(x<0) x = 0;
          if(y<0) y = 0;
          /* Shift the help around a bit to "better" fit the
             screen. However, fch.popup.e.getClientRects() is empty
             until the popup is shown, so we have to show it,
             calculate the resulting size, then move and/or resize it.

             This algorithm/these heuristics can certainly be improved
             upon. Just be careful to mess only with the X coordinate
             and the width. The browser will try to keep the widget
             from being truncated off-screen on the right, shifting it
             to the left if needed, and we cannot generically be sure
             that an enforced fully on-screen size will actually fit
             the current help text.
          */
          fch.popup.show(x, y);
          x = popupRect.left, y = popupRect.top;
          popupRect = fch.popup.e.getBoundingClientRect();
          const rectBody = document.body.getClientRects()[0];
          if(popupRect.right > rectBody.right){
            x -= (popupRect.right - rectBody.right);
          }
          if(x + popupRect.width > rectBody.right){
            x = rectBody.x + (rectBody.width*0.1);
            fch.popup.e.style.minWidth = '70%';
          }else{
            fch.popup.e.style.removeProperty('min-width');
            x -= popupRect.width/2;
          }
          if(x<0) x = 0;
          //console.debug("dimensions",x,y, popupRect, rectBody);
          fch.popup.show(x, y);
        };
        f.foreachElement = function(e){
          if(e.classList.contains('processed')) return;
          e.classList.add('processed');
          e.$helpContent = [];
          /* We have to move all child nodes out of the way because we
             cannot hide TEXT nodes via CSS (which cannot select TEXT
             nodes). We have to do it in two steps to avoid invaliding
             the list during traversal. */
          e.childNodes.forEach((ch)=>e.$helpContent.push(ch));
          e.$helpContent.forEach((ch)=>ch.remove());
          e.addEventListener('click', f.clickHandler, false);
        };
      }/*static init*/
      var elems;
      if(!arguments.length){
        arguments[0] = '.help-buttonlet:not(.processed)';
        arguments.length = 1;
      }
      if(arguments.length){
        if('string'===typeof arguments[0]){
          elems = document.querySelectorAll(arguments[0]);
        }else if(arguments[0] instanceof HTMLElement){
          elems = [arguments[0]];
        }else if(arguments[0].forEach){/* assume DOM element list or array */
          elems = arguments[0];
        }
      }

      if(elems) elems.forEach(f.foreachElement);











    },
    
    /**
       Sets up the given element as a "help buttonlet", adding the CSS
       class help-buttonlet to it. Any (optional) arguments after the
       first are appended to the element using fossil.dom.append(), so
       that they become the content for the buttonlet's popup help.
Changes to src/fossil.storage.js.
133
134
135
136
137
138
139















140
141
142
143
        and sessionStorage are unavailable. */
    isTransient: ()=>$storageHolder!==$storage,
    /** Returns a symbolic name for the current storage mechanism. */
    storageImplName: function(){
      if($storage===window.localStorage) return 'localStorage';
      else if($storage===window.sessionStorage) return 'sessionStorage';
      else return 'transient';















    }
  };

})(window.fossil);







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




133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
        and sessionStorage are unavailable. */
    isTransient: ()=>$storageHolder!==$storage,
    /** Returns a symbolic name for the current storage mechanism. */
    storageImplName: function(){
      if($storage===window.localStorage) return 'localStorage';
      else if($storage===window.sessionStorage) return 'sessionStorage';
      else return 'transient';
    },

    /**
       Returns a brief help text string for the currently-selected
       storage type.
    */
    storageHelpDescription: function(){
      return {
        localStorage: "Browser-local persistent storage with an "+
          "unspecified long-term lifetime (survives closing the browser, "+
          "but maybe not a browser upgrade).",
        sessionStorage: "Storage local to this browser tab, "+
          "lost if this tab is closed.",
        "transient": "Transient storage local to this invocation of this page."
      }[this.storageImplName()];
    }
  };

})(window.fossil);