Fossil

fossil.pikchr.js at tip
Login

fossil.pikchr.js at tip

File src/fossil.pikchr.js from the latest check-in


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
   100
   101
   102
   103
   104
   105
   106
   107
   108
   109
   110
   111
   112
   113
   114
   115
   116
   117
   118
   119
   120
   121
   122
   123
   124
   125
   126
   127
   128
   129
   130
   131
   132
   133
(function(F/*window.fossil object*/){
  "use strict";
  const D = F.dom, P = F.pikchr = {};

  /**
     Initializes pikchr-rendered elements with the ability to
     toggle between their SVG and source code.

     The first argument may be any of:

     - A single SVG.pikchr element.

     - A collection (with a forEach method) of such elements.

     - A CSS selector string for one or more such elements.

     - An array of such strings.

     Passing no value is equivalent to passing 'svg.pikchr'.

     For each SVG in the resulting set, this function sets up event
     handlers which allow the user to toggle the SVG between image and
     source code modes. The image will switch modes in response to
     cltr-click and, if its *parent* element has the "toggle" CSS
     class, it will also switch modes in response to single-click.

     If the parent element has the "source" CSS class, the image
     starts off with its source code visible and the image hidden,
     instead of the default of the other way around.

     Returns this object.

     Each element will only be processed once by this routine, even if
     it is passed to this function multiple times. Each processed
     element gets a "data" attribute set to it to indicate that it was
     already dealt with.

     This code expects the following structure around the SVGs, and
     will not process any which don't match this:

     <DIV.pikchr-wrapper>
       <DIV.pikchr-svg><SVG.pikchr></SVG></DIV>
       <DIV.pikchr-src>
         <PRE>pikchr source code</PRE>
         <SPAN class='hidden'><A>link to open pikchr in /pikchrshow</A></SPAN>
       </DIV>
     </DIV>
  */
  P.addSrcView = function f(svg){
    if(!f.hasOwnProperty('parentClick')){
      f.parentClick = function(ev){
        if(ev.altKey || ev.metaKey || ev.ctrlKey
           /* Every combination of special key (alt, shift, ctrl,
              meta) is handled differently everywhere. Shift is used
              by the browser, Ctrl doesn't work on an iMac, and Alt is
              intercepted by most Linux window managers to control
              window movement! So...  we just listen for *any* of them
              (except Shift) and the user will need to find one which
              works on on their environment. */
           || this.classList.contains('toggle')){
          this.classList.toggle('source');
          ev.stopPropagation();
          ev.preventDefault();
        }
      };
      /**
         Event handler for the "open in pikchrshow" links: store the
         source code for the link's pikchr in
         window.sessionStorage['pikchr-xfer'] then open
         /pikchrshow?fromSession to trigger loading of that pikchr.
      */
      f.clickPikchrShow = function(ev){
        const pId = this.dataset['pikchrid'] /* ID of the associated pikchr source code element */;
        if(!pId) return;
        const ePikchr = this.parentNode.parentNode.querySelector('#'+pId);
        if(!ePikchr) return;
        ev.stopPropagation() /* keep pikchr source view from toggling */;
        window.sessionStorage.setItem('pikchr-xfer', ePikchr.innerText);
        /*
          After returning from this function the link element will
          open [/pikchrshow?fromSession], and pikchrshow will extract
          the pikchr source code from sessionStorage['pikchr-xfer'].

          Quirks of this ^^^ design:

          We use only a single slot in sessionStorage. We could
          alternately use a key like pikchr-$pId and pass that key on
          to /pikchrshow via fromSession=pikchr-$pId, but that would
          eventually lead to stale session entries if loading of
          pikchrshow were interrupted at an untimely point. The
          down-side of _not_ doing that is that some user (or
          automation) options multiple "open in pikchrshow" links
          rapidly enough, the will open the same pikchr (the one which
          was stored in the session's slot most recently).  The
          current approach should be fine for normal human interaction
          speeds, but if it proves to be a problem we can instead use
          the above-described approach of storing each pikchr in its
          own session slot and simply accept that there may be stale
          entries at some point.
        */
      };
    };
    if(!svg) svg = 'svg.pikchr';
    if('string' === typeof svg){
      document.querySelectorAll(svg).forEach((e)=>f.call(this, e));
      return this;
    }else if(svg.forEach){
      svg.forEach((e)=>f.call(this, e));
      return this;
    }
    if(svg.dataset.pikchrProcessed){
      return this;
    }
    svg.dataset.pikchrProcessed = 1;
    const parent = svg.parentNode.parentNode /* outermost DIV.pikchr-wrapper */;
    const srcView = parent ? svg.parentNode.nextElementSibling /* DIV.pikchr-src */ : undefined;
    if(srcView && srcView.classList.contains('pikchr-src')){
      /* Without this element, there's nothing for us to do here. */
      parent.addEventListener('click', f.parentClick, false);
      const eSpan = window.sessionStorage
            ? srcView.querySelector('span') /* "open in..." link wrapper */
            : undefined;
      if(eSpan){
        const openLink = eSpan.querySelector('a');
        if(openLink){
          openLink.addEventListener('click', f.clickPikchrShow, false);
          eSpan.classList.remove('hidden');
        }
      }
    }
    return this;
  };
})(window.fossil);