Fossil

Artifact [2344d0aff8]
Login

Artifact [2344d0aff8]

Artifact 2344d0aff89fbb0893ba417d2b2ab42eb71f33c36f13aaf448bb66deac84f93b:


(function(F/*the fossil object*/){
  "use strict";
  /**
     Client-side implementation of the /pikchrshow app. Requires that
     the fossil JS bootstrapping is complete and that these fossil JS
     APIs have been installed: fossil.fetch, fossil.dom,
     fossil.copybutton
  */
  const E = (s)=>document.querySelector(s),
        D = F.dom,
        P = F.page;

  P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown,
                      2==pikchr text fossil, 3==raw SVG. */
  P.response = {/*stashed state for the server's preview response*/
    isError: false,
    inputText: undefined /* value of the editor field at render-time */,
    raw: undefined /* raw response text/HTML from server */
  };
  F.onPageLoad(function() {
    document.body.classList.add('pikchrshow');
    P.e = { /* various DOM elements we work with... */
      previewTarget: E('#pikchrshow-output'),
      previewModeLabel: E('#pikchrshow-output-wrapper > legend'),
      btnCopy: E('#pikchrshow-output-wrapper > legend > .copy-button'),
      btnSubmit: E('#pikchr-submit-preview'),
      cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'),
      taContent: E('#content'),
      taPreviewText: D.attr(D.textarea(), 'rows', 20, 'cols', 60,
                            'readonly', true),
      divControls: E('#pikchrshow-controls'),
      btnTogglePreviewMode: D.button("Preview mode"),
      selectMarkupAlignment: D.select()
    };
    D.append(P.e.divControls, P.e.btnTogglePreviewMode);

    // Setup markup alignment selection...
    D.append(P.e.divControls, P.e.selectMarkupAlignment);
    D.disable(D.option(P.e.selectMarkupAlignment, '', 'Markup Alignment'));
    ['left', 'center'].forEach(function(val,ndx){
      D.option(P.e.selectMarkupAlignment, ndx ? val : '', val);
    });

    // Setup clipboard-copy of markup/SVG...
    F.copyButton(P.e.btnCopy, {copyFromElement: P.e.taPreviewText});
    P.e.btnCopy.addEventListener('text-copied',function(ev){
       D.flashOnce(ev.target);
    },false);

    // Set up dark mode simulator...
    P.e.cbDarkMode.addEventListener('change', function(ev){
      if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
      else D.removeClass(P.e.previewTarget, 'dark-mode');
    }, false);
    if(P.e.cbDarkMode.checked) D.addClass(P.e.previewTarget, 'dark-mode');

    // Set up preview update and preview mode toggle...
    P.e.btnSubmit.addEventListener('click', ()=>P.preview(), false);
    P.e.btnTogglePreviewMode.addEventListener('click', function(){
      /* Rotate through the 4 available preview modes */
      P.previewMode = ++P.previewMode % 4;
      P.renderPreview();
    }, false);
    P.e.selectMarkupAlignment.addEventListener('change', function(ev){
      /* Update markdown/fossil wiki preview if it's active */
      if(P.previewMode==1 || P.previewMode==2){
        P.renderPreview();
      }
    }, false);

    if(P.e.taContent.value/*was pre-filled server-side*/){
      /* Fill our "response" state so that renderPreview() can work */
      P.response.inputText = P.e.taContent.value;
      P.response.raw = P.e.previewTarget.innerHTML;
      P.renderPreview()/*not strictly necessary, but gets all
                         labels/headers in alignment.*/;
    }    
  }/*F.onPageLoad()*/);

  /**
     Updates the preview view based on the current preview mode and
     error state.
  */
  P.renderPreview = function f(){
    if(!f.hasOwnProperty('rxNonce')){
      f.rxNonce = /<!--.+-->\r?\n?/g /*nonce comments*/;
    }
    const preTgt = this.e.previewTarget;
    if(this.response.isError){
      preTgt.innerHTML = this.response.raw;
      D.addClass(preTgt, 'error');
      this.e.previewModeLabel.innerText = "Error";
      return;
    }
    D.removeClass(preTgt, 'error');
    D.enable(this.e.btnTogglePreviewMode);
    let label;
    switch(this.previewMode){
    case 0:
      label = "Rendered SVG";
      preTgt.innerHTML = this.response.raw;
      this.e.taPreviewText.value = this.response.raw.replace(f.rxNonce, '')/*for copy button*/;
      break;
    case 1:
      label = "Markdown";
      this.e.taPreviewText.value = [
        '```pikchr'+(this.e.selectMarkupAlignment.value
                     ? ' '+this.e.selectMarkupAlignment.value : ''),
        this.response.inputText, '```'
      ].join('\n');
      D.append(D.clearElement(preTgt), this.e.taPreviewText);
      break;
    case 2:
      label = "Fossil wiki";
      this.e.taPreviewText.value = [
        '<verbatim type="pikchr',
        this.e.selectMarkupAlignment.value ? ' '+this.e.selectMarkupAlignment.value : '',
        '">', this.response.inputText, '</verbatim>'
      ].join('');
      D.append(D.clearElement(preTgt), this.e.taPreviewText);
      break;
    case 3:
      label = "Raw SVG";
      this.e.taPreviewText.value = this.response.raw.replace(f.rxNonce, '');
      D.append(D.clearElement(preTgt), this.e.taPreviewText);
      break;
    }
    D.append(D.clearElement(this.e.previewModeLabel),
             label, this.e.btnCopy);
  };

  /** Fetches the preview from the server and updates the preview to
      the rendered SVG content or error report. */
  P.preview = function fp(){
    if(!fp.hasOwnProperty('toDisable')){
      fp.toDisable = [
        /* input elements to disable during ajax operations */
        this.e.btnSubmit, this.e.taContent,
        this.e.selectMarkupAlignment
        /* this.e.btnTogglePreviewMode is handled separately */
      ];
      fp.target = this.e.previewTarget;
      fp.updateView = function(c,isError){
        P.previewMode = 0;
        P.response.raw = c;
        P.response.isError = isError;
        D.enable(fp.toDisable);
        P.renderPreview();
      };
    }
    D.disable(fp.toDisable, this.e.btnTogglePreviewMode);
    const content = this.e.taContent.value.trim();
    this.response.raw = undefined;
    this.response.inputText = content;
    if(!content){
      fp.updateView("No pikchr content!",true);
      return this;
    }
    const self = this;
    const fd = new FormData();
    fd.append('ajax', true);
    fd.append('content',content);
    F.message(
      "Fetching preview..."
    ).fetch('pikchrshow',{
      payload: fd,
      responseHeaders: 'x-pikchrshow-is-error',
      onload: (r,header)=>{
        fp.updateView(r,+header ? true : false);
        F.message('Updated preview.');
      },
      onerror: (e)=>{
        F.fetch.onerror(e);
        fp.updateView("Error fetching preview: "+e, true);
      }
    });
    return this;
  }/*preview()*/;

})(window.fossil);