Fossil

Artifact [e8f31eec28]
Login

Artifact [e8f31eec28]

Artifact e8f31eec2897ddb5c9c4bbb01ebacc72592e4b21bd49d60100d1d38f5d03bd05:


/**
   JS code for link-tester.html. We cannot host this JS inline in that
   file because fossil's default Content Security Policy won't let it
   run that way.
*/
window.addEventListener("DOMContentLoaded", function(){
  const E = function(s){
    const e = document.querySelector(s);
    if( !e ) throw new Error("Missing element: "+s);
    return e;
  };
  const EAll = function(s){
    const e = document.querySelectorAll(s);
    if( !e || !e.length ) throw new Error("Missing elements: "+s);
    return e;
  };
  const eIframe = E('#iframe');
  const eSelect = E('#selectPage');
  eSelect.addEventListener('change',function(ev){
    const so = ev.target.options[ev.target.selectedIndex];
    if( so ){
      eIframe.setAttribute('src', so.value || so.innerText);
    }
  });

  /*
    Prepend the fossil instance's URL to each link so that this script
    works when run from a non-localhost "fossil ui/server" instance.
    We _assume_ that this script is run from /uv or /ext, with no
    subsequent dirs after /uv or /ext. To run from deeper levels the
    following logic needs to be adjusted to guess where the fossil
    instance's CGI script is.
  */
  let top = (''+window.location).split('/');
  top.pop(); // this file name
  top.pop(); // parent dir
  /* We're hopefully now at the top-most fossil-served
     URL. */
  top = top.join('/');
  for( const o of eSelect.options ){
    o.value = top + (o.value || o.innerText);
  }

  const eBtnPrev = E('#btn-prev');
  const eBtnNext = E('#btn-next');

  const cycleLink = function(dir){
    let n = eSelect.selectedIndex + dir;
    if( n < 0 ) n = eSelect.options.length-1;
    else if( n>=eSelect.options.length ){
      n = 0;
    }
    if( n>=0 ){
      eSelect.selectedIndex = n;
    }
    eSelect.dispatchEvent(new Event('change',{target:eSelect}));
  };

  eBtnPrev.addEventListener('click', ()=>cycleLink(-1), false);
  eBtnNext.addEventListener('click', ()=>cycleLink(1), false);

  /**
     We have to adjust the iframe's size dynamically to account for
     other widgets around it. iframes don't simply like to fill up all
     available space without some help. If #controls only contained
     the one SELECT element, CSS would be sufficient, but once we add
     text around it, #controls's size becomes unpredictable and we
     need JS to calculate it. We do this every time the window size
     changes.
  */
  // Copied from fossil.dom.js
  const effectiveHeight = function f(e){
    if(!e) return 0;
    if(!f.measure){
      f.measure = function callee(e, depth){
        if(!e) return;
        const m = e.getBoundingClientRect();
        if(0===depth){
          callee.top = m.top;
          callee.bottom = m.bottom;
        }else{
          callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
          callee.bottom = Math.max(callee.bottom, m.bottom);
        }
        Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
        if(0===depth){
          //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
          f.extra += callee.bottom - callee.top;
        }
        return f.extra;
      };
    }
    f.extra = 0;
    f.measure(e,0);
    return f.extra;
  };

  // Copied from fossil.bootstrap.js
  const debounce = function f(func, waitMs, immediate) {
    var timeoutId;
    if(!waitMs) waitMs = f.$defaultDelay;
    return function() {
      const context = this, args = Array.prototype.slice.call(arguments);
      const later = function() {
        timeoutId = undefined;
        if(!immediate) func.apply(context, args);
      };
      const callNow = immediate && !timeoutId;
      clearTimeout(timeoutId);
      timeoutId = setTimeout(later, waitMs);
      if(callNow) func.apply(context, args);
    };
  };

  const ForceResizeKludge = (function(eToAvoid, eConstrained){
    const resized = function f(){
      if( f.$disabled ) return;
      const wh = window.innerHeight;
      let ht;
      let extra = 0;
      eToAvoid.forEach((e)=>e ? extra += effectiveHeight(e) : false);
      ht = wh - extra;
      if( ht < 100 ) ht = 100;
      eConstrained.style.top = 'calc('+extra+'px + 0.25em)';
      eConstrained.style.height =
        eConstrained.style.maxHeight = [
          "calc(", ht, "px",
          " - 0.65em"/*fudge value*/,")"
          /* ^^^^ hypothetically not needed, but both Chrome/FF on
             Linux will force scrollbars on the body if this value is
             too small; current value is empirically selected. */
        ].join('');
    };
    resized.$disabled = true/* gets deleted later */;
    window.addEventListener('resize', debounce(resized, 250), false);
    return resized;
  })(
    EAll('body > *:not(iframe)'),
    eIframe
  );

  delete ForceResizeKludge.$disabled;
  ForceResizeKludge();

  eSelect.dispatchEvent(new Event('change',{target:eSelect}));
});