Fossil SCM

Renovated the pikchr click handling as discussed off-list with drh.

stephan 2020-09-17 23:11 trunk
Commit 938bb6c7089c43e53ed6387d7afcd58a206c25db2d6c70a9c86e5bf70fd289cc
--- src/default.css
+++ src/default.css
@@ -1388,9 +1388,7 @@
13881388
padding: 1em;
13891389
font-size: 150%;
13901390
}
13911391
.pikchr-src { /* source code view for a pikchr (see fossil.pikchr.js) */
13921392
box-sizing: border-box/*reduces UI shift*/;
1393
- border-width: 1px;
1394
- border-style: dotted;
13951393
overflow: auto;
13961394
}
13971395
--- src/default.css
+++ src/default.css
@@ -1388,9 +1388,7 @@
1388 padding: 1em;
1389 font-size: 150%;
1390 }
1391 .pikchr-src { /* source code view for a pikchr (see fossil.pikchr.js) */
1392 box-sizing: border-box/*reduces UI shift*/;
1393 border-width: 1px;
1394 border-style: dotted;
1395 overflow: auto;
1396 }
1397
--- src/default.css
+++ src/default.css
@@ -1388,9 +1388,7 @@
1388 padding: 1em;
1389 font-size: 150%;
1390 }
1391 .pikchr-src { /* source code view for a pikchr (see fossil.pikchr.js) */
1392 box-sizing: border-box/*reduces UI shift*/;
 
 
1393 overflow: auto;
1394 }
1395
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -713,12 +713,12 @@
713713
/**
714714
Parses a string as HTML.
715715
716716
Usages:
717717
718
- (htmlString)
719
- (DOMElement target, htmlString)
718
+ Array (htmlString)
719
+ DOMElement (DOMElement target, htmlString)
720720
721721
The first form parses the string as HTML and returns an Array of
722722
all elements parsed from it. If string is falsy then it returns
723723
an empty array.
724724
725725
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -713,12 +713,12 @@
713 /**
714 Parses a string as HTML.
715
716 Usages:
717
718 (htmlString)
719 (DOMElement target, htmlString)
720
721 The first form parses the string as HTML and returns an Array of
722 all elements parsed from it. If string is falsy then it returns
723 an empty array.
724
725
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -713,12 +713,12 @@
713 /**
714 Parses a string as HTML.
715
716 Usages:
717
718 Array (htmlString)
719 DOMElement (DOMElement target, htmlString)
720
721 The first form parses the string as HTML and returns an Array of
722 all elements parsed from it. If string is falsy then it returns
723 an empty array.
724
725
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -322,13 +322,14 @@
322322
switch(this.previewMode){
323323
case 0:
324324
label = "SVG";
325325
f.showMarkupAlignment(false);
326326
D.parseHtml(D.clearElement(preTgt), P.response.raw);
327
- svg = f.getSvgNode(this.response.raw);
327
+ svg = preTgt.querySelector('svg.pikchr');
328328
if(svg){ /*for copy button*/
329329
this.e.taPreviewText.value = svg.outerHTML;
330
+ F.pikchr.addSrcView(svg);
330331
}
331332
break;
332333
case 1:
333334
label = "Markdown";
334335
f.showMarkupAlignment(true);
335336
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -322,13 +322,14 @@
322 switch(this.previewMode){
323 case 0:
324 label = "SVG";
325 f.showMarkupAlignment(false);
326 D.parseHtml(D.clearElement(preTgt), P.response.raw);
327 svg = f.getSvgNode(this.response.raw);
328 if(svg){ /*for copy button*/
329 this.e.taPreviewText.value = svg.outerHTML;
 
330 }
331 break;
332 case 1:
333 label = "Markdown";
334 f.showMarkupAlignment(true);
335
--- src/fossil.page.pikchrshow.js
+++ src/fossil.page.pikchrshow.js
@@ -322,13 +322,14 @@
322 switch(this.previewMode){
323 case 0:
324 label = "SVG";
325 f.showMarkupAlignment(false);
326 D.parseHtml(D.clearElement(preTgt), P.response.raw);
327 svg = preTgt.querySelector('svg.pikchr');
328 if(svg){ /*for copy button*/
329 this.e.taPreviewText.value = svg.outerHTML;
330 F.pikchr.addSrcView(svg);
331 }
332 break;
333 case 1:
334 label = "Markdown";
335 f.showMarkupAlignment(true);
336
--- src/fossil.pikchr.js
+++ src/fossil.pikchr.js
@@ -1,240 +1,88 @@
11
(function(F/*window.fossil object*/){
22
"use strict";
3
-
4
- const D = F.dom;
5
-
6
- const P = F.pikchr = {
7
- };
8
-
9
- ////////////////////////////////////////////////////////////////////////
10
- // Install an app-specific stylesheet, just for development, after which
11
- // it will be moved into default.css
12
- (function(){
13
- const head = document.head || document.querySelector('head'),
14
- styleTag = document.createElement('style'),
15
- wh = '1cm' /* fixed width/height of buttons */,
16
- styleCSS = `
17
-.pikchr-button-bar {
18
- position: absolute;
19
- position: absolute;
20
- top: 0;
21
- left: 0;
22
- display: inline-flex;
23
- flex-direction: column;
24
-}
25
-.pikchr-src-button {
26
- min-height: ${wh}; max-height: ${wh};
27
- min-width: ${wh}; max-width: ${wh};
28
- font-size: ${wh};
29
- border: 1px solid black;
30
- background-color: rgba(255,255,0,0.7);
31
- border-radius: 0.25cm;
32
- z-index: 50;
33
- cursor: pointer;
34
- text-align: center;
35
- display: inline-flex;
36
- align-items: center;
37
- justify-content: center;
38
- transform-origin: center;
39
- transition: transform 250ms linear;
40
- padding: 0; margin: 0;
41
-/* MIT-licensed SVG from: https://github.com/leungwensen/svg-icon/blob/master/dist/svg/ant/code.svg */
42
- background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg viewBox='0 0 1195 1195' \
43
-xmlns='http:/\x2fwww.w3.org/2000/svg'%3e%3cpath d='M321.333 440q-9-10-22.5-10t-22.5 \
44
-10l-182 181q-9 9-9 22.5t9 22.5l182 182q9 10 22.5 10t22.5-10q10-9 10-22.5t-10-22.5l-159-159 \
45
-159-159q10-10 10-23t-10-22zm552 0q9-10 22.5-10t22.5 10l182 181q9 9 9 22.5t-9 \
46
-22.5l-182 182q-9 10-22.5 10t-22.5-10q-10-9-10-22.5t10-22.5l159-159-159-159q-10-10-10-23t10-22zm-97-180q12 \
47
-6 16 19t-2 24l-371 704q-7 12-19.5 16t-24.5-2q-11-7-15-19.5t2-24.5l371-703q6-12 \
48
-18.5-16t24.5 2z'/%3e%3c/svg%3e");
49
- background-size: contain;
50
-}
51
-.pikchr-src-button.src-active {
52
- transform: scaleX(-1);
53
-/* MIT-licensed SVG from: https://github.com/leungwensen/svg-icon/blob/master/dist/svg/ant/picture.svg */
54
- background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg viewBox='0 0 1195 1195' \
55
-xmlns='http:/\x2fwww.w3.org/2000/svg'%3e%3cpath d='M1045.333 192h-896q-26 0-45 \
56
-19t-19 45v768q0 26 19 45t45 19h896q26 0 45-19t19-45V256q0-26-19-45t-45-19zm-896 \
57
-64h896v714l-236-348q-7-12-21-14t-25 7l-154 125-174-243q-10-14-28-13t-26 17l-232 \
58
-448V256zm855 768h-822l231-447 184 255 179-145zm-182-642q13 0 22.5 9.5t9.5 22.5-9.5 \
59
-22.5-22.5 9.5-22.5-9.5-9.5-22.5 9.5-22.5 22.5-9.5zm0-64q-40 0-68 28t-28 68 28 68 68 \
60
-28 68-28 28-68-28-68-68-28z'/%3e%3c/svg%3e");
61
-}
62
-textarea.pikchr-src-text {
63
- box-sizing: border-box/*reduces UI shift*/;
64
-}
65
-.pikchr-copy-button {
66
- min-width: ${wh}; max-width: ${wh};
67
- min-height: ${wh}; max-height: ${wh};
68
- display: inline-block;
69
-}
70
-.pikchr-button-bar > .pikchr-src-button,
71
-.pikchr-button-bar > .pikchr-copy-button {
72
- margin-bottom: 0.3em;
73
-}
74
-`;
75
- head.appendChild(styleTag);
76
- /* Adapted from https://stackoverflow.com/a/524721 */
77
- styleTag.type = 'text/css';
78
- D.append(styleTag, styleCSS);
79
- })();
3
+ const D = F.dom, P = F.pikchr = {};
804
815
/**
82
- Sets up a "view source" button on one or more pikchr-created SVG
83
- image elements.
6
+ Initializes pikchr-rendered elements with the ability to
7
+ toggle between their SVG and source code.
848
859
The first argument may be any of:
8610
87
- - A single SVG element.
11
+ - A single SVG.pikchr element.
8812
8913
- A collection (with a forEach method) of such elements.
9014
9115
- A CSS selector string for one or more such elements.
9216
9317
- An array of such strings.
9418
9519
Passing no value is equivalent to passing 'svg.pikchr'.
9620
97
- For each SVG in the resulting set, this function does the
98
- following:
99
-
100
- - It sets the "position" value of the element's *parent* node to
101
- "relative", as that is necessary for what follows.
102
-
103
- - It creates a small pseudo-button, adding it to the SVG
104
- element's parent node, styled to hover in one of the element's
105
- corners.
106
-
107
- - That button, when tapped, toggles the SVG on and off
108
- while revealing or hiding a readonly textarea element
109
- which contains the source code for that pikchr SVG
110
- (which pikchr has helpfully embedded in the SVG's
111
- metadata).
21
+ For each SVG in the resulting set, this function sets up event
22
+ handlers which allow the user to toggle the SVG between image and
23
+ source code modes. The image will switch modes in response to
24
+ cltr-click and, if its *parent* element has the "toggle" CSS
25
+ class, it will also switch modes in response to single-click.
26
+
27
+ If the parent element has the "source" CSS class, the image
28
+ starts off with its source code visible and the image hidden,
29
+ instead of the default of the other way around.
11230
11331
Returns this object.
11432
115
- The 2nd argument is intended to be a plain options object, but it
116
- is currently unused, as it's not yet clear what we can/should
117
- make configurable.
118
-
11933
Each element will only be processed once by this routine, even if
12034
it is passed to this function multiple times. Each processed
12135
element gets a "data" attribute set to it to indicate that it was
12236
already dealt with.
37
+
38
+ This code expects the following structure around the SVGs, and
39
+ will not process any which don't match this:
40
+
41
+ <DIV><SVG.pikchr></SVG><PRE.pikchr-src></PRE></DIV>
12342
*/
124
- P.addSrcView = function f(svg,opt){
125
- if(!f.hasOwnProperty('bodyClick')){
126
- f.bodyClick = function(){
127
- D.addClass(document.querySelectorAll('.pikchr-button-bar'), 'hidden');
43
+ P.addSrcView = function f(svg){
44
+ if(!f.hasOwnProperty('parentClick')){
45
+ f.parentClick = function(ev){
46
+ if(ev.ctrlKey || this.classList.contains('toggle')){
47
+ this._childs.forEach((e)=>e.classList.toggle('hidden'));
48
+ }
49
+ /* For the sake of small pics, we have to eliminate the
50
+ parent element's max-width... */
51
+ const src = this._childs[1];
52
+ if(src.classList.contains('hidden')){
53
+ this.style.maxWidth = this.dataset.origMaxWidth;
54
+ }else{
55
+ this.style.maxWidth = "unset";
56
+ }
12857
};
129
- document.body.addEventListener('click', f.bodyClick, false);
130
- }
58
+ };
13159
if(!svg) svg = 'svg.pikchr';
13260
if('string' === typeof svg){
133
- document.querySelectorAll(svg).forEach(
134
- (e)=>f.call(this, e, opt)
135
- );
61
+ document.querySelectorAll(svg).forEach((e)=>f.call(this, e));
13662
return this;
13763
}else if(svg.forEach){
138
- svg.forEach((e)=>f.call(this, e, opt));
64
+ svg.forEach((e)=>f.call(this, e));
13965
return this;
14066
}
14167
if(svg.dataset.pikchrProcessed){
14268
return this;
14369
}
14470
svg.dataset.pikchrProcessed = 1;
14571
const parent = svg.parentNode;
146
- parent.style.position = 'relative' /* REQUIRED for btn placement */;
147
- const srcView = parent.querySelector('.pikchr-src');
148
- if(!srcView){
149
- console.warn("No pikchr source node found in",parent);
72
+ const srcView = svg.nextElementSibling;
73
+ if(!srcView || !srcView.classList.contains('pikchr-src')){
74
+ /* Without this element, there's nothing for us to do here. */
15075
return this;
15176
}
152
- const buttonBar = D.addClass(D.span(), 'pikchr-button-bar');
153
- const btnFlip = D.append(
154
- D.addClass(D.span(), 'pikchr-src-button'),
155
- );
156
- const btnCopy = F.copyButton(D.span(), {
157
- cssClass: ['copy-button', 'pikchr-copy-button'],
158
- extractText: ()=>(srcView.classList.contains('hidden') ? svg.outerHTML : srcView.value),
159
- oncopy: ()=>D.flashOnce(btnCopy, ()=>btnFlip.click())
160
- // ^^^ after copying and flashing, flip back to SVG mode. */
161
- });
162
- D.append(buttonBar, btnFlip, btnCopy);
163
- // not yet sure which options we can/should support:
164
- // opt = F.mergeLastWins({},opt);
165
- D.addClass(srcView, 'hidden')/*should already be so, but just in case*/;
166
- D.append(parent, D.addClass(buttonBar, 'hidden'));
167
-
168
- parent.addEventListener('click', function f(ev){
169
- ev.preventDefault();
170
- ev.stopPropagation();
171
- D.toggleClass(buttonBar, 'hidden');
172
- }, false);
173
-
174
- /** Show the mode-switch buttons only in source view, and switch to
175
- source view if the SVG is tapped. This allows easy switching to
176
- source view while also keeping the buttons out of the way in
177
- SVG mode and giving the user the option of select/copy in the
178
- source mode via normal text-selection approaches. */
179
- svg.addEventListener('click', function(ev){
180
- D.removeClass(buttonBar, 'hidden');
181
- btnFlip.click();
182
- }, false);
183
-
184
- /** Toggle the source/SVG view on click. */
185
- btnFlip.addEventListener('click', function f(ev){
186
- ev.preventDefault();
187
- ev.stopPropagation();
188
- if(!f.hasOwnProperty('origMaxWidth')){
189
- f.origMaxWidth = parent.style.maxWidth;
190
- }
191
- const svgStyle = window.getComputedStyle(svg);
192
- srcView.style.minWidth = svgStyle.width;
193
- srcView.style.minHeight = svgStyle.height;
194
- /* ^^^ The SVG wrapper/parent element has a max-width, so the
195
- textarea will be too small on tiny images and won't be
196
- enlargable. */
197
- if(0){
198
- /* We seem to have a fundamental incompatibility with how we
199
- really want to position srcView at the same pos/size as the
200
- svg and how that interacts with centered items.
201
- Until/unless this can be solved, we have to decide between
202
- the lesser of two evils:
203
-
204
- 1) This option. Small images have uselessly tiny source
205
- view which cannot be enlarged because the parent element
206
- has a width and/or max-width. width/max-width are important
207
- for center alignment via the margin:auto trick.
208
-
209
- 2) Center-aligned images shift all the way to the left when
210
- the source view is visible, then back to the center when
211
- source view is hidden. Source views are resizable and may
212
- even grow a bit automatically for tiny images.
213
- */
214
- if(srcView.classList.contains('hidden')){/*initial state*/
215
- parent.style.width = f.origMaxWidth;
216
- parent.style.maxWidth = 'unset';
217
- }else{/*srcView is active*/
218
- parent.style.maxWidth = f.origMaxWidth;
219
- parent.style.width = 'unset';
220
- }
221
- }else if(1){
222
- /* Option #2: gives us good results for non-centered items but
223
- not for centered. We apparently have no(?) reliable way of
224
- distinguishing centered from left/indented pikchrs here
225
- unless we add a CSS class to mark them as such in the
226
- pikchr-to-wiki-image code. */
227
- if(srcView.classList.contains('hidden')){/*initial state*/
228
- parent.style.width = 'unset';
229
- parent.style.maxWidth = 'unset';
230
- }else{/*srcView is active*/
231
- parent.style.maxWidth = f.origMaxWidth;
232
- parent.style.width = 'unset';
233
- }
234
- }
235
- btnFlip.classList.toggle('src-active');
236
- D.toggleClass([svg, srcView, buttonBar], 'hidden');
237
- }, false);
77
+ console.debug(svg, parent, srcView);
78
+ parent.dataset.origMaxWidth = parent.style.maxWidth;
79
+ parent._childs = [svg, srcView];
80
+ D.addClass(srcView, 'hidden');
81
+ D.removeClass(svg, 'hidden');
82
+ parent.addEventListener('click', f.parentClick, false);
83
+ if(parent.classList.contains('source')){
84
+ /* Start off in source-view mode via a very fake click event */
85
+ f.parentClick.call(parent, {ctrlKey:true});
86
+ }
23887
};
239
-
24088
})(window.fossil);
24189
--- src/fossil.pikchr.js
+++ src/fossil.pikchr.js
@@ -1,240 +1,88 @@
1 (function(F/*window.fossil object*/){
2 "use strict";
3
4 const D = F.dom;
5
6 const P = F.pikchr = {
7 };
8
9 ////////////////////////////////////////////////////////////////////////
10 // Install an app-specific stylesheet, just for development, after which
11 // it will be moved into default.css
12 (function(){
13 const head = document.head || document.querySelector('head'),
14 styleTag = document.createElement('style'),
15 wh = '1cm' /* fixed width/height of buttons */,
16 styleCSS = `
17 .pikchr-button-bar {
18 position: absolute;
19 position: absolute;
20 top: 0;
21 left: 0;
22 display: inline-flex;
23 flex-direction: column;
24 }
25 .pikchr-src-button {
26 min-height: ${wh}; max-height: ${wh};
27 min-width: ${wh}; max-width: ${wh};
28 font-size: ${wh};
29 border: 1px solid black;
30 background-color: rgba(255,255,0,0.7);
31 border-radius: 0.25cm;
32 z-index: 50;
33 cursor: pointer;
34 text-align: center;
35 display: inline-flex;
36 align-items: center;
37 justify-content: center;
38 transform-origin: center;
39 transition: transform 250ms linear;
40 padding: 0; margin: 0;
41 /* MIT-licensed SVG from: https://github.com/leungwensen/svg-icon/blob/master/dist/svg/ant/code.svg */
42 background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg viewBox='0 0 1195 1195' \
43 xmlns='http:/\x2fwww.w3.org/2000/svg'%3e%3cpath d='M321.333 440q-9-10-22.5-10t-22.5 \
44 10l-182 181q-9 9-9 22.5t9 22.5l182 182q9 10 22.5 10t22.5-10q10-9 10-22.5t-10-22.5l-159-159 \
45 159-159q10-10 10-23t-10-22zm552 0q9-10 22.5-10t22.5 10l182 181q9 9 9 22.5t-9 \
46 22.5l-182 182q-9 10-22.5 10t-22.5-10q-10-9-10-22.5t10-22.5l159-159-159-159q-10-10-10-23t10-22zm-97-180q12 \
47 6 16 19t-2 24l-371 704q-7 12-19.5 16t-24.5-2q-11-7-15-19.5t2-24.5l371-703q6-12 \
48 18.5-16t24.5 2z'/%3e%3c/svg%3e");
49 background-size: contain;
50 }
51 .pikchr-src-button.src-active {
52 transform: scaleX(-1);
53 /* MIT-licensed SVG from: https://github.com/leungwensen/svg-icon/blob/master/dist/svg/ant/picture.svg */
54 background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg viewBox='0 0 1195 1195' \
55 xmlns='http:/\x2fwww.w3.org/2000/svg'%3e%3cpath d='M1045.333 192h-896q-26 0-45 \
56 19t-19 45v768q0 26 19 45t45 19h896q26 0 45-19t19-45V256q0-26-19-45t-45-19zm-896 \
57 64h896v714l-236-348q-7-12-21-14t-25 7l-154 125-174-243q-10-14-28-13t-26 17l-232 \
58 448V256zm855 768h-822l231-447 184 255 179-145zm-182-642q13 0 22.5 9.5t9.5 22.5-9.5 \
59 22.5-22.5 9.5-22.5-9.5-9.5-22.5 9.5-22.5 22.5-9.5zm0-64q-40 0-68 28t-28 68 28 68 68 \
60 28 68-28 28-68-28-68-68-28z'/%3e%3c/svg%3e");
61 }
62 textarea.pikchr-src-text {
63 box-sizing: border-box/*reduces UI shift*/;
64 }
65 .pikchr-copy-button {
66 min-width: ${wh}; max-width: ${wh};
67 min-height: ${wh}; max-height: ${wh};
68 display: inline-block;
69 }
70 .pikchr-button-bar > .pikchr-src-button,
71 .pikchr-button-bar > .pikchr-copy-button {
72 margin-bottom: 0.3em;
73 }
74 `;
75 head.appendChild(styleTag);
76 /* Adapted from https://stackoverflow.com/a/524721 */
77 styleTag.type = 'text/css';
78 D.append(styleTag, styleCSS);
79 })();
80
81 /**
82 Sets up a "view source" button on one or more pikchr-created SVG
83 image elements.
84
85 The first argument may be any of:
86
87 - A single SVG element.
88
89 - A collection (with a forEach method) of such elements.
90
91 - A CSS selector string for one or more such elements.
92
93 - An array of such strings.
94
95 Passing no value is equivalent to passing 'svg.pikchr'.
96
97 For each SVG in the resulting set, this function does the
98 following:
99
100 - It sets the "position" value of the element's *parent* node to
101 "relative", as that is necessary for what follows.
102
103 - It creates a small pseudo-button, adding it to the SVG
104 element's parent node, styled to hover in one of the element's
105 corners.
106
107 - That button, when tapped, toggles the SVG on and off
108 while revealing or hiding a readonly textarea element
109 which contains the source code for that pikchr SVG
110 (which pikchr has helpfully embedded in the SVG's
111 metadata).
112
113 Returns this object.
114
115 The 2nd argument is intended to be a plain options object, but it
116 is currently unused, as it's not yet clear what we can/should
117 make configurable.
118
119 Each element will only be processed once by this routine, even if
120 it is passed to this function multiple times. Each processed
121 element gets a "data" attribute set to it to indicate that it was
122 already dealt with.
 
 
 
 
 
123 */
124 P.addSrcView = function f(svg,opt){
125 if(!f.hasOwnProperty('bodyClick')){
126 f.bodyClick = function(){
127 D.addClass(document.querySelectorAll('.pikchr-button-bar'), 'hidden');
 
 
 
 
 
 
 
 
 
 
128 };
129 document.body.addEventListener('click', f.bodyClick, false);
130 }
131 if(!svg) svg = 'svg.pikchr';
132 if('string' === typeof svg){
133 document.querySelectorAll(svg).forEach(
134 (e)=>f.call(this, e, opt)
135 );
136 return this;
137 }else if(svg.forEach){
138 svg.forEach((e)=>f.call(this, e, opt));
139 return this;
140 }
141 if(svg.dataset.pikchrProcessed){
142 return this;
143 }
144 svg.dataset.pikchrProcessed = 1;
145 const parent = svg.parentNode;
146 parent.style.position = 'relative' /* REQUIRED for btn placement */;
147 const srcView = parent.querySelector('.pikchr-src');
148 if(!srcView){
149 console.warn("No pikchr source node found in",parent);
150 return this;
151 }
152 const buttonBar = D.addClass(D.span(), 'pikchr-button-bar');
153 const btnFlip = D.append(
154 D.addClass(D.span(), 'pikchr-src-button'),
155 );
156 const btnCopy = F.copyButton(D.span(), {
157 cssClass: ['copy-button', 'pikchr-copy-button'],
158 extractText: ()=>(srcView.classList.contains('hidden') ? svg.outerHTML : srcView.value),
159 oncopy: ()=>D.flashOnce(btnCopy, ()=>btnFlip.click())
160 // ^^^ after copying and flashing, flip back to SVG mode. */
161 });
162 D.append(buttonBar, btnFlip, btnCopy);
163 // not yet sure which options we can/should support:
164 // opt = F.mergeLastWins({},opt);
165 D.addClass(srcView, 'hidden')/*should already be so, but just in case*/;
166 D.append(parent, D.addClass(buttonBar, 'hidden'));
167
168 parent.addEventListener('click', function f(ev){
169 ev.preventDefault();
170 ev.stopPropagation();
171 D.toggleClass(buttonBar, 'hidden');
172 }, false);
173
174 /** Show the mode-switch buttons only in source view, and switch to
175 source view if the SVG is tapped. This allows easy switching to
176 source view while also keeping the buttons out of the way in
177 SVG mode and giving the user the option of select/copy in the
178 source mode via normal text-selection approaches. */
179 svg.addEventListener('click', function(ev){
180 D.removeClass(buttonBar, 'hidden');
181 btnFlip.click();
182 }, false);
183
184 /** Toggle the source/SVG view on click. */
185 btnFlip.addEventListener('click', function f(ev){
186 ev.preventDefault();
187 ev.stopPropagation();
188 if(!f.hasOwnProperty('origMaxWidth')){
189 f.origMaxWidth = parent.style.maxWidth;
190 }
191 const svgStyle = window.getComputedStyle(svg);
192 srcView.style.minWidth = svgStyle.width;
193 srcView.style.minHeight = svgStyle.height;
194 /* ^^^ The SVG wrapper/parent element has a max-width, so the
195 textarea will be too small on tiny images and won't be
196 enlargable. */
197 if(0){
198 /* We seem to have a fundamental incompatibility with how we
199 really want to position srcView at the same pos/size as the
200 svg and how that interacts with centered items.
201 Until/unless this can be solved, we have to decide between
202 the lesser of two evils:
203
204 1) This option. Small images have uselessly tiny source
205 view which cannot be enlarged because the parent element
206 has a width and/or max-width. width/max-width are important
207 for center alignment via the margin:auto trick.
208
209 2) Center-aligned images shift all the way to the left when
210 the source view is visible, then back to the center when
211 source view is hidden. Source views are resizable and may
212 even grow a bit automatically for tiny images.
213 */
214 if(srcView.classList.contains('hidden')){/*initial state*/
215 parent.style.width = f.origMaxWidth;
216 parent.style.maxWidth = 'unset';
217 }else{/*srcView is active*/
218 parent.style.maxWidth = f.origMaxWidth;
219 parent.style.width = 'unset';
220 }
221 }else if(1){
222 /* Option #2: gives us good results for non-centered items but
223 not for centered. We apparently have no(?) reliable way of
224 distinguishing centered from left/indented pikchrs here
225 unless we add a CSS class to mark them as such in the
226 pikchr-to-wiki-image code. */
227 if(srcView.classList.contains('hidden')){/*initial state*/
228 parent.style.width = 'unset';
229 parent.style.maxWidth = 'unset';
230 }else{/*srcView is active*/
231 parent.style.maxWidth = f.origMaxWidth;
232 parent.style.width = 'unset';
233 }
234 }
235 btnFlip.classList.toggle('src-active');
236 D.toggleClass([svg, srcView, buttonBar], 'hidden');
237 }, false);
238 };
239
240 })(window.fossil);
241
--- src/fossil.pikchr.js
+++ src/fossil.pikchr.js
@@ -1,240 +1,88 @@
1 (function(F/*window.fossil object*/){
2 "use strict";
3 const D = F.dom, P = F.pikchr = {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
5 /**
6 Initializes pikchr-rendered elements with the ability to
7 toggle between their SVG and source code.
8
9 The first argument may be any of:
10
11 - A single SVG.pikchr element.
12
13 - A collection (with a forEach method) of such elements.
14
15 - A CSS selector string for one or more such elements.
16
17 - An array of such strings.
18
19 Passing no value is equivalent to passing 'svg.pikchr'.
20
21 For each SVG in the resulting set, this function sets up event
22 handlers which allow the user to toggle the SVG between image and
23 source code modes. The image will switch modes in response to
24 cltr-click and, if its *parent* element has the "toggle" CSS
25 class, it will also switch modes in response to single-click.
26
27 If the parent element has the "source" CSS class, the image
28 starts off with its source code visible and the image hidden,
29 instead of the default of the other way around.
 
 
 
 
 
 
30
31 Returns this object.
32
 
 
 
 
33 Each element will only be processed once by this routine, even if
34 it is passed to this function multiple times. Each processed
35 element gets a "data" attribute set to it to indicate that it was
36 already dealt with.
37
38 This code expects the following structure around the SVGs, and
39 will not process any which don't match this:
40
41 <DIV><SVG.pikchr></SVG><PRE.pikchr-src></PRE></DIV>
42 */
43 P.addSrcView = function f(svg){
44 if(!f.hasOwnProperty('parentClick')){
45 f.parentClick = function(ev){
46 if(ev.ctrlKey || this.classList.contains('toggle')){
47 this._childs.forEach((e)=>e.classList.toggle('hidden'));
48 }
49 /* For the sake of small pics, we have to eliminate the
50 parent element's max-width... */
51 const src = this._childs[1];
52 if(src.classList.contains('hidden')){
53 this.style.maxWidth = this.dataset.origMaxWidth;
54 }else{
55 this.style.maxWidth = "unset";
56 }
57 };
58 };
 
59 if(!svg) svg = 'svg.pikchr';
60 if('string' === typeof svg){
61 document.querySelectorAll(svg).forEach((e)=>f.call(this, e));
 
 
62 return this;
63 }else if(svg.forEach){
64 svg.forEach((e)=>f.call(this, e));
65 return this;
66 }
67 if(svg.dataset.pikchrProcessed){
68 return this;
69 }
70 svg.dataset.pikchrProcessed = 1;
71 const parent = svg.parentNode;
72 const srcView = svg.nextElementSibling;
73 if(!srcView || !srcView.classList.contains('pikchr-src')){
74 /* Without this element, there's nothing for us to do here. */
 
75 return this;
76 }
77 console.debug(svg, parent, srcView);
78 parent.dataset.origMaxWidth = parent.style.maxWidth;
79 parent._childs = [svg, srcView];
80 D.addClass(srcView, 'hidden');
81 D.removeClass(svg, 'hidden');
82 parent.addEventListener('click', f.parentClick, false);
83 if(parent.classList.contains('source')){
84 /* Start off in source-view mode via a very fake click event */
85 f.parentClick.call(parent, {ctrlKey:true});
86 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87 };
 
88 })(window.fossil);
89
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -342,64 +342,46 @@
342342
void pikchr_to_html(
343343
Blob *ob, /* Write the generated SVG here */
344344
const char *zSrc, int nSrc, /* The Pikchr source text */
345345
const char *zArg, int nArg /* Addition arguments */
346346
){
347
- int w = 0, h = 0;
348
- char *zIn = fossil_strndup(zSrc, nSrc);
349
- char *zOut = pikchr(zIn, "pikchr", 0, &w, &h);
350
- if( w>0 && h>0 ){
351
- static int nSvg = 0;
352
- const char *zSafeNonce = safe_html_nonce(1);
353
- const char *zCss = "";
354
- const char *zClickOk = "e.ctrlKey";
355
- blob_append(ob, zSafeNonce, -1);
356
- blob_append_char(ob, '\n');
357
- while( nArg>0 ){
358
- int i;
359
- for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
360
- if( i==6 && strncmp(zArg, "center", 6)==0 ){
361
- zCss = "display:block;margin:auto;";
362
- }else if( i==6 && strncmp(zArg, "indent", 6)==0 ){
363
- zCss = "margin-left:4em;";
364
- }else if( i==10 && strncmp(zArg, "float-left", 10)==0 ){
365
- zCss = "float:left;padding=4em;";
366
- }else if( i==11 && strncmp(zArg, "float-right", 11)==0 ){
367
- zCss = "float:right;padding=4em;";
368
- }else if( i==6 && strncmp(zArg, "toggle", 6)==0 ){
369
- zClickOk = "1";
370
- }
371
- while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
372
- zArg += i;
373
- nArg -= i;
374
- }
375
- blob_appendf(ob, "<div id='svgid-%d'>\n", ++nSvg);
376
- blob_appendf(ob, "<div class='pikchr-svg'");
377
- blob_appendf(ob, " style='max-width:%dpx;%s'>\n", w, zCss);
378
- blob_append(ob, zOut, -1);
379
- blob_appendf(ob, "</div>\n");
380
- blob_appendf(ob, "<pre class='hidden'><code>"
381
- "%s</code></pre>\n", zIn);
382
- blob_appendf(ob, "</div>\n");
383
- blob_appendf(ob,
384
- "<script nonce='%s'>\n"
385
- "document.getElementById('svgid-%d').onclick=function(e){\n"
386
- " if(%s){\n"
387
- " for(var c of this.children){c.classList.toggle('hidden');}\n"
388
- " }\n"
389
- "}\n"
390
- "</script>\n",
391
- style_nonce(), nSvg, zClickOk);
392
- blob_appendf(ob, "%s\n", zSafeNonce);
393
- }else{
394
- blob_appendf(ob, "<pre>\n%s\n</pre>\n", zOut);
395
- }
396
- fossil_free(zIn);
397
- free(zOut);
398
-}
399
-
400
-
347
+ int pikFlags = PIKCHR_PROCESS_NONCE
348
+ | PIKCHR_PROCESS_DIV
349
+ | PIKCHR_PROCESS_SRC_HIDDEN;
350
+ Blob bSrc = empty_blob;
351
+
352
+ while( nArg>0 ){
353
+ int i;
354
+ for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
355
+ if( i==6 && strncmp(zArg, "center", 6)==0 ){
356
+ pikFlags |= PIKCHR_PROCESS_DIV_CENTER;
357
+ break;
358
+ }else if( i==6 && strncmp(zArg, "indent", 6)==0 ){
359
+ pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
360
+ break;
361
+ }else if( i==10 && strncmp(zArg, "float-left", 10)==0 ){
362
+ pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
363
+ break;
364
+ }else if( i==11 && strncmp(zArg, "float-right", 11)==0 ){
365
+ pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
366
+ break;
367
+ }else if( i==6 && strncmp(zArg, "toggle", 6)==0 ){
368
+ pikFlags |= PIKCHR_PROCESS_DIV_TOGGLE;
369
+ break;
370
+ }else if( i==6 && strncmp(zArg, "source", 6)==0 ){
371
+ pikFlags |= PIKCHR_PROCESS_DIV_SOURCE;
372
+ break;
373
+ }
374
+ while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
375
+ zArg += i;
376
+ nArg -= i;
377
+ }
378
+ blob_append(&bSrc, zSrc, nSrc)
379
+ /*have to dup input to ensure a NUL-terminated source string */;
380
+ pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
381
+ blob_reset(&bSrc);
382
+}
401383
402384
/* Invoked for `...` blocks where there are nSep grave accents in a
403385
** row that serve as the delimiter. According to CommonMark:
404386
**
405387
** * https://spec.commonmark.org/0.29/#fenced-code-blocks
406388
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -342,64 +342,46 @@
342 void pikchr_to_html(
343 Blob *ob, /* Write the generated SVG here */
344 const char *zSrc, int nSrc, /* The Pikchr source text */
345 const char *zArg, int nArg /* Addition arguments */
346 ){
347 int w = 0, h = 0;
348 char *zIn = fossil_strndup(zSrc, nSrc);
349 char *zOut = pikchr(zIn, "pikchr", 0, &w, &h);
350 if( w>0 && h>0 ){
351 static int nSvg = 0;
352 const char *zSafeNonce = safe_html_nonce(1);
353 const char *zCss = "";
354 const char *zClickOk = "e.ctrlKey";
355 blob_append(ob, zSafeNonce, -1);
356 blob_append_char(ob, '\n');
357 while( nArg>0 ){
358 int i;
359 for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
360 if( i==6 && strncmp(zArg, "center", 6)==0 ){
361 zCss = "display:block;margin:auto;";
362 }else if( i==6 && strncmp(zArg, "indent", 6)==0 ){
363 zCss = "margin-left:4em;";
364 }else if( i==10 && strncmp(zArg, "float-left", 10)==0 ){
365 zCss = "float:left;padding=4em;";
366 }else if( i==11 && strncmp(zArg, "float-right", 11)==0 ){
367 zCss = "float:right;padding=4em;";
368 }else if( i==6 && strncmp(zArg, "toggle", 6)==0 ){
369 zClickOk = "1";
370 }
371 while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
372 zArg += i;
373 nArg -= i;
374 }
375 blob_appendf(ob, "<div id='svgid-%d'>\n", ++nSvg);
376 blob_appendf(ob, "<div class='pikchr-svg'");
377 blob_appendf(ob, " style='max-width:%dpx;%s'>\n", w, zCss);
378 blob_append(ob, zOut, -1);
379 blob_appendf(ob, "</div>\n");
380 blob_appendf(ob, "<pre class='hidden'><code>"
381 "%s</code></pre>\n", zIn);
382 blob_appendf(ob, "</div>\n");
383 blob_appendf(ob,
384 "<script nonce='%s'>\n"
385 "document.getElementById('svgid-%d').onclick=function(e){\n"
386 " if(%s){\n"
387 " for(var c of this.children){c.classList.toggle('hidden');}\n"
388 " }\n"
389 "}\n"
390 "</script>\n",
391 style_nonce(), nSvg, zClickOk);
392 blob_appendf(ob, "%s\n", zSafeNonce);
393 }else{
394 blob_appendf(ob, "<pre>\n%s\n</pre>\n", zOut);
395 }
396 fossil_free(zIn);
397 free(zOut);
398 }
399
400
401
402 /* Invoked for `...` blocks where there are nSep grave accents in a
403 ** row that serve as the delimiter. According to CommonMark:
404 **
405 ** * https://spec.commonmark.org/0.29/#fenced-code-blocks
406
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -342,64 +342,46 @@
342 void pikchr_to_html(
343 Blob *ob, /* Write the generated SVG here */
344 const char *zSrc, int nSrc, /* The Pikchr source text */
345 const char *zArg, int nArg /* Addition arguments */
346 ){
347 int pikFlags = PIKCHR_PROCESS_NONCE
348 | PIKCHR_PROCESS_DIV
349 | PIKCHR_PROCESS_SRC_HIDDEN;
350 Blob bSrc = empty_blob;
351
352 while( nArg>0 ){
353 int i;
354 for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
355 if( i==6 && strncmp(zArg, "center", 6)==0 ){
356 pikFlags |= PIKCHR_PROCESS_DIV_CENTER;
357 break;
358 }else if( i==6 && strncmp(zArg, "indent", 6)==0 ){
359 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
360 break;
361 }else if( i==10 && strncmp(zArg, "float-left", 10)==0 ){
362 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
363 break;
364 }else if( i==11 && strncmp(zArg, "float-right", 11)==0 ){
365 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
366 break;
367 }else if( i==6 && strncmp(zArg, "toggle", 6)==0 ){
368 pikFlags |= PIKCHR_PROCESS_DIV_TOGGLE;
369 break;
370 }else if( i==6 && strncmp(zArg, "source", 6)==0 ){
371 pikFlags |= PIKCHR_PROCESS_DIV_SOURCE;
372 break;
373 }
374 while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
375 zArg += i;
376 nArg -= i;
377 }
378 blob_append(&bSrc, zSrc, nSrc)
379 /*have to dup input to ensure a NUL-terminated source string */;
380 pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
381 blob_reset(&bSrc);
382 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
384 /* Invoked for `...` blocks where there are nSep grave accents in a
385 ** row that serve as the delimiter. According to CommonMark:
386 **
387 ** * https://spec.commonmark.org/0.29/#fenced-code-blocks
388
+59 -22
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -33,18 +33,21 @@
3333
#define PIKCHR_PROCESS_DIV 0x40
3434
#define PIKCHR_PROCESS_DIV_INDENT 0x0100
3535
#define PIKCHR_PROCESS_DIV_CENTER 0x0200
3636
#define PIKCHR_PROCESS_DIV_FLOAT_LEFT 0x0400
3737
#define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800
38
+#define PIKCHR_PROCESS_DIV_TOGGLE 0x1000
39
+#define PIKCHR_PROCESS_DIV_SOURCE 0x2000
3840
#endif
3941
4042
/*
41
-** Processes a pikchr script, optionally with embedded TH1. zIn is the
42
-** input script. pikFlags may be a bitmask of any of the
43
-** PIKCHR_PROCESS_xxx flags (see below). thFlags may be a bitmask of
44
-** any of the TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to
45
-** pOut, appending to it without modifying any prior contents.
43
+** Processes a pikchr script, optionally with embedded TH1, and
44
+** produces HTML code for it. zIn is the NUL-terminated input
45
+** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
46
+** flags documented below. thFlags may be a bitmask of any of the
47
+** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
48
+** appending to it without modifying any prior contents.
4649
**
4750
** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
4851
** processing failed. In either case, the error message (if any) from
4952
** TH1 or pikchr will be appended to pOut.
5053
**
@@ -53,17 +56,17 @@
5356
** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
5457
** init flags specified in the 3rd argument. If thFlags is non-0 then
5558
** this flag is assumed even if it is not specified.
5659
**
5760
** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
58
-** TH1 step, thus the output will be (presumably) a
61
+** TH1 eval step, thus the output will be (presumably) a
5962
** TH1-generated/processed pikchr script (or whatever else the TH1
6063
** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
6164
** if it is not specified.
6265
**
63
-** The remaining flags listed below are ignored if
64
-** PIKCHR_PROCESS_TH1_NOSVG is specified:
66
+** All of the remaining flags listed below are ignored if
67
+** PIKCHR_PROCESS_TH1_NOSVG is specified!
6568
**
6669
** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
6770
** element which specifies a max-width style value based on the SVG's
6871
** calculated size. This flag has multiple mutually exclusive forms:
6972
**
@@ -71,40 +74,58 @@
7174
** - PIKCHR_PROCESS_DIV_INDENT indents the div.
7275
** - PIKCHR_PROCESS_DIV_CENTER centers the div.
7376
** - PIKCHR_PROCESS_DIV_FLOAT_LEFT floats the div left.
7477
** - PIKCHR_PROCESS_DIV_FLOAT_RIGHT floats the div right.
7578
**
76
-** If more than one is specified, which one is used is undefined.
79
+** If more than one is specified, which one is used is undefined. Those
80
+** flags may be OR'd with one or both of the following:
81
+**
82
+** - PIKCHR_PROCESS_DIV_TOGGLE: adds the 'toggle' CSS class to the
83
+** outer DIV so that event-handler code can install different
84
+** toggling behaviour than the default. Default is ctrl-click, but
85
+** this flag enables single-click toggling for the element.
86
+**
87
+** - PIKCHR_PROCESS_DIV_SOURCE: adds the 'source' CSS class to the
88
+** outer DIV, which is a hint to the client-side renderer (see
89
+** fossil.pikchr.js) that the pikchr should initially be rendered
90
+** in source code form mode (the default is to hide the source and
91
+** show the SVG).
7792
**
7893
** - PIKCHR_PROCESS_NONCE: if set, the resulting SVG/DIV are wrapped
7994
** in "safe nonce" comments, which are a fossil-internal mechanism
8095
** which prevents the wiki/markdown processors from re-processing this
81
-** output.
96
+** output. This is necessary when calling this routine in the context
97
+** of wiki/embedded doc processing, but not (e.g.) when fetching
98
+** an image for /pikchrpage.
8299
**
83
-** - PIKCHR_PROCESS_SRC: if set, a new TEXTAREA.pikchr-src element is injected
84
-** adjacet to the SVG element which contains the HTML-escaped content of
85
-** the input script.
100
+** - PIKCHR_PROCESS_SRC: if set, a new PRE.pikchr-src element is
101
+** injected adjacent to the SVG element which contains the
102
+** HTML-escaped content of the input script.
86103
**
87104
** - PIKCHR_PROCESS_SRC_HIDDEN: exactly like PIKCHR_PROCESS_SRC but
88105
** the .pikchr-src tag also gets the CSS class 'hidden' (which, in
89
-** fossil's default CSS, will hide that element).
106
+** fossil's default CSS, will hide that element). This is almost
107
+** always what client code will want to do if it includes the source
108
+** at all.
90109
**
91110
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
92111
** error report is wrapped in a PRE element, else it is retained
93
-** as-is (intended for console output).
112
+** as-is (intended only for console output).
94113
*/
95114
int pikchr_process(const char * zIn, int pikFlags, int thFlags,
96
- Blob * pOut){
115
+ Blob * pOut){
97116
Blob bIn = empty_blob;
98117
int isErr = 0;
99118
100119
if(!(PIKCHR_PROCESS_DIV & pikFlags)
101120
/* If any DIV_xxx flags are set, set DIV */
102121
&& (PIKCHR_PROCESS_DIV_INDENT
103122
| PIKCHR_PROCESS_DIV_CENTER
104123
| PIKCHR_PROCESS_DIV_FLOAT_RIGHT
105124
| PIKCHR_PROCESS_DIV_FLOAT_LEFT
125
+ | PIKCHR_PROCESS_DIV_SOURCE
126
+ | PIKCHR_PROCESS_DIV_TOGGLE
106127
) & pikFlags){
107128
pikFlags |= PIKCHR_PROCESS_DIV;
108129
}
109130
if(!(PIKCHR_PROCESS_TH1 & pikFlags)
110131
/* If any TH1_xxx flags are set, set TH1 */
@@ -132,10 +153,12 @@
132153
const char * zContent = blob_str(&bIn);
133154
char *zOut;
134155
135156
zOut = pikchr(zContent, "pikchr", 0, &w, &h);
136157
if( w>0 && h>0 ){
158
+ const char * zClassToggle = "";
159
+ const char * zClassSource = "";
137160
const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
138161
? safe_html_nonce(1) : 0;
139162
if(zNonce){
140163
blob_append(pOut, zNonce, -1);
141164
}
@@ -149,19 +172,27 @@
149172
}else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
150173
blob_append(&css, "float:left;padding=4em;", -1);
151174
}else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
152175
blob_append(&css, "float:right;padding=4em;", -1);
153176
}
154
- blob_appendf(pOut,"<div class=\"pikchr\" style=\"%b\">\n", &css);
177
+ if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
178
+ zClassToggle = " toggle";
179
+ }
180
+ if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
181
+ zClassSource = " source";
182
+ }
183
+ blob_appendf(pOut,"<div class=\"pikchr-svg%s%s\" "
184
+ "style=\"%b\">\n",
185
+ zClassToggle/*safe-for-%s*/,
186
+ zClassSource/*safe-for-%s*/, &css);
155187
blob_reset(&css);
156188
}
157189
blob_append(pOut, zOut, -1);
158190
if((PIKCHR_PROCESS_SRC & pikFlags)
159191
|| (PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)){
160
- blob_appendf(pOut, "<textarea rows='10' readonly "
161
- "class='pikchr-src%s'>"
162
- "%h</textarea>\n",
192
+ blob_appendf(pOut, "<pre class='pikchr-src%s'>"
193
+ "%h</pre>\n",
163194
(PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)
164195
? " hidden" : "",
165196
blob_str(&bIn));
166197
}
167198
if(PIKCHR_PROCESS_DIV & pikFlags){
@@ -357,11 +388,11 @@
357388
**
358389
** -div-left Like -div but floats the div left.
359390
**
360391
** -div-right Like -div but floats the div right.
361392
**
362
-** -svg-src Stores the input pikchr's source code in the output as
393
+** -src Stores the input pikchr's source code in the output as
363394
** a separate element adjacent to the SVG one. The
364395
** source element initially has the "hidden" CSS class.
365396
**
366397
** -th Process the input using TH1 before passing it to pikchr.
367398
**
@@ -399,11 +430,11 @@
399430
const char * zInfile = "-";
400431
const char * zOutfile = "-";
401432
const int fTh1 = find_option("th",0,0)!=0;
402433
const int fNosvg = find_option("th-nosvg",0,0)!=0;
403434
int isErr = 0;
404
- int pikFlags = find_option("svg-src",0,0)!=0
435
+ int pikFlags = find_option("src",0,0)!=0
405436
? PIKCHR_PROCESS_SRC_HIDDEN : 0;
406437
u32 fThFlags = TH_INIT_NO_ENCODE
407438
| (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
408439
409440
Th_InitTraceLog()/*processes -th-trace flag*/;
@@ -417,10 +448,16 @@
417448
}else if(find_option("div-float-left",0,0)!=0){
418449
pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
419450
}else if(find_option("div-float-right",0,0)!=0){
420451
pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
421452
}
453
+ if(find_option("div-toggle",0,0)!=0){
454
+ pikFlags |= PIKCHR_PROCESS_DIV_TOGGLE;
455
+ }
456
+ if(find_option("div-source",0,0)!=0){
457
+ pikFlags |= PIKCHR_PROCESS_DIV_SOURCE;
458
+ }
422459
423460
verify_all_options();
424461
if(g.argc>4){
425462
usage("?INFILE? ?OUTFILE?");
426463
}
427464
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -33,18 +33,21 @@
33 #define PIKCHR_PROCESS_DIV 0x40
34 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
35 #define PIKCHR_PROCESS_DIV_CENTER 0x0200
36 #define PIKCHR_PROCESS_DIV_FLOAT_LEFT 0x0400
37 #define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800
 
 
38 #endif
39
40 /*
41 ** Processes a pikchr script, optionally with embedded TH1. zIn is the
42 ** input script. pikFlags may be a bitmask of any of the
43 ** PIKCHR_PROCESS_xxx flags (see below). thFlags may be a bitmask of
44 ** any of the TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to
45 ** pOut, appending to it without modifying any prior contents.
 
46 **
47 ** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
48 ** processing failed. In either case, the error message (if any) from
49 ** TH1 or pikchr will be appended to pOut.
50 **
@@ -53,17 +56,17 @@
53 ** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
54 ** init flags specified in the 3rd argument. If thFlags is non-0 then
55 ** this flag is assumed even if it is not specified.
56 **
57 ** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
58 ** TH1 step, thus the output will be (presumably) a
59 ** TH1-generated/processed pikchr script (or whatever else the TH1
60 ** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
61 ** if it is not specified.
62 **
63 ** The remaining flags listed below are ignored if
64 ** PIKCHR_PROCESS_TH1_NOSVG is specified:
65 **
66 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
67 ** element which specifies a max-width style value based on the SVG's
68 ** calculated size. This flag has multiple mutually exclusive forms:
69 **
@@ -71,40 +74,58 @@
71 ** - PIKCHR_PROCESS_DIV_INDENT indents the div.
72 ** - PIKCHR_PROCESS_DIV_CENTER centers the div.
73 ** - PIKCHR_PROCESS_DIV_FLOAT_LEFT floats the div left.
74 ** - PIKCHR_PROCESS_DIV_FLOAT_RIGHT floats the div right.
75 **
76 ** If more than one is specified, which one is used is undefined.
 
 
 
 
 
 
 
 
 
 
 
 
77 **
78 ** - PIKCHR_PROCESS_NONCE: if set, the resulting SVG/DIV are wrapped
79 ** in "safe nonce" comments, which are a fossil-internal mechanism
80 ** which prevents the wiki/markdown processors from re-processing this
81 ** output.
 
 
82 **
83 ** - PIKCHR_PROCESS_SRC: if set, a new TEXTAREA.pikchr-src element is injected
84 ** adjacet to the SVG element which contains the HTML-escaped content of
85 ** the input script.
86 **
87 ** - PIKCHR_PROCESS_SRC_HIDDEN: exactly like PIKCHR_PROCESS_SRC but
88 ** the .pikchr-src tag also gets the CSS class 'hidden' (which, in
89 ** fossil's default CSS, will hide that element).
 
 
90 **
91 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
92 ** error report is wrapped in a PRE element, else it is retained
93 ** as-is (intended for console output).
94 */
95 int pikchr_process(const char * zIn, int pikFlags, int thFlags,
96 Blob * pOut){
97 Blob bIn = empty_blob;
98 int isErr = 0;
99
100 if(!(PIKCHR_PROCESS_DIV & pikFlags)
101 /* If any DIV_xxx flags are set, set DIV */
102 && (PIKCHR_PROCESS_DIV_INDENT
103 | PIKCHR_PROCESS_DIV_CENTER
104 | PIKCHR_PROCESS_DIV_FLOAT_RIGHT
105 | PIKCHR_PROCESS_DIV_FLOAT_LEFT
 
 
106 ) & pikFlags){
107 pikFlags |= PIKCHR_PROCESS_DIV;
108 }
109 if(!(PIKCHR_PROCESS_TH1 & pikFlags)
110 /* If any TH1_xxx flags are set, set TH1 */
@@ -132,10 +153,12 @@
132 const char * zContent = blob_str(&bIn);
133 char *zOut;
134
135 zOut = pikchr(zContent, "pikchr", 0, &w, &h);
136 if( w>0 && h>0 ){
 
 
137 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
138 ? safe_html_nonce(1) : 0;
139 if(zNonce){
140 blob_append(pOut, zNonce, -1);
141 }
@@ -149,19 +172,27 @@
149 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
150 blob_append(&css, "float:left;padding=4em;", -1);
151 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
152 blob_append(&css, "float:right;padding=4em;", -1);
153 }
154 blob_appendf(pOut,"<div class=\"pikchr\" style=\"%b\">\n", &css);
 
 
 
 
 
 
 
 
 
155 blob_reset(&css);
156 }
157 blob_append(pOut, zOut, -1);
158 if((PIKCHR_PROCESS_SRC & pikFlags)
159 || (PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)){
160 blob_appendf(pOut, "<textarea rows='10' readonly "
161 "class='pikchr-src%s'>"
162 "%h</textarea>\n",
163 (PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)
164 ? " hidden" : "",
165 blob_str(&bIn));
166 }
167 if(PIKCHR_PROCESS_DIV & pikFlags){
@@ -357,11 +388,11 @@
357 **
358 ** -div-left Like -div but floats the div left.
359 **
360 ** -div-right Like -div but floats the div right.
361 **
362 ** -svg-src Stores the input pikchr's source code in the output as
363 ** a separate element adjacent to the SVG one. The
364 ** source element initially has the "hidden" CSS class.
365 **
366 ** -th Process the input using TH1 before passing it to pikchr.
367 **
@@ -399,11 +430,11 @@
399 const char * zInfile = "-";
400 const char * zOutfile = "-";
401 const int fTh1 = find_option("th",0,0)!=0;
402 const int fNosvg = find_option("th-nosvg",0,0)!=0;
403 int isErr = 0;
404 int pikFlags = find_option("svg-src",0,0)!=0
405 ? PIKCHR_PROCESS_SRC_HIDDEN : 0;
406 u32 fThFlags = TH_INIT_NO_ENCODE
407 | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
408
409 Th_InitTraceLog()/*processes -th-trace flag*/;
@@ -417,10 +448,16 @@
417 }else if(find_option("div-float-left",0,0)!=0){
418 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
419 }else if(find_option("div-float-right",0,0)!=0){
420 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
421 }
 
 
 
 
 
 
422
423 verify_all_options();
424 if(g.argc>4){
425 usage("?INFILE? ?OUTFILE?");
426 }
427
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -33,18 +33,21 @@
33 #define PIKCHR_PROCESS_DIV 0x40
34 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
35 #define PIKCHR_PROCESS_DIV_CENTER 0x0200
36 #define PIKCHR_PROCESS_DIV_FLOAT_LEFT 0x0400
37 #define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800
38 #define PIKCHR_PROCESS_DIV_TOGGLE 0x1000
39 #define PIKCHR_PROCESS_DIV_SOURCE 0x2000
40 #endif
41
42 /*
43 ** Processes a pikchr script, optionally with embedded TH1, and
44 ** produces HTML code for it. zIn is the NUL-terminated input
45 ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
46 ** flags documented below. thFlags may be a bitmask of any of the
47 ** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
48 ** appending to it without modifying any prior contents.
49 **
50 ** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
51 ** processing failed. In either case, the error message (if any) from
52 ** TH1 or pikchr will be appended to pOut.
53 **
@@ -53,17 +56,17 @@
56 ** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
57 ** init flags specified in the 3rd argument. If thFlags is non-0 then
58 ** this flag is assumed even if it is not specified.
59 **
60 ** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
61 ** TH1 eval step, thus the output will be (presumably) a
62 ** TH1-generated/processed pikchr script (or whatever else the TH1
63 ** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
64 ** if it is not specified.
65 **
66 ** All of the remaining flags listed below are ignored if
67 ** PIKCHR_PROCESS_TH1_NOSVG is specified!
68 **
69 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
70 ** element which specifies a max-width style value based on the SVG's
71 ** calculated size. This flag has multiple mutually exclusive forms:
72 **
@@ -71,40 +74,58 @@
74 ** - PIKCHR_PROCESS_DIV_INDENT indents the div.
75 ** - PIKCHR_PROCESS_DIV_CENTER centers the div.
76 ** - PIKCHR_PROCESS_DIV_FLOAT_LEFT floats the div left.
77 ** - PIKCHR_PROCESS_DIV_FLOAT_RIGHT floats the div right.
78 **
79 ** If more than one is specified, which one is used is undefined. Those
80 ** flags may be OR'd with one or both of the following:
81 **
82 ** - PIKCHR_PROCESS_DIV_TOGGLE: adds the 'toggle' CSS class to the
83 ** outer DIV so that event-handler code can install different
84 ** toggling behaviour than the default. Default is ctrl-click, but
85 ** this flag enables single-click toggling for the element.
86 **
87 ** - PIKCHR_PROCESS_DIV_SOURCE: adds the 'source' CSS class to the
88 ** outer DIV, which is a hint to the client-side renderer (see
89 ** fossil.pikchr.js) that the pikchr should initially be rendered
90 ** in source code form mode (the default is to hide the source and
91 ** show the SVG).
92 **
93 ** - PIKCHR_PROCESS_NONCE: if set, the resulting SVG/DIV are wrapped
94 ** in "safe nonce" comments, which are a fossil-internal mechanism
95 ** which prevents the wiki/markdown processors from re-processing this
96 ** output. This is necessary when calling this routine in the context
97 ** of wiki/embedded doc processing, but not (e.g.) when fetching
98 ** an image for /pikchrpage.
99 **
100 ** - PIKCHR_PROCESS_SRC: if set, a new PRE.pikchr-src element is
101 ** injected adjacent to the SVG element which contains the
102 ** HTML-escaped content of the input script.
103 **
104 ** - PIKCHR_PROCESS_SRC_HIDDEN: exactly like PIKCHR_PROCESS_SRC but
105 ** the .pikchr-src tag also gets the CSS class 'hidden' (which, in
106 ** fossil's default CSS, will hide that element). This is almost
107 ** always what client code will want to do if it includes the source
108 ** at all.
109 **
110 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
111 ** error report is wrapped in a PRE element, else it is retained
112 ** as-is (intended only for console output).
113 */
114 int pikchr_process(const char * zIn, int pikFlags, int thFlags,
115 Blob * pOut){
116 Blob bIn = empty_blob;
117 int isErr = 0;
118
119 if(!(PIKCHR_PROCESS_DIV & pikFlags)
120 /* If any DIV_xxx flags are set, set DIV */
121 && (PIKCHR_PROCESS_DIV_INDENT
122 | PIKCHR_PROCESS_DIV_CENTER
123 | PIKCHR_PROCESS_DIV_FLOAT_RIGHT
124 | PIKCHR_PROCESS_DIV_FLOAT_LEFT
125 | PIKCHR_PROCESS_DIV_SOURCE
126 | PIKCHR_PROCESS_DIV_TOGGLE
127 ) & pikFlags){
128 pikFlags |= PIKCHR_PROCESS_DIV;
129 }
130 if(!(PIKCHR_PROCESS_TH1 & pikFlags)
131 /* If any TH1_xxx flags are set, set TH1 */
@@ -132,10 +153,12 @@
153 const char * zContent = blob_str(&bIn);
154 char *zOut;
155
156 zOut = pikchr(zContent, "pikchr", 0, &w, &h);
157 if( w>0 && h>0 ){
158 const char * zClassToggle = "";
159 const char * zClassSource = "";
160 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
161 ? safe_html_nonce(1) : 0;
162 if(zNonce){
163 blob_append(pOut, zNonce, -1);
164 }
@@ -149,19 +172,27 @@
172 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
173 blob_append(&css, "float:left;padding=4em;", -1);
174 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
175 blob_append(&css, "float:right;padding=4em;", -1);
176 }
177 if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
178 zClassToggle = " toggle";
179 }
180 if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
181 zClassSource = " source";
182 }
183 blob_appendf(pOut,"<div class=\"pikchr-svg%s%s\" "
184 "style=\"%b\">\n",
185 zClassToggle/*safe-for-%s*/,
186 zClassSource/*safe-for-%s*/, &css);
187 blob_reset(&css);
188 }
189 blob_append(pOut, zOut, -1);
190 if((PIKCHR_PROCESS_SRC & pikFlags)
191 || (PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)){
192 blob_appendf(pOut, "<pre class='pikchr-src%s'>"
193 "%h</pre>\n",
 
194 (PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)
195 ? " hidden" : "",
196 blob_str(&bIn));
197 }
198 if(PIKCHR_PROCESS_DIV & pikFlags){
@@ -357,11 +388,11 @@
388 **
389 ** -div-left Like -div but floats the div left.
390 **
391 ** -div-right Like -div but floats the div right.
392 **
393 ** -src Stores the input pikchr's source code in the output as
394 ** a separate element adjacent to the SVG one. The
395 ** source element initially has the "hidden" CSS class.
396 **
397 ** -th Process the input using TH1 before passing it to pikchr.
398 **
@@ -399,11 +430,11 @@
430 const char * zInfile = "-";
431 const char * zOutfile = "-";
432 const int fTh1 = find_option("th",0,0)!=0;
433 const int fNosvg = find_option("th-nosvg",0,0)!=0;
434 int isErr = 0;
435 int pikFlags = find_option("src",0,0)!=0
436 ? PIKCHR_PROCESS_SRC_HIDDEN : 0;
437 u32 fThFlags = TH_INIT_NO_ENCODE
438 | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
439
440 Th_InitTraceLog()/*processes -th-trace flag*/;
@@ -417,10 +448,16 @@
448 }else if(find_option("div-float-left",0,0)!=0){
449 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
450 }else if(find_option("div-float-right",0,0)!=0){
451 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
452 }
453 if(find_option("div-toggle",0,0)!=0){
454 pikFlags |= PIKCHR_PROCESS_DIV_TOGGLE;
455 }
456 if(find_option("div-source",0,0)!=0){
457 pikFlags |= PIKCHR_PROCESS_DIV_SOURCE;
458 }
459
460 verify_all_options();
461 if(g.argc>4){
462 usage("?INFILE? ?OUTFILE?");
463 }
464

Keyboard Shortcuts

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