Fossil

Check-in [c304b4ffdf]
Login

Check-in [c304b4ffdf]

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

Overview
Comment:Rearranged the pikchrshow controls a bit to make better use of space and reduce flickering of the preview mode label/controls. Adapted CSS for pikchr error reporting structure changes. Code renaming for consistency/clarity. fossil.copyButton() click handler is now a no-op if the pseudo-button element has the 'disabled' CSS class and added style to grey such a button out.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: c304b4ffdf163aed2b7ab42084a87ce5dadc7a4c057ce827137db9562ac15d6d
User & Date: stephan 2020-09-11 23:56:22.404
Context
2020-09-12
00:00
Import the latest version of pikchr that supports the "file" object type. ... (check-in: b01fae602d user: drh tags: trunk)
2020-09-11
23:56
Rearranged the pikchrshow controls a bit to make better use of space and reduce flickering of the preview mode label/controls. Adapted CSS for pikchr error reporting structure changes. Code renaming for consistency/clarity. fossil.copyButton() click handler is now a no-op if the pseudo-button element has the 'disabled' CSS class and added style to grey such a button out. ... (check-in: c304b4ffdf user: stephan tags: trunk)
22:30
Minor code readability tweaks, nothing functional. ... (check-in: f73f1e36ad user: stephan tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
901
902
903
904
905
906
907



908
909
910
911
912
913
914
d='M4,5h4v1h-4zm0,2h4v1h-4z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
d='M5,3h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
d='M10,4.4v1.6h1.6zm-4,-0.6h3v3h-3zm0,3h6v5.4h-6z'/%3E%3Cpath style='fill:rgb(80,128,208)' \
d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  cursor: pointer;



}
.copy-button-flipped {
/*Note: .16em is suitable for element grouping.*/
  margin-left: .16em;
  margin-right: 0;
}
.nobr {







>
>
>







901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
d='M4,5h4v1h-4zm0,2h4v1h-4z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
d='M5,3h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
d='M10,4.4v1.6h1.6zm-4,-0.6h3v3h-3zm0,3h6v5.4h-6z'/%3E%3Cpath style='fill:rgb(80,128,208)' \
d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  cursor: pointer;
}
.copy-button.disabled {
  opacity: 0.4;
}
.copy-button-flipped {
/*Note: .16em is suitable for element grouping.*/
  margin-left: .16em;
  margin-right: 0;
}
.nobr {
Changes to src/fossil.copybutton.js.
55
56
57
58
59
60
61






62
63
64
65
66
67
68

     The copy button emits custom event 'text-copied' after it has
     successfully copied text to the clipboard. The event's "detail"
     member is an object with a "text" property holding the copied
     text. Other properties may be added in the future. The event is
     not fired if copying to the clipboard fails (e.g. is not
     available in the current environment).







     Returns the copy-initialized element.

     Example:

     const button = fossil.copyButton('#my-copy-button', {
       copyFromId: 'some-other-element-id'







>
>
>
>
>
>







55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

     The copy button emits custom event 'text-copied' after it has
     successfully copied text to the clipboard. The event's "detail"
     member is an object with a "text" property holding the copied
     text. Other properties may be added in the future. The event is
     not fired if copying to the clipboard fails (e.g. is not
     available in the current environment).

     As a special case, the copy button's click handler is suppressed
     (becomes a no-op) for as long as the element has the CSS class
     "disabled". This allows elements which cannot be disabled via
     HTML attributes, e.g. a SPAN, to act as a copy button while still
     providing a way to disable them.

     Returns the copy-initialized element.

     Example:

     const button = fossil.copyButton('#my-copy-button', {
       copyFromId: 'some-other-element-id'
89
90
91
92
93
94
95

96
97
98
99
100
101
102
    const extract = opt.extractText || (
      undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value
    );
    D.copyStyle(e, opt.style);
    e.addEventListener(
      'click',
      function(){

        const txt = extract.call(opt);
        if(txt && D.copyTextToClipboard(txt)){
          e.dispatchEvent(new CustomEvent('text-copied',{
            detail: {text: txt}
          }));
        }
      },







>







95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
    const extract = opt.extractText || (
      undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value
    );
    D.copyStyle(e, opt.style);
    e.addEventListener(
      'click',
      function(){
        if(e.classList.contains('disabled')) return;
        const txt = extract.call(opt);
        if(txt && D.copyTextToClipboard(txt)){
          e.dispatchEvent(new CustomEvent('text-copied',{
            detail: {text: txt}
          }));
        }
      },
Changes to src/fossil.dom.js.
313
314
315
316
317
318
319




320
321
322
323
324
325
326
  */
  dom.removeClass = function(e,c){
    const a = argsToArray(arguments);
    a.unshift('remove');
    return domAddRemoveClass.apply(this, a);
  };





  dom.hasClass = function(e,c){
    return (e && e.classList) ? e.classList.contains(c) : false;
  };

  /**
     Each argument after the first may be a single DOM element
     or a container of them with a forEach() method. All such







>
>
>
>







313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
  */
  dom.removeClass = function(e,c){
    const a = argsToArray(arguments);
    a.unshift('remove');
    return domAddRemoveClass.apply(this, a);
  };

  /**
     Returns true if DOM element e contains CSS class c, else
     false.
  */
  dom.hasClass = function(e,c){
    return (e && e.classList) ? e.classList.contains(c) : false;
  };

  /**
     Each argument after the first may be a single DOM element
     or a container of them with a forEach() method. All such
540
541
542
543
544
545
546





547
548
549
550
551
552
553
      e.style.opacity = opacity;
      delete e.dataset.isBlinking;
      if(afterFlashCallback) afterFlashCallback();
    }, howLongMs);
    return e;
  };
  dom.flashOnce.defaultTimeMs = 400;






  /**
     Attempts to copy the given text to the system clipboard. Returns
     true if it succeeds, else false.
  */
  dom.copyTextToClipboard = function(text){
    if( window.clipboardData && window.clipboardData.setData ){







>
>
>
>
>







544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
      e.style.opacity = opacity;
      delete e.dataset.isBlinking;
      if(afterFlashCallback) afterFlashCallback();
    }, howLongMs);
    return e;
  };
  dom.flashOnce.defaultTimeMs = 400;
  /**
     A DOM event handler which simply passes event.target
     to dom.flashOnce().
  */
  dom.flashOnce.eventHandler = (event)=>dom.flashOnce(event.target)

  /**
     Attempts to copy the given text to the system clipboard. Returns
     true if it succeeds, else false.
  */
  dom.copyTextToClipboard = function(text){
    if( window.clipboardData && window.clipboardData.setData ){
Changes to src/fossil.page.pikchrshow.js.
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
    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),
      uiControls: E('#pikchrshow-controls'),
      btnTogglePreviewMode: D.button("Preview mode"),
      selectMarkupAlignment: D.select()
    };



    D.append(P.e.uiControls, P.e.btnTogglePreviewMode);



    ////////////////////////////////////////////////////////////
    // Setup markup alignment selection...
    D.append(P.e.uiControls, 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){







|
>
|






|


>
>
>
|
>
>











|
|
<
<












|







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
    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'),
      previewLegend: E('#pikchrshow-output-wrapper > legend'),
      previewModeLabel: D.span(/*holds the text for the preview mode label*/),
      previewCopyButton: D.addClass(D.span(), '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),
      uiControls: E('#pikchrshow-controls'),
      previewModeToggle: D.button("Preview mode"),
      selectMarkupAlignment: D.select()
    };
    ////////////////////////////////////////////////////////////
    // Setup the preview fieldset's LEGEND element...
    D.append( P.e.previewLegend,
              P.e.previewModeToggle,
              P.e.previewModeLabel,
              P.e.previewCopyButton );    

    ////////////////////////////////////////////////////////////
    // Setup markup alignment selection...
    D.append(P.e.uiControls, 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.previewCopyButton, {copyFromElement: P.e.taPreviewText});
    P.e.previewCopyButton.addEventListener('text-copied', D.flashOnce.eventHandler, 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.previewModeToggle.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){
184
185
186
187
188
189
190

191
192
193
194
195
196
197
198
    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;







>
|







188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
    if(this.response.isError){
      preTgt.innerHTML = this.response.raw;
      D.addClass(preTgt, 'error');
      this.e.previewModeLabel.innerText = "Error";
      return;
    }
    D.removeClass(preTgt, 'error');
    D.removeClass(P.e.previewCopyButton, 'disabled');
    D.enable(this.e.previewModeToggle);
    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;
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

250
251
252
253
254
255
256
      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.cbAutoPreview, this.e.selectScript
        /* 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;
    }







|
<













|










|
>







221
222
223
224
225
226
227
228

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
      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;
    }
    this.e.previewModeLabel.innerText = label;

  };

  /**
     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.cbAutoPreview, this.e.selectScript
        /* this.e.previewModeToggle 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.previewModeToggle);
    D.addClass(this.e.previewCopyButton, 'disabled');
    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;
    }
Changes to src/pikchrshow.c.
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
134
     "{display: flex; flex-direction: column; align-items: stretch;}");
  CX("#pikchrshow-form > * {margin: 0.25em 0}");
  CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}");
  CX("#pikchrshow-output > pre, "
     "#pikchrshow-output > pre > div, "
     "#pikchrshow-output > pre > div > pre "
     "{margin: 0; padding: 0}");
  CX("#pikchrshow-output > pre > div > pre "
     /* Server-side error report */
     "{padding: 0.5em}");
  CX("#pikchrshow-controls {" /* where the buttons live */
     "display: flex; flex-direction: row; "
     "align-items: center; flex-wrap: wrap;"
     "}");
  CX("#pikchrshow-controls > * {"
     "display: inline; margin: 0 0.25em 0.5em 0;"
     "}");
  CX("#pikchrshow-controls > .input-with-label > * {"
     "cursor: pointer;"
     "}");
  CX("#pikchrshow-output.dark-mode svg {"
     /* Flip the colors to approximate a dark theme look */
     "filter: invert(1) hue-rotate(180deg);"
     "}");
  CX("#sbs-wrapper > fieldset {"
     "padding: 0.25em 0.5em; border-radius: 0.25em;"
     "}");
  CX("fieldset > legend > .copy-button {margin-left: 0.25em}");
  CX(".dragover {border: 0.5em dotted rgba(0,255,0,0.6)}");
  CX("</style>");
  CX("<div>Input pikchr code and tap Preview to render it:</div>");
  CX("<div id='sbs-wrapper'>");
  CX("<div id='pikchrshow-form'>");
  CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
     zContent/*safe-for-%s*/);
  CX("<div id='pikchrshow-controls'>");
  CX("<button id='pikchr-submit-preview'>Preview</button>");
  style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
                         "Dark mode?",
                         "1", flipColors, 0);
  CX("</div>"/*#pikchrshow-controls*/);
  CX("</div>"/*#pikchrshow-form*/);
  CX("<fieldset id='pikchrshow-output-wrapper'>");
  CX("<legend>Preview <span class='copy-button'></span></legend>");
  CX("<div id='pikchrshow-output'>");
  if(*zContent){
    int w = 0, h = 0;
    char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
    if( w>0 && h>0 ){
      const char *zNonce = safe_html_nonce(1);
      CX("%s<div style='max-width:%dpx;'>\n%s</div>%s",







|



















|















|







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
134
     "{display: flex; flex-direction: column; align-items: stretch;}");
  CX("#pikchrshow-form > * {margin: 0.25em 0}");
  CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}");
  CX("#pikchrshow-output > pre, "
     "#pikchrshow-output > pre > div, "
     "#pikchrshow-output > pre > div > pre "
     "{margin: 0; padding: 0}");
  CX("#pikchrshow-output.error > pre "
     /* Server-side error report */
     "{padding: 0.5em}");
  CX("#pikchrshow-controls {" /* where the buttons live */
     "display: flex; flex-direction: row; "
     "align-items: center; flex-wrap: wrap;"
     "}");
  CX("#pikchrshow-controls > * {"
     "display: inline; margin: 0 0.25em 0.5em 0;"
     "}");
  CX("#pikchrshow-controls > .input-with-label > * {"
     "cursor: pointer;"
     "}");
  CX("#pikchrshow-output.dark-mode svg {"
     /* Flip the colors to approximate a dark theme look */
     "filter: invert(1) hue-rotate(180deg);"
     "}");
  CX("#sbs-wrapper > fieldset {"
     "padding: 0.25em 0.5em; border-radius: 0.25em;"
     "}");
  CX("fieldset > legend > * {margin-right: 0.25em}");
  CX(".dragover {border: 0.5em dotted rgba(0,255,0,0.6)}");
  CX("</style>");
  CX("<div>Input pikchr code and tap Preview to render it:</div>");
  CX("<div id='sbs-wrapper'>");
  CX("<div id='pikchrshow-form'>");
  CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
     zContent/*safe-for-%s*/);
  CX("<div id='pikchrshow-controls'>");
  CX("<button id='pikchr-submit-preview'>Preview</button>");
  style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
                         "Dark mode?",
                         "1", flipColors, 0);
  CX("</div>"/*#pikchrshow-controls*/);
  CX("</div>"/*#pikchrshow-form*/);
  CX("<fieldset id='pikchrshow-output-wrapper'>");
  CX("<legend></legend>");
  CX("<div id='pikchrshow-output'>");
  if(*zContent){
    int w = 0, h = 0;
    char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
    if( w>0 && h>0 ){
      const char *zNonce = safe_html_nonce(1);
      CX("%s<div style='max-width:%dpx;'>\n%s</div>%s",