Fossil

Diff
Login

Differences From Artifact [3e9725ffae]:

To Artifact [d3ae252cb5]:


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

  This is the main entry point for the WASM rendition of fossil's
  /pikchrshow app. It sets up the various UI bits, loads a Worker for
  the pikchr process, and manages the communication between the UI and
  worker.

  API dependencies: fossil.dom, fossil.storage
*/
(function(F/*fossil object*/){
  'use strict';

  /* Recall that the 'self' symbol, except where locally
     overwritten, refers to the global window or worker object. */


  /** Name of the stored copy of this app's config. */
  const configStorageKey = 'pikchrshow-config';






  /**




     The PikchrFiddle object is intended to be the primary app-level
     object for the main-thread side of the fiddle application. It
     uses a worker thread to load the WASM module and communicate
     with it.
  */
  const PS/*local convenience alias*/ = F.PikchrShow/*canonical name*/ = {
    /* Config options. */
    config: {
      /* If true, display input/output areas side-by-side, else stack
         them vertically. */
      sideBySide: true,
      /* If true, swap positions of the input/output areas. */
      swapInOut: false,
      /* If true, the SVG is allowed to resize to fit the parent
         content area, else the parent is resized to fit the rendered
         SVG (as sized by pikchr). */
      renderAutoScale: false,
      /* If true, automatically render while the user is typing. */
      renderWhileTyping: false
    },










    renderMode: 'html'/*one of: 'text','html'*/,


    _msgMap: {},
    /** Adds a worker message handler for messages of the given
        type. */
    addMsgHandler: function f(type,callback){
      if(Array.isArray(type)){
        type.forEach((t)=>this.addMsgHandler(t, callback));
        return this;







|







>



>
>
>
>
>
|
>
>
>
>
|
|
<
<
<
|










|



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







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

  This is the main entry point for the WASM rendition of fossil's
  /pikchrshow app. It sets up the various UI bits, loads a Worker for
  the pikchr process, and manages the communication between the UI and
  worker.

  API dependencies: fossil.dom, fossil.copybutton, fossil.storage
*/
(function(F/*fossil object*/){
  'use strict';

  /* Recall that the 'self' symbol, except where locally
     overwritten, refers to the global window or worker object. */

  const D = F.dom;
  /** Name of the stored copy of this app's config. */
  const configStorageKey = 'pikchrshow-config';

  /* querySelectorAll() proxy */
  const EAll = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelectorAll(arguments[arguments.length-1]);
  };
  /* querySelector() proxy */
  const E = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelector(arguments[arguments.length-1]);
  };

  /** The main application object. */



  const PS = {
    /* Config options. */
    config: {
      /* If true, display input/output areas side-by-side, else stack
         them vertically. */
      sideBySide: true,
      /* If true, swap positions of the input/output areas. */
      swapInOut: false,
      /* If true, the SVG is allowed to resize to fit the parent
         content area, else the parent is resized to fit the rendered
         SVG (as sized by pikchr). */
      renderAutofit: false,
      /* If true, automatically render while the user is typing. */
      renderWhileTyping: false
    },
    /* Various DOM elements. */
    e: {
      previewCopyButton: E('#preview-copy-button'),
      previewModeLabel: E('label[for=preview-copy-button]'),
      zoneOutputButtons: E('.zone-wrapper.output > legend > .button-bar'),
      outText: E('#pikchr-output-text'),
      pikOutWrapper: E('#pikchr-output-wrapper'),
      pikOut: E('#pikchr-output')
    },
    renderModes: ['svg'/*SVG must be at index 0*/,'markdown', 'wiki', 'text'],
    renderModeLabels: {
      svg: 'SVG', markdown: 'Markdown', wiki: 'Fossil Wiki', text: 'Text'
    },
    _msgMap: {},
    /** Adds a worker message handler for messages of the given
        type. */
    addMsgHandler: function f(type,callback){
      if(Array.isArray(type)){
        type.forEach((t)=>this.addMsgHandler(t, callback));
        return this;
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
      return this;
    },
    /** Stores this object's config in the browser's storage. */
    storeConfig: function(){
      F.storage.setJSON(configStorageKey,this.config);
    }
  };

  PS._config = F.storage.getJSON(configStorageKey);
  if(PS._config){
    /* Copy all properties to PS.config which are currently in
       PS._config. We don't bother copying any other properties: those
       would be stale/removed config entries. */
    Object.keys(PS.config).forEach(function(k){
      if(PS._config.hasOwnProperty(k)){
        PS.config[k] = PS._config[k];
      }
    });
    delete PS._config;
  }

  PS.worker = new Worker('builtin/extsrc/pikchr-worker.js');
  PS.worker.onmessage = (ev)=>PS.runMsgHandlers(ev.data);
  PS.addMsgHandler('stdout', console.log.bind(console));
  PS.addMsgHandler('stderr', console.error.bind(console));

  /* querySelectorAll() proxy */
  const EAll = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelectorAll(arguments[arguments.length-1]);
  };
  /* querySelector() proxy */
  const E = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelector(arguments[arguments.length-1]);
  };

  /** Handles status updates from the Module object. */
  PS.addMsgHandler('module', function f(ev){
    ev = ev.data;
    if('status'!==ev.type){
      console.warn("Unexpected module-type message:",ev);
      return;
    }







|


















<
<
<
<
<
<
<
<
<
<
<







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
134
      return this;
    },
    /** Stores this object's config in the browser's storage. */
    storeConfig: function(){
      F.storage.setJSON(configStorageKey,this.config);
    }
  };
  PS.renderModes.selectedIndex = 0;
  PS._config = F.storage.getJSON(configStorageKey);
  if(PS._config){
    /* Copy all properties to PS.config which are currently in
       PS._config. We don't bother copying any other properties: those
       would be stale/removed config entries. */
    Object.keys(PS.config).forEach(function(k){
      if(PS._config.hasOwnProperty(k)){
        PS.config[k] = PS._config[k];
      }
    });
    delete PS._config;
  }

  PS.worker = new Worker('builtin/extsrc/pikchr-worker.js');
  PS.worker.onmessage = (ev)=>PS.runMsgHandlers(ev.data);
  PS.addMsgHandler('stdout', console.log.bind(console));
  PS.addMsgHandler('stderr', console.error.bind(console));












  /** Handles status updates from the Module object. */
  PS.addMsgHandler('module', function f(ev){
    ev = ev.data;
    if('status'!==ev.type){
      console.warn("Unexpected module-type message:",ev);
      return;
    }
154
155
156
157
158
159
160



161
162
163
164
165
166
167
      /* The module can post messages about fatal problems,
         e.g. an exit() being triggered or assertion failure,
         after the last "load" message has arrived, so
         leave f.ui.status and message listener intact. */
    }
  });




  /**
     The 'pikchrshow-ready' event is fired (with no payload) when the
     wasm module has finished loading. */
  PS.addMsgHandler('pikchrshow-ready', function(){
    PS.clearMsgHandlers('pikchrshow-ready');
    F.page.onPikchrshowLoaded();
  });







>
>
>







162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
      /* The module can post messages about fatal problems,
         e.g. an exit() being triggered or assertion failure,
         after the last "load" message has arrived, so
         leave f.ui.status and message listener intact. */
    }
  });

  PS.e.previewModeLabel.innerText =
    PS.renderModeLabels[PS.renderModes[PS.renderModes.selectedIndex]];

  /**
     The 'pikchrshow-ready' event is fired (with no payload) when the
     wasm module has finished loading. */
  PS.addMsgHandler('pikchrshow-ready', function(){
    PS.clearMsgHandlers('pikchrshow-ready');
    F.page.onPikchrshowLoaded();
  });
285
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
      preStartWork();
      this.wMsg('pikchr',{
        pikchr: txt,
        darkMode: !!window.fossil.config.skin.isDark
      });
    };

    const eOut = E('#pikchr-output');
    const eOutWrapper = E('#pikchr-output-wrapper');
    PS.addMsgHandler('pikchr', function(ev){
      const m = ev.data;
      eOut.classList[m.isError ? 'add' : 'remove']('error');
      eOut.dataset.pikchr = m.pikchr;
      let content;
      let sz;

      switch(PS.renderMode){
          case 'text':


            content = '<textarea>'+m.result+'</textarea>';









            eOut.classList.add('text');
            eOutWrapper.classList.add('text');
            break;

          default:
            content = m.result;

            eOut.classList.remove('text');
            eOutWrapper.classList.remove('text');


            break;

      }
      eOut.innerHTML = content;
      let vw = null, vh = null;
      if(!PS.config.renderAutoScale
         && !m.isError && 'html'===PS.renderMode){
        const svg = E(eOut,':scope > svg');
        const vb = svg ? svg.getAttribute('viewBox').split(' ') : false;
        if(vb && 4===vb.length){
          vw = (+vb[2] + 10)+'px';
          vh = (+vb[3] + 10)+'px';
        }else if(svg){
          console.warn("SVG element is missing viewBox attribute.");
        }
      }
      eOut.style.width = vw;
      eOut.style.height = vh;
    })/*'pikchr' msg handler*/;

    E('#btn-render-mode').addEventListener('click',function(){
      let mode = PS.renderMode;
      const modes = ['text','html'];
      let ndx = modes.indexOf(mode) + 1;
      if(ndx>=modes.length) ndx = 0;
      PS.renderMode = modes[ndx];
      if(eOut.dataset.pikchr){
        PS.render(eOut.dataset.pikchr);
      }
    });



    PS.addMsgHandler('working',function f(ev){
      switch(ev.data){
          case 'start': /* See notes in preStartWork(). */; return;
          case 'end':
            //preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
            btnRender.innerText = preStartWork._.btnLabel;







<
<


|
|
<
<
>
|

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

>
|
<
>
|
|
>
>

>

<

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



|
<
<
|
|
|
|


>
>







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
      preStartWork();
      this.wMsg('pikchr',{
        pikchr: txt,
        darkMode: !!window.fossil.config.skin.isDark
      });
    };



    PS.addMsgHandler('pikchr', function(ev){
      const m = ev.data;
      PS.e.pikOut.classList[m.isError ? 'add' : 'remove']('error');
      PS.e.pikOut.dataset.pikchr = m.pikchr;


      const mode = PS.renderModes[PS.renderModes.selectedIndex];
      switch(mode){
          case 'text':
          case 'markdown':
          case 'wiki': {
            const body = [m.result];
            if('markdown'===mode){
              body.unshift('```pikchr');
              body.push('```');
            }else if('wiki'===mode){
              body.unshift('<verbatim type="pikchr">');
              body.push('</verbatim>');
            }
            PS.e.outText.value = body.join('\n');
            PS.e.outText.classList.remove('hidden');
            PS.e.pikOut.classList.add('hidden');
            PS.e.pikOutWrapper.classList.add('text');
            break;
          }
          case 'svg':

            PS.e.outText.classList.add('hidden');
            PS.e.pikOut.classList.remove('hidden');
            PS.e.pikOutWrapper.classList.remove('text');
            PS.e.pikOut.innerHTML = m.result;
            PS.e.outText.value = m.result/*for clipboard copy*/;
            break;
          default: throw new Error("Unhandled render mode: "+mode);
      }

      let vw = null, vh = null;
      if('svg'===mode && !PS.config.renderAutofit && !m.isError){




        vw = m.width; vh = m.height;



      }

      PS.e.pikOut.style.width = vw ? vw+'px' : null;
      PS.e.pikOut.style.height = vh ? vh+'px' : null;
    })/*'pikchr' msg handler*/;

    E('#btn-render-mode').addEventListener('click',function(){
      const modes = PS.renderModes;


      modes.selectedIndex = (modes.selectedIndex + 1) % modes.length;
      PS.e.previewModeLabel.innerText = PS.renderModeLabels[modes[modes.selectedIndex]];
      if(PS.e.pikOut.dataset.pikchr){
        PS.render(PS.e.pikOut.dataset.pikchr);
      }
    });
    F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText});
    PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false);

    PS.addMsgHandler('working',function f(ev){
      switch(ev.data){
          case 'start': /* See notes in preStartWork(). */; return;
          case 'end':
            //preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
            btnRender.innerText = preStartWork._.btnLabel;
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
          e.dispatchEvent(new Event('change'));
        }
        e.addEventListener('change', function(){
          PS.config[this.dataset.config] = this.checked;
          PS.storeConfig();
        }, false);
      });
    E('#opt-cb-autoscale').addEventListener('change',function(){
      /* PS.config.renderAutoScale was set by the data-config
         event handler. */
      if('html'==PS.renderMode && eOut.dataset.pikchr){
        PS.render(eOut.dataset.pikchr);
      }
    });
    /* For each button with data-cmd=X, map a click handler which
       calls PS.render(X). */
    const cmdClick = function(){PS.render(this.dataset.cmd);};
    EAll('button[data-cmd]').forEach(
      e => e.addEventListener('click', cmdClick, false)







|
|

|
|







390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
          e.dispatchEvent(new Event('change'));
        }
        e.addEventListener('change', function(){
          PS.config[this.dataset.config] = this.checked;
          PS.storeConfig();
        }, false);
      });
    E('#opt-cb-autofit').addEventListener('change',function(){
      /* PS.config.renderAutofit was set by the data-config
         event handler. */
      if(0==PS.renderModes.selectedIndex && PS.e.pikOut.dataset.pikchr){
        PS.render(PS.e.pikOut.dataset.pikchr);
      }
    });
    /* For each button with data-cmd=X, map a click handler which
       calls PS.render(X). */
    const cmdClick = function(){PS.render(this.dataset.cmd);};
    EAll('button[data-cmd]').forEach(
      e => e.addEventListener('click', cmdClick, false)