Fossil SCM

pikchrshow: support 4 different preview modes, clipboard copy of previewed content, and markup alignment option (left/center).

stephan 2020-09-10 22:31 trunk
Commit d330c09135556fc98bbb709d54ae876ae1b172ed21c6f7b7da25ca8137e69e30
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -97,10 +97,11 @@
9797
dom.button = function(label){
9898
const b = this.create('button');
9999
if(label) b.appendChild(this.text(label));
100100
return b;
101101
};
102
+ dom.textarea = dom.createElemFactory('textarea');
102103
dom.select = dom.createElemFactory('select');
103104
/**
104105
Returns an OPTION element with the given value and label
105106
text (which defaults to the value).
106107
107108
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -97,10 +97,11 @@
97 dom.button = function(label){
98 const b = this.create('button');
99 if(label) b.appendChild(this.text(label));
100 return b;
101 };
 
102 dom.select = dom.createElemFactory('select');
103 /**
104 Returns an OPTION element with the given value and label
105 text (which defaults to the value).
106
107
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -97,10 +97,11 @@
97 dom.button = function(label){
98 const b = this.create('button');
99 if(label) b.appendChild(this.text(label));
100 return b;
101 };
102 dom.textarea = dom.createElemFactory('textarea');
103 dom.select = dom.createElemFactory('select');
104 /**
105 Returns an OPTION element with the given value and label
106 text (which defaults to the value).
107
108
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -1,50 +1,158 @@
11
(function(F/*the fossil object*/){
22
"use strict";
33
/**
44
Client-side implementation of the /pikchrshow app. Requires that
5
- the fossil JS bootstrapping is complete and that these fossil
6
- JS APIs have been installed: fossil.fetch, fossil.dom
5
+ the fossil JS bootstrapping is complete and that these fossil JS
6
+ APIs have been installed: fossil.fetch, fossil.dom,
7
+ fossil.copybutton
78
*/
89
const E = (s)=>document.querySelector(s),
910
D = F.dom,
1011
P = F.page;
12
+
13
+ P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown,
14
+ 2==pikchr text fossil, 3==raw SVG. */
15
+ P.response = {/*stashed state for the server's preview response*/
16
+ isError: false,
17
+ inputText: undefined /* value of the editor field at render-time */,
18
+ raw: undefined /* raw response text/HTML from server */
19
+ };
1120
F.onPageLoad(function() {
1221
document.body.classList.add('pikchrshow');
1322
P.e = { /* various DOM elements we work with... */
1423
previewTarget: E('#pikchrshow-output'),
24
+ previewModeLabel: E('#pikchrshow-output-wrapper > legend'),
25
+ btnCopy: E('#pikchrshow-output-wrapper > legend > .copy-button'),
1526
btnSubmit: E('#pikchr-submit-preview'),
1627
cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'),
17
- taContent: E('#content')
28
+ taContent: E('#content'),
29
+ taPreviewText: D.attr(D.textarea(), 'rows', 20, 'cols', 60,
30
+ 'readonly', true),
31
+ divControls: E('#pikchrshow-controls'),
32
+ btnTogglePreviewMode: D.button("Preview mode"),
33
+ selectMarkupAlignment: D.select()
1834
};
35
+ D.append(P.e.divControls, P.e.btnTogglePreviewMode);
36
+
37
+ // Setup markup alignment selection...
38
+ D.append(P.e.divControls, P.e.selectMarkupAlignment);
39
+ D.disable(D.option(P.e.selectMarkupAlignment, '', 'Markup Alignment'));
40
+ ['left', 'center'].forEach(function(val,ndx){
41
+ D.option(P.e.selectMarkupAlignment, ndx ? val : '', val);
42
+ });
43
+
44
+ // Setup clipboard-copy of markup/SVG...
45
+ F.copyButton(P.e.btnCopy, {copyFromElement: P.e.taPreviewText});
46
+ P.e.btnCopy.addEventListener('text-copied',function(ev){
47
+ D.flashOnce(ev.target);
48
+ },false);
1949
50
+ // Set up dark mode simulator...
2051
P.e.cbDarkMode.addEventListener('change', function(ev){
2152
if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
2253
else D.removeClass(P.e.previewTarget, 'dark-mode');
2354
}, false);
2455
if(P.e.cbDarkMode.checked) D.addClass(P.e.previewTarget, 'dark-mode');
2556
26
- P.e.btnSubmit.addEventListener('click', function(){
27
- P.preview();
57
+ // Set up preview update and preview mode toggle...
58
+ P.e.btnSubmit.addEventListener('click', ()=>P.preview(), false);
59
+ P.e.btnTogglePreviewMode.addEventListener('click', function(){
60
+ /* Rotate through the 4 available preview modes */
61
+ P.previewMode = ++P.previewMode % 4;
62
+ P.renderPreview();
63
+ }, false);
64
+ P.e.selectMarkupAlignment.addEventListener('change', function(ev){
65
+ /* Update markdown/fossil wiki preview if it's active */
66
+ if(P.previewMode==1 || P.previewMode==2){
67
+ P.renderPreview();
68
+ }
2869
}, false);
70
+
71
+ if(P.e.taContent.value/*was pre-filled server-side*/){
72
+ /* Fill our "response" state so that renderPreview() can work */
73
+ P.response.inputText = P.e.taContent.value;
74
+ P.response.raw = P.e.previewTarget.innerHTML;
75
+ P.renderPreview()/*not strictly necessary, but gets all
76
+ labels/headers in alignment.*/;
77
+ }
2978
}/*F.onPageLoad()*/);
3079
80
+ /**
81
+ Updates the preview view based on the current preview mode and
82
+ error state.
83
+ */
84
+ P.renderPreview = function f(){
85
+ if(!f.hasOwnProperty('rxNonce')){
86
+ f.rxNonce = /<!--.+-->\r?\n?/g /*nonce comments*/;
87
+ }
88
+ const preTgt = this.e.previewTarget;
89
+ if(this.response.isError){
90
+ preTgt.innerHTML = this.response.raw;
91
+ D.addClass(preTgt, 'error');
92
+ this.e.previewModeLabel.innerText = "Error";
93
+ return;
94
+ }
95
+ D.removeClass(preTgt, 'error');
96
+ D.removeClass(this.e.btnTogglePreviewMode, 'hidden');
97
+ let label;
98
+ switch(this.previewMode){
99
+ case 0:
100
+ label = "Rendered SVG";
101
+ preTgt.innerHTML = this.response.raw;
102
+ this.e.taPreviewText.value = this.response.raw.replace(f.rxNonce, '')/*for copy button*/;
103
+ break;
104
+ case 1:
105
+ label = "Markdown";
106
+ this.e.taPreviewText.value = [
107
+ '```pikchr'+(this.e.selectMarkupAlignment.value
108
+ ? ' '+this.e.selectMarkupAlignment.value : ''),
109
+ this.response.inputText, '```'
110
+ ].join('\n');
111
+ D.append(D.clearElement(preTgt), this.e.taPreviewText);
112
+ break;
113
+ case 2:
114
+ label = "Fossil wiki";
115
+ this.e.taPreviewText.value = [
116
+ '<verbatim type="pikchr',
117
+ this.e.selectMarkupAlignment.value ? ' '+this.e.selectMarkupAlignment.value : '',
118
+ '">', this.response.inputText, '</verbatim>'
119
+ ].join('');
120
+ D.append(D.clearElement(preTgt), this.e.taPreviewText);
121
+ break;
122
+ case 3:
123
+ label = "Raw SVG";
124
+ this.e.taPreviewText.value = this.response.raw.replace(f.rxNonce, '');
125
+ D.append(D.clearElement(preTgt), this.e.taPreviewText);
126
+ break;
127
+ }
128
+ D.append(D.clearElement(this.e.previewModeLabel),
129
+ label, this.e.btnCopy);
130
+ };
131
+
132
+ /** Fetches the preview from the server and updates the preview to
133
+ the rendered SVG content or error report. */
31134
P.preview = function fp(){
32135
if(!fp.hasOwnProperty('toDisable')){
33
- fp.toDisable = [
34
- P.e.btnSubmit, P.e.taContent
136
+ fp.toDisable = [ /* elements to disable during ajax operations */
137
+ this.e.btnSubmit, this.e.taContent,
138
+ this.e.btnTogglePreviewMode, this.e.selectMarkupAlignment,
35139
];
36
- fp.target = P.e.previewTarget;
140
+ fp.target = this.e.previewTarget;
37141
fp.updateView = function(c,isError){
142
+ P.previewMode = 0;
143
+ P.response.raw = c;
144
+ P.response.isError = isError;
38145
D.enable(fp.toDisable);
39
- fp.target.innerHTML = c || '';
40
- if(isError) D.addClass(fp.target, 'error');
41
- else D.removeClass(fp.target, 'error');
146
+ P.renderPreview();
42147
};
43148
}
44149
D.disable(fp.toDisable);
45
- const content = this.e.taContent.value;
150
+ D.addClass(this.e.btnTogglePreviewMode, 'hidden');
151
+ const content = this.e.taContent.value.trim();
152
+ this.response.raw = undefined;
153
+ this.response.inputText = content;
46154
if(!content){
47155
fp.updateView("No pikchr content!",true);
48156
return this;
49157
}
50158
const self = this;
51159
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -1,50 +1,158 @@
1 (function(F/*the fossil object*/){
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
6 JS APIs have been installed: fossil.fetch, fossil.dom
 
7 */
8 const E = (s)=>document.querySelector(s),
9 D = F.dom,
10 P = F.page;
 
 
 
 
 
 
 
 
11 F.onPageLoad(function() {
12 document.body.classList.add('pikchrshow');
13 P.e = { /* various DOM elements we work with... */
14 previewTarget: E('#pikchrshow-output'),
 
 
15 btnSubmit: E('#pikchr-submit-preview'),
16 cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'),
17 taContent: E('#content')
 
 
 
 
 
18 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20 P.e.cbDarkMode.addEventListener('change', function(ev){
21 if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
22 else D.removeClass(P.e.previewTarget, 'dark-mode');
23 }, false);
24 if(P.e.cbDarkMode.checked) D.addClass(P.e.previewTarget, 'dark-mode');
25
26 P.e.btnSubmit.addEventListener('click', function(){
27 P.preview();
 
 
 
 
 
 
 
 
 
 
28 }, false);
 
 
 
 
 
 
 
 
29 }/*F.onPageLoad()*/);
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31 P.preview = function fp(){
32 if(!fp.hasOwnProperty('toDisable')){
33 fp.toDisable = [
34 P.e.btnSubmit, P.e.taContent
 
35 ];
36 fp.target = P.e.previewTarget;
37 fp.updateView = function(c,isError){
 
 
 
38 D.enable(fp.toDisable);
39 fp.target.innerHTML = c || '';
40 if(isError) D.addClass(fp.target, 'error');
41 else D.removeClass(fp.target, 'error');
42 };
43 }
44 D.disable(fp.toDisable);
45 const content = this.e.taContent.value;
 
 
 
46 if(!content){
47 fp.updateView("No pikchr content!",true);
48 return this;
49 }
50 const self = this;
51
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -1,50 +1,158 @@
1 (function(F/*the fossil object*/){
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
8 */
9 const E = (s)=>document.querySelector(s),
10 D = F.dom,
11 P = F.page;
12
13 P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown,
14 2==pikchr text fossil, 3==raw SVG. */
15 P.response = {/*stashed state for the server's preview response*/
16 isError: false,
17 inputText: undefined /* value of the editor field at render-time */,
18 raw: undefined /* raw response text/HTML from server */
19 };
20 F.onPageLoad(function() {
21 document.body.classList.add('pikchrshow');
22 P.e = { /* various DOM elements we work with... */
23 previewTarget: E('#pikchrshow-output'),
24 previewModeLabel: E('#pikchrshow-output-wrapper > legend'),
25 btnCopy: E('#pikchrshow-output-wrapper > legend > .copy-button'),
26 btnSubmit: E('#pikchr-submit-preview'),
27 cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'),
28 taContent: E('#content'),
29 taPreviewText: D.attr(D.textarea(), 'rows', 20, 'cols', 60,
30 'readonly', true),
31 divControls: E('#pikchrshow-controls'),
32 btnTogglePreviewMode: D.button("Preview mode"),
33 selectMarkupAlignment: D.select()
34 };
35 D.append(P.e.divControls, P.e.btnTogglePreviewMode);
36
37 // Setup markup alignment selection...
38 D.append(P.e.divControls, P.e.selectMarkupAlignment);
39 D.disable(D.option(P.e.selectMarkupAlignment, '', 'Markup Alignment'));
40 ['left', 'center'].forEach(function(val,ndx){
41 D.option(P.e.selectMarkupAlignment, ndx ? val : '', val);
42 });
43
44 // Setup clipboard-copy of markup/SVG...
45 F.copyButton(P.e.btnCopy, {copyFromElement: P.e.taPreviewText});
46 P.e.btnCopy.addEventListener('text-copied',function(ev){
47 D.flashOnce(ev.target);
48 },false);
49
50 // Set up dark mode simulator...
51 P.e.cbDarkMode.addEventListener('change', function(ev){
52 if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
53 else D.removeClass(P.e.previewTarget, 'dark-mode');
54 }, false);
55 if(P.e.cbDarkMode.checked) D.addClass(P.e.previewTarget, 'dark-mode');
56
57 // Set up preview update and preview mode toggle...
58 P.e.btnSubmit.addEventListener('click', ()=>P.preview(), false);
59 P.e.btnTogglePreviewMode.addEventListener('click', function(){
60 /* Rotate through the 4 available preview modes */
61 P.previewMode = ++P.previewMode % 4;
62 P.renderPreview();
63 }, false);
64 P.e.selectMarkupAlignment.addEventListener('change', function(ev){
65 /* Update markdown/fossil wiki preview if it's active */
66 if(P.previewMode==1 || P.previewMode==2){
67 P.renderPreview();
68 }
69 }, false);
70
71 if(P.e.taContent.value/*was pre-filled server-side*/){
72 /* Fill our "response" state so that renderPreview() can work */
73 P.response.inputText = P.e.taContent.value;
74 P.response.raw = P.e.previewTarget.innerHTML;
75 P.renderPreview()/*not strictly necessary, but gets all
76 labels/headers in alignment.*/;
77 }
78 }/*F.onPageLoad()*/);
79
80 /**
81 Updates the preview view based on the current preview mode and
82 error state.
83 */
84 P.renderPreview = function f(){
85 if(!f.hasOwnProperty('rxNonce')){
86 f.rxNonce = /<!--.+-->\r?\n?/g /*nonce comments*/;
87 }
88 const preTgt = this.e.previewTarget;
89 if(this.response.isError){
90 preTgt.innerHTML = this.response.raw;
91 D.addClass(preTgt, 'error');
92 this.e.previewModeLabel.innerText = "Error";
93 return;
94 }
95 D.removeClass(preTgt, 'error');
96 D.removeClass(this.e.btnTogglePreviewMode, 'hidden');
97 let label;
98 switch(this.previewMode){
99 case 0:
100 label = "Rendered SVG";
101 preTgt.innerHTML = this.response.raw;
102 this.e.taPreviewText.value = this.response.raw.replace(f.rxNonce, '')/*for copy button*/;
103 break;
104 case 1:
105 label = "Markdown";
106 this.e.taPreviewText.value = [
107 '```pikchr'+(this.e.selectMarkupAlignment.value
108 ? ' '+this.e.selectMarkupAlignment.value : ''),
109 this.response.inputText, '```'
110 ].join('\n');
111 D.append(D.clearElement(preTgt), this.e.taPreviewText);
112 break;
113 case 2:
114 label = "Fossil wiki";
115 this.e.taPreviewText.value = [
116 '<verbatim type="pikchr',
117 this.e.selectMarkupAlignment.value ? ' '+this.e.selectMarkupAlignment.value : '',
118 '">', this.response.inputText, '</verbatim>'
119 ].join('');
120 D.append(D.clearElement(preTgt), this.e.taPreviewText);
121 break;
122 case 3:
123 label = "Raw SVG";
124 this.e.taPreviewText.value = this.response.raw.replace(f.rxNonce, '');
125 D.append(D.clearElement(preTgt), this.e.taPreviewText);
126 break;
127 }
128 D.append(D.clearElement(this.e.previewModeLabel),
129 label, this.e.btnCopy);
130 };
131
132 /** Fetches the preview from the server and updates the preview to
133 the rendered SVG content or error report. */
134 P.preview = function fp(){
135 if(!fp.hasOwnProperty('toDisable')){
136 fp.toDisable = [ /* elements to disable during ajax operations */
137 this.e.btnSubmit, this.e.taContent,
138 this.e.btnTogglePreviewMode, this.e.selectMarkupAlignment,
139 ];
140 fp.target = this.e.previewTarget;
141 fp.updateView = function(c,isError){
142 P.previewMode = 0;
143 P.response.raw = c;
144 P.response.isError = isError;
145 D.enable(fp.toDisable);
146 P.renderPreview();
 
 
147 };
148 }
149 D.disable(fp.toDisable);
150 D.addClass(this.e.btnTogglePreviewMode, 'hidden');
151 const content = this.e.taContent.value.trim();
152 this.response.raw = undefined;
153 this.response.inputText = content;
154 if(!content){
155 fp.updateView("No pikchr content!",true);
156 return this;
157 }
158 const self = this;
159
+21 -14
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -70,49 +70,55 @@
7070
CX("<style>");
7171
CX("div.content { padding-top: 0.5em }");
7272
CX("#sbs-wrapper {"
7373
"display: flex; flex-direction: row; flex-wrap: wrap;"
7474
"}");
75
- CX("#sbs-wrapper > * {margin: 0 0 1em 0}");
75
+ CX("#sbs-wrapper > * {"
76
+ "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"
77
+ "}");
78
+ CX("#sbs-wrapper textarea {max-width: initial}");
7679
CX("#pikchrshow-output, #pikchrshow-form"
77
- "{display: flex; flex-direction: column}");
78
- CX("#pikchrshow-form {flex: 2 1 auto}");
80
+ "{display: flex; flex-direction: column; align-items: stretch;}");
7981
CX("#pikchrshow-form > * {margin: 0.25em 0}");
80
- CX("#pikchrshow-output {"
81
- "flex: 1 1 auto; border-width: 1px; border-style: solid;"
82
- "border-radius: 0.25em; padding: 0.5em;"
83
- "}");
82
+ CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}");
8483
CX("#pikchrshow-output > pre, "
8584
"#pikchrshow-output > pre > div, "
8685
"#pikchrshow-output > pre > div > pre "
8786
"{margin: 0; padding: 0}");
88
- CX("#pikchrshow-controls {"
89
- "display: flex; flex-direction: row; align-items: center;"
87
+ CX("#pikchrshow-controls {" /* where the buttons live */
88
+ "display: flex; flex-direction: row; "
89
+ "align-items: center; flex-wrap: wrap;"
9090
"}");
9191
CX("#pikchrshow-controls > * {"
92
- "display: inline; margin-left: 0.5em;"
92
+ "display: inline; margin: 0 0.25em 0.5em 0;"
9393
"}");
9494
CX("#pikchrshow-controls > .input-with-label > * {"
9595
"cursor: pointer;"
9696
"}");
9797
CX("#pikchrshow-output.dark-mode > svg {"
9898
/* Flip the colors to approximate a dark theme look */
9999
"filter: invert(1) hue-rotate(180deg);"
100100
"}");
101
+ CX("#sbs-wrapper > fieldset {"
102
+ "padding: 0.25em 0.5em; border-radius: 0.25em;"
103
+ "}");
104
+ CX("fieldset > legend > .copy-button {margin-left: 0.25em}");
101105
CX("</style>");
102106
CX("<div>Input pikchr code and tap Preview to render it:</div>");
103107
CX("<div id='sbs-wrapper'>");
104108
CX("<div id='pikchrshow-form'>");
105
- CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
106
- zContent/*safe-for-%s*/);
107109
CX("<div id='pikchrshow-controls'>");
108110
CX("<button id='pikchr-submit-preview'>Preview</button>");
109111
style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
110
- "Simulate dark color theme?",
112
+ "Simulate dark theme?",
111113
"1", flipColors, 0);
112114
CX("</div>"/*#pikchrshow-controls*/);
115
+ CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
116
+ zContent/*safe-for-%s*/);
113117
CX("</div>"/*#pikchrshow-form*/);
118
+ CX("<fieldset id='pikchrshow-output-wrapper'>");
119
+ CX("<legend>Preview <span class='copy-button'></span></legend>");
114120
CX("<div id='pikchrshow-output'>");
115121
if(*zContent){
116122
int w = 0, h = 0;
117123
char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
118124
if( w>0 && h>0 ){
@@ -122,14 +128,15 @@
122128
CX("<pre>\n%s\n</pre>\n", zOut);
123129
}
124130
fossil_free(zOut);
125131
}
126132
CX("</div>"/*#pikchrshow-output*/);
133
+ CX("</fieldset>");
127134
CX("</div>"/*sbs-wrapper*/);
128135
if(!builtin_bundle_all_fossil_js_apis()){
129
- builtin_emit_fossil_js_apis("dom", "fetch", 0);
136
+ builtin_emit_fossil_js_apis("dom", "fetch", "copybutton", 0);
130137
}
131138
builtin_emit_fossil_js_apis("page.pikchrshow", 0);
132139
builtin_fulfill_js_requests();
133140
style_footer();
134141
}
135142
136143
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -70,49 +70,55 @@
70 CX("<style>");
71 CX("div.content { padding-top: 0.5em }");
72 CX("#sbs-wrapper {"
73 "display: flex; flex-direction: row; flex-wrap: wrap;"
74 "}");
75 CX("#sbs-wrapper > * {margin: 0 0 1em 0}");
 
 
 
76 CX("#pikchrshow-output, #pikchrshow-form"
77 "{display: flex; flex-direction: column}");
78 CX("#pikchrshow-form {flex: 2 1 auto}");
79 CX("#pikchrshow-form > * {margin: 0.25em 0}");
80 CX("#pikchrshow-output {"
81 "flex: 1 1 auto; border-width: 1px; border-style: solid;"
82 "border-radius: 0.25em; padding: 0.5em;"
83 "}");
84 CX("#pikchrshow-output > pre, "
85 "#pikchrshow-output > pre > div, "
86 "#pikchrshow-output > pre > div > pre "
87 "{margin: 0; padding: 0}");
88 CX("#pikchrshow-controls {"
89 "display: flex; flex-direction: row; align-items: center;"
 
90 "}");
91 CX("#pikchrshow-controls > * {"
92 "display: inline; margin-left: 0.5em;"
93 "}");
94 CX("#pikchrshow-controls > .input-with-label > * {"
95 "cursor: pointer;"
96 "}");
97 CX("#pikchrshow-output.dark-mode > svg {"
98 /* Flip the colors to approximate a dark theme look */
99 "filter: invert(1) hue-rotate(180deg);"
100 "}");
 
 
 
 
101 CX("</style>");
102 CX("<div>Input pikchr code and tap Preview to render it:</div>");
103 CX("<div id='sbs-wrapper'>");
104 CX("<div id='pikchrshow-form'>");
105 CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
106 zContent/*safe-for-%s*/);
107 CX("<div id='pikchrshow-controls'>");
108 CX("<button id='pikchr-submit-preview'>Preview</button>");
109 style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
110 "Simulate dark color theme?",
111 "1", flipColors, 0);
112 CX("</div>"/*#pikchrshow-controls*/);
 
 
113 CX("</div>"/*#pikchrshow-form*/);
 
 
114 CX("<div id='pikchrshow-output'>");
115 if(*zContent){
116 int w = 0, h = 0;
117 char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
118 if( w>0 && h>0 ){
@@ -122,14 +128,15 @@
122 CX("<pre>\n%s\n</pre>\n", zOut);
123 }
124 fossil_free(zOut);
125 }
126 CX("</div>"/*#pikchrshow-output*/);
 
127 CX("</div>"/*sbs-wrapper*/);
128 if(!builtin_bundle_all_fossil_js_apis()){
129 builtin_emit_fossil_js_apis("dom", "fetch", 0);
130 }
131 builtin_emit_fossil_js_apis("page.pikchrshow", 0);
132 builtin_fulfill_js_requests();
133 style_footer();
134 }
135
136
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -70,49 +70,55 @@
70 CX("<style>");
71 CX("div.content { padding-top: 0.5em }");
72 CX("#sbs-wrapper {"
73 "display: flex; flex-direction: row; flex-wrap: wrap;"
74 "}");
75 CX("#sbs-wrapper > * {"
76 "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"
77 "}");
78 CX("#sbs-wrapper textarea {max-width: initial}");
79 CX("#pikchrshow-output, #pikchrshow-form"
80 "{display: flex; flex-direction: column; align-items: stretch;}");
 
81 CX("#pikchrshow-form > * {margin: 0.25em 0}");
82 CX("#pikchrshow-output {flex: 5 1 auto; padding: 0}");
 
 
 
83 CX("#pikchrshow-output > pre, "
84 "#pikchrshow-output > pre > div, "
85 "#pikchrshow-output > pre > div > pre "
86 "{margin: 0; padding: 0}");
87 CX("#pikchrshow-controls {" /* where the buttons live */
88 "display: flex; flex-direction: row; "
89 "align-items: center; flex-wrap: wrap;"
90 "}");
91 CX("#pikchrshow-controls > * {"
92 "display: inline; margin: 0 0.25em 0.5em 0;"
93 "}");
94 CX("#pikchrshow-controls > .input-with-label > * {"
95 "cursor: pointer;"
96 "}");
97 CX("#pikchrshow-output.dark-mode > svg {"
98 /* Flip the colors to approximate a dark theme look */
99 "filter: invert(1) hue-rotate(180deg);"
100 "}");
101 CX("#sbs-wrapper > fieldset {"
102 "padding: 0.25em 0.5em; border-radius: 0.25em;"
103 "}");
104 CX("fieldset > legend > .copy-button {margin-left: 0.25em}");
105 CX("</style>");
106 CX("<div>Input pikchr code and tap Preview to render it:</div>");
107 CX("<div id='sbs-wrapper'>");
108 CX("<div id='pikchrshow-form'>");
 
 
109 CX("<div id='pikchrshow-controls'>");
110 CX("<button id='pikchr-submit-preview'>Preview</button>");
111 style_labeled_checkbox("flipcolors-wrapper", "flipcolors",
112 "Simulate dark theme?",
113 "1", flipColors, 0);
114 CX("</div>"/*#pikchrshow-controls*/);
115 CX("<textarea id='content' name='content' rows='15'>%s</textarea>",
116 zContent/*safe-for-%s*/);
117 CX("</div>"/*#pikchrshow-form*/);
118 CX("<fieldset id='pikchrshow-output-wrapper'>");
119 CX("<legend>Preview <span class='copy-button'></span></legend>");
120 CX("<div id='pikchrshow-output'>");
121 if(*zContent){
122 int w = 0, h = 0;
123 char *zOut = pikchr(zContent, "pikchr", 0, &w, &h);
124 if( w>0 && h>0 ){
@@ -122,14 +128,15 @@
128 CX("<pre>\n%s\n</pre>\n", zOut);
129 }
130 fossil_free(zOut);
131 }
132 CX("</div>"/*#pikchrshow-output*/);
133 CX("</fieldset>");
134 CX("</div>"/*sbs-wrapper*/);
135 if(!builtin_bundle_all_fossil_js_apis()){
136 builtin_emit_fossil_js_apis("dom", "fetch", "copybutton", 0);
137 }
138 builtin_emit_fossil_js_apis("page.pikchrshow", 0);
139 builtin_fulfill_js_requests();
140 style_footer();
141 }
142
143

Keyboard Shortcuts

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