Fossil SCM

pikchrshow now supports stashing a single pikchr to/from browser-local storage to enable switching back and forth between example scripts while editing one's own script. Experimentally re-indented the C-side implementation to make its generated HTML hierarchy clearer. Over-the-wire size is now 4.8kb.

stephan 2020-09-14 02:06 trunk
Commit 49a04c97b5a78055f2da0f7920ba4c9a2a444bb22336a1296e91ebe9c9f7b590
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -2,11 +2,11 @@
22
"use strict";
33
/**
44
Client-side implementation of the /pikchrshow app. Requires that
55
the fossil JS bootstrapping is complete and that these fossil JS
66
APIs have been installed: fossil.fetch, fossil.dom,
7
- fossil.copybutton, fossil.popupwidget
7
+ fossil.copybutton, fossil.popupwidget, fossil.storage
88
*/
99
const E = (s)=>document.querySelector(s),
1010
D = F.dom,
1111
P = F.page;
1212
@@ -26,10 +26,13 @@
2626
D.addClass(D.span(),'copy-button'),
2727
'id','preview-copy-button'
2828
),
2929
previewModeLabel: D.label('preview-copy-button'),
3030
btnSubmit: E('#pikchr-submit-preview'),
31
+ btnStash: E('#pikchr-stash'),
32
+ btnUnstash: E('#pikchr-unstash'),
33
+ btnClearStash: E('#pikchr-clear-stash'),
3134
cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'),
3235
taContent: E('#content'),
3336
taPreviewText: D.textarea(20,0,true),
3437
uiControls: E('#pikchrshow-controls'),
3538
previewModeToggle: D.button("Preview mode"),
@@ -228,20 +231,55 @@
228231
].forEach(function(e){
229232
Object.keys(dropEvents).forEach(
230233
(k)=>e.addEventListener(k, dropEvents[k], true)
231234
);
232235
});
236
+
237
+ ////////////////////////////////////////////////////////////
238
+ // Setup stash/unstash
239
+ const stashKey = 'pikchrshow-stash';
240
+ P.e.btnStash.addEventListener('click', function(){
241
+ const val = P.e.taContent.value;
242
+ if(val){
243
+ F.storage.set(stashKey, val);
244
+ D.enable(P.e.btnUnstash);
245
+ F.toast.message("Stashed pikchr.");
246
+ }
247
+ }, false);
248
+ P.e.btnUnstash.addEventListener('click', function(){
249
+ const val = F.storage.get(stashKey);
250
+ P.e.taContent.value = val || '';
251
+ }, false);
252
+ P.e.btnClearStash.addEventListener('click', function(){
253
+ F.storage.remove(stashKey);
254
+ D.disable(P.e.btnUnstash);
255
+ F.toast.message("Cleared pikchr stash.");
256
+ }, false);
257
+ F.helpButtonlets.create(P.e.btnClearStash.nextElementSibling);
258
+ // If we have stashed contents, enable Unstash, else disable it:
259
+ if(F.storage.contains(stashKey)) D.enable(P.e.btnUnstash);
260
+ else D.disable(P.e.btnUnstash);
233261
234262
////////////////////////////////////////////////////////////
235263
// If we start with content, get it in sync with the state
236
- // generated by P.preview().
237
- if(P.e.taContent.value/*was pre-filled server-side*/){
264
+ // generated by P.preview(). Normally the server pre-populates it
265
+ // with an example.
266
+ let needsPreview;
267
+ if(!P.e.taContent.value){
268
+ P.e.taContent.value = F.storage.get(stashKey,'');
269
+ needsPreview = true;
270
+ }
271
+ if(P.e.taContent.value){
238272
/* Fill our "response" state so that renderPreview() can work */
239273
P.response.inputText = P.e.taContent.value;
240274
P.response.raw = P.e.previewTarget.innerHTML;
241
- P.renderPreview()/*it's already rendered, but this gets all
242
- labels/headers in sync.*/;
275
+ if(needsPreview) P.preview();
276
+ else{
277
+ /*If it's from the server, it's already rendered, but this
278
+ gets all labels/headers in sync.*/
279
+ P.renderPreview();
280
+ }
243281
}
244282
}/*F.onPageLoad()*/);
245283
246284
/**
247285
Updates the preview view based on the current preview mode and
@@ -324,11 +362,12 @@
324362
P.preview = function fp(){
325363
if(!fp.hasOwnProperty('toDisable')){
326364
fp.toDisable = [
327365
/* input elements to disable during ajax operations */
328366
this.e.btnSubmit, this.e.taContent,
329
- this.e.cbAutoPreview, this.e.selectScript
367
+ this.e.cbAutoPreview, this.e.selectScript,
368
+ this.e.btnStash, this.e.btnClearStash
330369
/* handled separately: previewModeToggle, previewCopyButton,
331370
markupAlignRadios */
332371
];
333372
fp.target = this.e.previewTarget;
334373
fp.updateView = function(c,isError){
@@ -357,22 +396,19 @@
357396
}
358397
const self = this;
359398
const fd = new FormData();
360399
fd.append('ajax', true);
361400
fd.append('content',content);
362
- F.message(
363
- "Fetching preview..."
364
- ).fetch('pikchrshow',{
401
+ F.fetch('pikchrshow',{
365402
payload: fd,
366403
responseHeaders: 'x-pikchrshow-is-error',
367404
onload: (r,isErrHeader)=>{
368405
const isErr = +isErrHeader ? true : false;
369406
if(!isErr && sampleScript){
370407
sampleScript.cached = r;
371408
}
372409
fp.updateView(r,isErr);
373
- F.message('Updated preview.');
374410
},
375411
onerror: (e)=>{
376412
F.fetch.onerror(e);
377413
fp.updateView("Error fetching preview: "+e, true);
378414
}
379415
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -2,11 +2,11 @@
2 "use strict";
3 /**
4 Client-side implementation of the /pikchrshow app. Requires that
5 the fossil JS bootstrapping is complete and that these fossil JS
6 APIs have been installed: fossil.fetch, fossil.dom,
7 fossil.copybutton, fossil.popupwidget
8 */
9 const E = (s)=>document.querySelector(s),
10 D = F.dom,
11 P = F.page;
12
@@ -26,10 +26,13 @@
26 D.addClass(D.span(),'copy-button'),
27 'id','preview-copy-button'
28 ),
29 previewModeLabel: D.label('preview-copy-button'),
30 btnSubmit: E('#pikchr-submit-preview'),
 
 
 
31 cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'),
32 taContent: E('#content'),
33 taPreviewText: D.textarea(20,0,true),
34 uiControls: E('#pikchrshow-controls'),
35 previewModeToggle: D.button("Preview mode"),
@@ -228,20 +231,55 @@
228 ].forEach(function(e){
229 Object.keys(dropEvents).forEach(
230 (k)=>e.addEventListener(k, dropEvents[k], true)
231 );
232 });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
234 ////////////////////////////////////////////////////////////
235 // If we start with content, get it in sync with the state
236 // generated by P.preview().
237 if(P.e.taContent.value/*was pre-filled server-side*/){
 
 
 
 
 
 
238 /* Fill our "response" state so that renderPreview() can work */
239 P.response.inputText = P.e.taContent.value;
240 P.response.raw = P.e.previewTarget.innerHTML;
241 P.renderPreview()/*it's already rendered, but this gets all
242 labels/headers in sync.*/;
 
 
 
 
243 }
244 }/*F.onPageLoad()*/);
245
246 /**
247 Updates the preview view based on the current preview mode and
@@ -324,11 +362,12 @@
324 P.preview = function fp(){
325 if(!fp.hasOwnProperty('toDisable')){
326 fp.toDisable = [
327 /* input elements to disable during ajax operations */
328 this.e.btnSubmit, this.e.taContent,
329 this.e.cbAutoPreview, this.e.selectScript
 
330 /* handled separately: previewModeToggle, previewCopyButton,
331 markupAlignRadios */
332 ];
333 fp.target = this.e.previewTarget;
334 fp.updateView = function(c,isError){
@@ -357,22 +396,19 @@
357 }
358 const self = this;
359 const fd = new FormData();
360 fd.append('ajax', true);
361 fd.append('content',content);
362 F.message(
363 "Fetching preview..."
364 ).fetch('pikchrshow',{
365 payload: fd,
366 responseHeaders: 'x-pikchrshow-is-error',
367 onload: (r,isErrHeader)=>{
368 const isErr = +isErrHeader ? true : false;
369 if(!isErr && sampleScript){
370 sampleScript.cached = r;
371 }
372 fp.updateView(r,isErr);
373 F.message('Updated preview.');
374 },
375 onerror: (e)=>{
376 F.fetch.onerror(e);
377 fp.updateView("Error fetching preview: "+e, true);
378 }
379
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -2,11 +2,11 @@
2 "use strict";
3 /**
4 Client-side implementation of the /pikchrshow app. Requires that
5 the fossil JS bootstrapping is complete and that these fossil JS
6 APIs have been installed: fossil.fetch, fossil.dom,
7 fossil.copybutton, fossil.popupwidget, fossil.storage
8 */
9 const E = (s)=>document.querySelector(s),
10 D = F.dom,
11 P = F.page;
12
@@ -26,10 +26,13 @@
26 D.addClass(D.span(),'copy-button'),
27 'id','preview-copy-button'
28 ),
29 previewModeLabel: D.label('preview-copy-button'),
30 btnSubmit: E('#pikchr-submit-preview'),
31 btnStash: E('#pikchr-stash'),
32 btnUnstash: E('#pikchr-unstash'),
33 btnClearStash: E('#pikchr-clear-stash'),
34 cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'),
35 taContent: E('#content'),
36 taPreviewText: D.textarea(20,0,true),
37 uiControls: E('#pikchrshow-controls'),
38 previewModeToggle: D.button("Preview mode"),
@@ -228,20 +231,55 @@
231 ].forEach(function(e){
232 Object.keys(dropEvents).forEach(
233 (k)=>e.addEventListener(k, dropEvents[k], true)
234 );
235 });
236
237 ////////////////////////////////////////////////////////////
238 // Setup stash/unstash
239 const stashKey = 'pikchrshow-stash';
240 P.e.btnStash.addEventListener('click', function(){
241 const val = P.e.taContent.value;
242 if(val){
243 F.storage.set(stashKey, val);
244 D.enable(P.e.btnUnstash);
245 F.toast.message("Stashed pikchr.");
246 }
247 }, false);
248 P.e.btnUnstash.addEventListener('click', function(){
249 const val = F.storage.get(stashKey);
250 P.e.taContent.value = val || '';
251 }, false);
252 P.e.btnClearStash.addEventListener('click', function(){
253 F.storage.remove(stashKey);
254 D.disable(P.e.btnUnstash);
255 F.toast.message("Cleared pikchr stash.");
256 }, false);
257 F.helpButtonlets.create(P.e.btnClearStash.nextElementSibling);
258 // If we have stashed contents, enable Unstash, else disable it:
259 if(F.storage.contains(stashKey)) D.enable(P.e.btnUnstash);
260 else D.disable(P.e.btnUnstash);
261
262 ////////////////////////////////////////////////////////////
263 // If we start with content, get it in sync with the state
264 // generated by P.preview(). Normally the server pre-populates it
265 // with an example.
266 let needsPreview;
267 if(!P.e.taContent.value){
268 P.e.taContent.value = F.storage.get(stashKey,'');
269 needsPreview = true;
270 }
271 if(P.e.taContent.value){
272 /* Fill our "response" state so that renderPreview() can work */
273 P.response.inputText = P.e.taContent.value;
274 P.response.raw = P.e.previewTarget.innerHTML;
275 if(needsPreview) P.preview();
276 else{
277 /*If it's from the server, it's already rendered, but this
278 gets all labels/headers in sync.*/
279 P.renderPreview();
280 }
281 }
282 }/*F.onPageLoad()*/);
283
284 /**
285 Updates the preview view based on the current preview mode and
@@ -324,11 +362,12 @@
362 P.preview = function fp(){
363 if(!fp.hasOwnProperty('toDisable')){
364 fp.toDisable = [
365 /* input elements to disable during ajax operations */
366 this.e.btnSubmit, this.e.taContent,
367 this.e.cbAutoPreview, this.e.selectScript,
368 this.e.btnStash, this.e.btnClearStash
369 /* handled separately: previewModeToggle, previewCopyButton,
370 markupAlignRadios */
371 ];
372 fp.target = this.e.previewTarget;
373 fp.updateView = function(c,isError){
@@ -357,22 +396,19 @@
396 }
397 const self = this;
398 const fd = new FormData();
399 fd.append('ajax', true);
400 fd.append('content',content);
401 F.fetch('pikchrshow',{
 
 
402 payload: fd,
403 responseHeaders: 'x-pikchrshow-is-error',
404 onload: (r,isErrHeader)=>{
405 const isErr = +isErrHeader ? true : false;
406 if(!isErr && sampleScript){
407 sampleScript.cached = r;
408 }
409 fp.updateView(r,isErr);
 
410 },
411 onerror: (e)=>{
412 F.fetch.onerror(e);
413 fp.updateView("Error fetching preview: "+e, true);
414 }
415
+95 -87
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -70,100 +70,108 @@
7070
"arrow right 200% \"HTML+SVG\" \"Output\"\n"
7171
"arrow <-> down from last box.s\n"
7272
"box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n";
7373
}
7474
style_header("PikchrShow");
75
- CX("<style>");
76
- CX("div.content { padding-top: 0.5em }\n");
77
- CX("#sbs-wrapper {"
78
- "display: flex; flex-direction: column;"
79
- "}\n");
80
- CX("#sbs-wrapper > * {"
81
- "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"
82
- "align-self: stretch;"
83
- "}\n");
84
- CX("#sbs-wrapper textarea {"
85
- "max-width: initial; flex: 1 1 auto;"
86
- "}\n");
87
- CX("#pikchrshow-output, #pikchrshow-form"
88
- "{display: flex; flex-direction: column; align-items: stretch;}");
89
- CX("#pikchrshow-form > * {margin: 0.25em 0}\n");
90
- CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}\n");
91
- CX("#pikchrshow-output > pre, "
92
- "#pikchrshow-output > pre > div, "
93
- "#pikchrshow-output > pre > div > pre "
94
- "{margin: 0; padding: 0}\n");
95
- CX("#pikchrshow-output.error > pre "
96
- /* Server-side error report */
97
- "{padding: 0.5em}\n");
98
- CX("#pikchrshow-controls {" /* where the buttons live */
99
- "display: flex; flex-direction: row; "
100
- "align-items: center; flex-wrap: wrap;"
101
- "}\n");
102
- CX("#pikchrshow-controls > * {"
103
- "display: inline; margin: 0 0.25em 0.5em 0;"
104
- "}\n");
105
- CX("#pikchrshow-output-wrapper label {"
106
- "cursor: pointer;"
107
- "}\n");
108
- CX("body.pikchrshow .input-with-label > * {"
109
- "margin: 0 0.2em;"
110
- "}\n");
111
- CX("body.pikchrshow .input-with-label > label {"
112
- "cursor: pointer;"
113
- "}\n");
114
- CX("#pikchrshow-output.dark-mode svg {"
115
- /* Flip the colors to approximate a dark theme look */
116
- "filter: invert(1) hue-rotate(180deg);"
117
- "}\n");
118
- CX("#pikchrshow-output-wrapper {"
119
- "padding: 0.25em 0.5em; border-radius: 0.25em;"
120
- "border-width: 1px;"/*some skins disable fieldset borders*/
121
- "}\n");
122
- CX("#pikchrshow-output-wrapper > legend > *:not(.copy-button){"
123
- "margin-right: 0.5em; vertical-align: middle;"
124
- "}\n");
125
- CX("body.pikchrshow .v-align-middle{"
126
- "vertical-align: middle"
127
- "}\n");
128
- CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
129
- CX("</style>");
75
+ CX("<style>"); {
76
+ CX("div.content { padding-top: 0.5em }\n");
77
+ CX("#sbs-wrapper {"
78
+ "display: flex; flex-direction: column;"
79
+ "}\n");
80
+ CX("#sbs-wrapper > * {"
81
+ "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"
82
+ "align-self: stretch;"
83
+ "}\n");
84
+ CX("#sbs-wrapper textarea {"
85
+ "max-width: initial; flex: 1 1 auto;"
86
+ "}\n");
87
+ CX("#pikchrshow-output, #pikchrshow-form"
88
+ "{display: flex; flex-direction: column; align-items: stretch;}");
89
+ CX("#pikchrshow-form > * {margin: 0.25em 0}\n");
90
+ CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}\n");
91
+ CX("#pikchrshow-output > pre, "
92
+ "#pikchrshow-output > pre > div, "
93
+ "#pikchrshow-output > pre > div > pre "
94
+ "{margin: 0; padding: 0}\n");
95
+ CX("#pikchrshow-output.error > pre "
96
+ /* Server-side error report */
97
+ "{padding: 0.5em}\n");
98
+ CX("#pikchrshow-controls {" /* where the buttons live */
99
+ "display: flex; flex-direction: row; "
100
+ "align-items: center; flex-wrap: wrap;"
101
+ "}\n");
102
+ CX("#pikchrshow-controls > * {"
103
+ "display: inline; margin: 0 0.25em 0.5em 0;"
104
+ "}\n");
105
+ CX("#pikchrshow-output-wrapper label {"
106
+ "cursor: pointer;"
107
+ "}\n");
108
+ CX("body.pikchrshow .input-with-label > * {"
109
+ "margin: 0 0.2em;"
110
+ "}\n");
111
+ CX("body.pikchrshow .input-with-label > label {"
112
+ "cursor: pointer;"
113
+ "}\n");
114
+ CX("#pikchrshow-output.dark-mode svg {"
115
+ /* Flip the colors to approximate a dark theme look */
116
+ "filter: invert(1) hue-rotate(180deg);"
117
+ "}\n");
118
+ CX("#pikchrshow-output-wrapper {"
119
+ "padding: 0.25em 0.5em; border-radius: 0.25em;"
120
+ "border-width: 1px;"/*some skins disable fieldset borders*/
121
+ "}\n");
122
+ CX("#pikchrshow-output-wrapper > legend > *:not(.copy-button){"
123
+ "margin-right: 0.5em; vertical-align: middle;"
124
+ "}\n");
125
+ CX("body.pikchrshow .v-align-middle{"
126
+ "vertical-align: middle"
127
+ "}\n");
128
+ CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
129
+ } CX("</style>");
130130
CX("<div>Input pikchr code and tap Preview to render it:</div>");
131
- CX("<div id='sbs-wrapper'>");
132
- CX("<div id='pikchrshow-form'>");
133
- CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
134
- zContent/*safe-for-%s*/);
135
- CX("<div id='pikchrshow-controls'>");
136
- CX("<button id='pikchr-submit-preview'>Preview</button>");
137
- style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
138
- "Dark mode?",
139
- "1", isDark, 0);
140
- CX("</div>"/*#pikchrshow-controls*/);
141
- CX("</div>"/*#pikchrshow-form*/);
142
- CX("<fieldset id='pikchrshow-output-wrapper'>");
143
- CX("<legend></legend>"
144
- /* Reminder: Firefox does not properly flexbox a LEGEND element,
145
- always flowing it in column mode. */);
146
- CX("<div id='pikchrshow-output'>");
147
- if(*zContent){
148
- int w = 0, h = 0;
149
- char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
150
- if( w>0 && h>0 ){
151
- const char *zNonce = safe_html_nonce(1);
152
- CX("%s<div style='max-width:%dpx;'>\n%s</div>%s",
153
- zNonce, w, zOut, zNonce);
154
- }else{
155
- CX("<pre>\n%s\n</pre>\n", zOut);
156
- }
157
- fossil_free(zOut);
158
- }
159
- CX("</div>"/*#pikchrshow-output*/);
160
- CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
161
- CX("</div>"/*sbs-wrapper*/);
131
+ CX("<div id='sbs-wrapper'>"); {
132
+ CX("<div id='pikchrshow-form'>"); {
133
+ CX("<textarea id='content' name='content' rows='15'>"
134
+ "%s</textarea>",zContent/*safe-for-%s*/);
135
+ CX("<div id='pikchrshow-controls'>"); {
136
+ CX("<button id='pikchr-submit-preview'>Preview</button>");
137
+ CX("<div class='input-with-label'>"); {
138
+ CX("<button id='pikchr-stash'>Stash</button>");
139
+ CX("<button id='pikchr-unstash'>Unstash</button>");
140
+ CX("<button id='pikchr-clear-stash'>Clear stash</button>");
141
+ CX("<span>Stores/restores a single pikchr script to/from "
142
+ "browser-local storage from/to the editor.</span>"
143
+ /* gets turned into a help-buttonlet */);
144
+ } CX("</div>"/*stash controls*/);
145
+ style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
146
+ "Dark mode?",
147
+ "1", isDark, 0);
148
+ } CX("</div>"/*#pikchrshow-controls*/);
149
+ }
150
+ CX("</div>"/*#pikchrshow-form*/);
151
+ CX("<fieldset id='pikchrshow-output-wrapper'>"); {
152
+ CX("<legend></legend>"
153
+ /* Reminder: Firefox does not properly flexbox a LEGEND
154
+ element, always flowing it in column mode. */);
155
+ CX("<div id='pikchrshow-output'>");
156
+ if(*zContent){
157
+ int w = 0, h = 0;
158
+ char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
159
+ if( w>0 && h>0 ){
160
+ const char *zNonce = safe_html_nonce(1);
161
+ CX("%s<div style='max-width:%dpx;'>\n%s</div>%s",
162
+ zNonce, w, zOut, zNonce);
163
+ }else{
164
+ CX("<pre>\n%s\n</pre>\n", zOut);
165
+ }
166
+ fossil_free(zOut);
167
+ } CX("</div>"/*#pikchrshow-output*/);
168
+ } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
169
+ } CX("</div>"/*sbs-wrapper*/);
162170
if(!builtin_bundle_all_fossil_js_apis()){
163171
builtin_emit_fossil_js_apis("dom", "fetch", "copybutton",
164
- "popupwidget", 0);
172
+ "popupwidget", "storage", 0);
165173
}
166174
builtin_emit_fossil_js_apis("page.pikchrshow", 0);
167175
builtin_fulfill_js_requests();
168176
style_footer();
169177
}
170178
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -70,100 +70,108 @@
70 "arrow right 200% \"HTML+SVG\" \"Output\"\n"
71 "arrow <-> down from last box.s\n"
72 "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n";
73 }
74 style_header("PikchrShow");
75 CX("<style>");
76 CX("div.content { padding-top: 0.5em }\n");
77 CX("#sbs-wrapper {"
78 "display: flex; flex-direction: column;"
79 "}\n");
80 CX("#sbs-wrapper > * {"
81 "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"
82 "align-self: stretch;"
83 "}\n");
84 CX("#sbs-wrapper textarea {"
85 "max-width: initial; flex: 1 1 auto;"
86 "}\n");
87 CX("#pikchrshow-output, #pikchrshow-form"
88 "{display: flex; flex-direction: column; align-items: stretch;}");
89 CX("#pikchrshow-form > * {margin: 0.25em 0}\n");
90 CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}\n");
91 CX("#pikchrshow-output > pre, "
92 "#pikchrshow-output > pre > div, "
93 "#pikchrshow-output > pre > div > pre "
94 "{margin: 0; padding: 0}\n");
95 CX("#pikchrshow-output.error > pre "
96 /* Server-side error report */
97 "{padding: 0.5em}\n");
98 CX("#pikchrshow-controls {" /* where the buttons live */
99 "display: flex; flex-direction: row; "
100 "align-items: center; flex-wrap: wrap;"
101 "}\n");
102 CX("#pikchrshow-controls > * {"
103 "display: inline; margin: 0 0.25em 0.5em 0;"
104 "}\n");
105 CX("#pikchrshow-output-wrapper label {"
106 "cursor: pointer;"
107 "}\n");
108 CX("body.pikchrshow .input-with-label > * {"
109 "margin: 0 0.2em;"
110 "}\n");
111 CX("body.pikchrshow .input-with-label > label {"
112 "cursor: pointer;"
113 "}\n");
114 CX("#pikchrshow-output.dark-mode svg {"
115 /* Flip the colors to approximate a dark theme look */
116 "filter: invert(1) hue-rotate(180deg);"
117 "}\n");
118 CX("#pikchrshow-output-wrapper {"
119 "padding: 0.25em 0.5em; border-radius: 0.25em;"
120 "border-width: 1px;"/*some skins disable fieldset borders*/
121 "}\n");
122 CX("#pikchrshow-output-wrapper > legend > *:not(.copy-button){"
123 "margin-right: 0.5em; vertical-align: middle;"
124 "}\n");
125 CX("body.pikchrshow .v-align-middle{"
126 "vertical-align: middle"
127 "}\n");
128 CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
129 CX("</style>");
130 CX("<div>Input pikchr code and tap Preview to render it:</div>");
131 CX("<div id='sbs-wrapper'>");
132 CX("<div id='pikchrshow-form'>");
133 CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
134 zContent/*safe-for-%s*/);
135 CX("<div id='pikchrshow-controls'>");
136 CX("<button id='pikchr-submit-preview'>Preview</button>");
137 style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
138 "Dark mode?",
139 "1", isDark, 0);
140 CX("</div>"/*#pikchrshow-controls*/);
141 CX("</div>"/*#pikchrshow-form*/);
142 CX("<fieldset id='pikchrshow-output-wrapper'>");
143 CX("<legend></legend>"
144 /* Reminder: Firefox does not properly flexbox a LEGEND element,
145 always flowing it in column mode. */);
146 CX("<div id='pikchrshow-output'>");
147 if(*zContent){
148 int w = 0, h = 0;
149 char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
150 if( w>0 && h>0 ){
151 const char *zNonce = safe_html_nonce(1);
152 CX("%s<div style='max-width:%dpx;'>\n%s</div>%s",
153 zNonce, w, zOut, zNonce);
154 }else{
155 CX("<pre>\n%s\n</pre>\n", zOut);
156 }
157 fossil_free(zOut);
158 }
159 CX("</div>"/*#pikchrshow-output*/);
160 CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
161 CX("</div>"/*sbs-wrapper*/);
 
 
 
 
 
 
 
 
162 if(!builtin_bundle_all_fossil_js_apis()){
163 builtin_emit_fossil_js_apis("dom", "fetch", "copybutton",
164 "popupwidget", 0);
165 }
166 builtin_emit_fossil_js_apis("page.pikchrshow", 0);
167 builtin_fulfill_js_requests();
168 style_footer();
169 }
170
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -70,100 +70,108 @@
70 "arrow right 200% \"HTML+SVG\" \"Output\"\n"
71 "arrow <-> down from last box.s\n"
72 "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n";
73 }
74 style_header("PikchrShow");
75 CX("<style>"); {
76 CX("div.content { padding-top: 0.5em }\n");
77 CX("#sbs-wrapper {"
78 "display: flex; flex-direction: column;"
79 "}\n");
80 CX("#sbs-wrapper > * {"
81 "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"
82 "align-self: stretch;"
83 "}\n");
84 CX("#sbs-wrapper textarea {"
85 "max-width: initial; flex: 1 1 auto;"
86 "}\n");
87 CX("#pikchrshow-output, #pikchrshow-form"
88 "{display: flex; flex-direction: column; align-items: stretch;}");
89 CX("#pikchrshow-form > * {margin: 0.25em 0}\n");
90 CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}\n");
91 CX("#pikchrshow-output > pre, "
92 "#pikchrshow-output > pre > div, "
93 "#pikchrshow-output > pre > div > pre "
94 "{margin: 0; padding: 0}\n");
95 CX("#pikchrshow-output.error > pre "
96 /* Server-side error report */
97 "{padding: 0.5em}\n");
98 CX("#pikchrshow-controls {" /* where the buttons live */
99 "display: flex; flex-direction: row; "
100 "align-items: center; flex-wrap: wrap;"
101 "}\n");
102 CX("#pikchrshow-controls > * {"
103 "display: inline; margin: 0 0.25em 0.5em 0;"
104 "}\n");
105 CX("#pikchrshow-output-wrapper label {"
106 "cursor: pointer;"
107 "}\n");
108 CX("body.pikchrshow .input-with-label > * {"
109 "margin: 0 0.2em;"
110 "}\n");
111 CX("body.pikchrshow .input-with-label > label {"
112 "cursor: pointer;"
113 "}\n");
114 CX("#pikchrshow-output.dark-mode svg {"
115 /* Flip the colors to approximate a dark theme look */
116 "filter: invert(1) hue-rotate(180deg);"
117 "}\n");
118 CX("#pikchrshow-output-wrapper {"
119 "padding: 0.25em 0.5em; border-radius: 0.25em;"
120 "border-width: 1px;"/*some skins disable fieldset borders*/
121 "}\n");
122 CX("#pikchrshow-output-wrapper > legend > *:not(.copy-button){"
123 "margin-right: 0.5em; vertical-align: middle;"
124 "}\n");
125 CX("body.pikchrshow .v-align-middle{"
126 "vertical-align: middle"
127 "}\n");
128 CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
129 } CX("</style>");
130 CX("<div>Input pikchr code and tap Preview to render it:</div>");
131 CX("<div id='sbs-wrapper'>"); {
132 CX("<div id='pikchrshow-form'>"); {
133 CX("<textarea id='content' name='content' rows='15'>"
134 "%s</textarea>",zContent/*safe-for-%s*/);
135 CX("<div id='pikchrshow-controls'>"); {
136 CX("<button id='pikchr-submit-preview'>Preview</button>");
137 CX("<div class='input-with-label'>"); {
138 CX("<button id='pikchr-stash'>Stash</button>");
139 CX("<button id='pikchr-unstash'>Unstash</button>");
140 CX("<button id='pikchr-clear-stash'>Clear stash</button>");
141 CX("<span>Stores/restores a single pikchr script to/from "
142 "browser-local storage from/to the editor.</span>"
143 /* gets turned into a help-buttonlet */);
144 } CX("</div>"/*stash controls*/);
145 style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
146 "Dark mode?",
147 "1", isDark, 0);
148 } CX("</div>"/*#pikchrshow-controls*/);
149 }
150 CX("</div>"/*#pikchrshow-form*/);
151 CX("<fieldset id='pikchrshow-output-wrapper'>"); {
152 CX("<legend></legend>"
153 /* Reminder: Firefox does not properly flexbox a LEGEND
154 element, always flowing it in column mode. */);
155 CX("<div id='pikchrshow-output'>");
156 if(*zContent){
157 int w = 0, h = 0;
158 char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
159 if( w>0 && h>0 ){
160 const char *zNonce = safe_html_nonce(1);
161 CX("%s<div style='max-width:%dpx;'>\n%s</div>%s",
162 zNonce, w, zOut, zNonce);
163 }else{
164 CX("<pre>\n%s\n</pre>\n", zOut);
165 }
166 fossil_free(zOut);
167 } CX("</div>"/*#pikchrshow-output*/);
168 } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
169 } CX("</div>"/*sbs-wrapper*/);
170 if(!builtin_bundle_all_fossil_js_apis()){
171 builtin_emit_fossil_js_apis("dom", "fetch", "copybutton",
172 "popupwidget", "storage", 0);
173 }
174 builtin_emit_fossil_js_apis("page.pikchrshow", 0);
175 builtin_fulfill_js_requests();
176 style_footer();
177 }
178

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button