Fossil SCM
pikchrshow now squirrels away a copy of the raw response SVG, instead of fishing it out the DOM on demand, because in the latter case the browser converts u00a0 characters to nbsp elements, resulting in illegal SVG.
Commit
f45cd279190cc9bb86f21d4b5ed28e091827e6036223808fdce8a537aaca69ea
Parent
7de85417dee9c28…
1 file changed
+29
-5
+29
-5
| --- src/fossil.page.pikchrshow.js | ||
| +++ src/fossil.page.pikchrshow.js | ||
| @@ -13,12 +13,30 @@ | ||
| 13 | 13 | P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown, |
| 14 | 14 | 2==pikchr text fossil, 3==raw SVG. */ |
| 15 | 15 | P.response = {/*stashed state for the server's preview response*/ |
| 16 | 16 | isError: false, |
| 17 | 17 | inputText: undefined /* value of the editor field at render-time */, |
| 18 | - raw: undefined /* raw response text/HTML from server */ | |
| 18 | + raw: undefined /* raw response text/HTML from server */, | |
| 19 | + rawSvg: undefined /* plain-text SVG part of responses. Required | |
| 20 | + because the browser will convert \u00a0 to | |
| 21 | + if we extract the SVG from the DOM, | |
| 22 | + resulting in illegal SVG. */ | |
| 23 | + }; | |
| 24 | + | |
| 25 | + /** | |
| 26 | + If string r contains an SVG element, this returns that section | |
| 27 | + of the string, else it returns falsy. | |
| 28 | + */ | |
| 29 | + const getResponseSvg = function(r){ | |
| 30 | + const i0 = r.indexOf("<svg"); | |
| 31 | + if(i0>=0){ | |
| 32 | + const i1 = r.indexOf("</svg"); | |
| 33 | + return r.substring(i0,i1+6); | |
| 34 | + } | |
| 35 | + return ''; | |
| 19 | 36 | }; |
| 37 | + | |
| 20 | 38 | F.onPageLoad(function() { |
| 21 | 39 | document.body.classList.add('pikchrshow'); |
| 22 | 40 | P.e = { /* various DOM elements we work with... */ |
| 23 | 41 | previewTarget: E('#pikchrshow-output'), |
| 24 | 42 | previewLegend: E('#pikchrshow-output-wrapper > legend'), |
| @@ -275,10 +293,14 @@ | ||
| 275 | 293 | } |
| 276 | 294 | if(P.e.taContent.value){ |
| 277 | 295 | /* Fill our "response" state so that renderPreview() can work */ |
| 278 | 296 | P.response.inputText = P.e.taContent.value; |
| 279 | 297 | P.response.raw = P.e.previewTarget.innerHTML; |
| 298 | + P.response.rawSvg = getResponseSvg( | |
| 299 | + P.response.raw /*note that this is already in the DOM, | |
| 300 | + which means that the browser has already mangled | |
| 301 | + \u00a0 to , so...*/.split(' ').join('\u00a0')); | |
| 280 | 302 | if(needsPreview) P.preview(); |
| 281 | 303 | else{ |
| 282 | 304 | /*If it's from the server, it's already rendered, but this |
| 283 | 305 | gets all labels/headers in sync.*/ |
| 284 | 306 | P.renderPreview(); |
| @@ -323,12 +345,12 @@ | ||
| 323 | 345 | case 0: |
| 324 | 346 | label = "SVG"; |
| 325 | 347 | f.showMarkupAlignment(false); |
| 326 | 348 | D.parseHtml(D.clearElement(preTgt), P.response.raw); |
| 327 | 349 | svg = preTgt.querySelector('svg.pikchr'); |
| 328 | - if(svg){ /*for copy button*/ | |
| 329 | - this.e.taPreviewText.value = svg.outerHTML; | |
| 350 | + if(svg && P.response.rawSvg){ /*for copy button*/ | |
| 351 | + this.e.taPreviewText.value = P.response.rawSvg; | |
| 330 | 352 | F.pikchr.addSrcView(svg); |
| 331 | 353 | } |
| 332 | 354 | break; |
| 333 | 355 | case 1: |
| 334 | 356 | label = "Markdown"; |
| @@ -352,11 +374,12 @@ | ||
| 352 | 374 | case 3: |
| 353 | 375 | label = "Raw SVG"; |
| 354 | 376 | f.showMarkupAlignment(false); |
| 355 | 377 | svg = f.getSvgNode(this.response.raw); |
| 356 | 378 | if(svg){ |
| 357 | - this.e.taPreviewText.value = svg.outerHTML; | |
| 379 | + this.e.taPreviewText.value = | |
| 380 | + P.response.rawSvg || "Error extracting SVG element."; | |
| 358 | 381 | }else{ |
| 359 | 382 | this.e.taPreviewText.value = "ERROR parsing response HTML:\n"+ |
| 360 | 383 | this.response.raw; |
| 361 | 384 | console.error("svg parsed HTML nodes:",childs); |
| 362 | 385 | } |
| @@ -382,20 +405,21 @@ | ||
| 382 | 405 | ]; |
| 383 | 406 | fp.target = this.e.previewTarget; |
| 384 | 407 | fp.updateView = function(c,isError){ |
| 385 | 408 | P.previewMode = 0; |
| 386 | 409 | P.response.raw = c; |
| 410 | + P.response.rawSvg = getResponseSvg(c); | |
| 387 | 411 | P.response.isError = isError; |
| 388 | 412 | D.enable(fp.toDisable); |
| 389 | 413 | P.renderPreview(); |
| 390 | 414 | }; |
| 391 | 415 | } |
| 392 | 416 | D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); |
| 393 | 417 | D.addClass(this.e.markupAlignWrapper, 'hidden'); |
| 394 | 418 | D.addClass(this.e.previewCopyButton, 'disabled'); |
| 395 | 419 | const content = this.e.taContent.value.trim(); |
| 396 | - this.response.raw = undefined; | |
| 420 | + this.response.raw = this.response.rawSvg = undefined; | |
| 397 | 421 | this.response.inputText = content; |
| 398 | 422 | const sampleScript = fp.$_sampleScript; |
| 399 | 423 | delete fp.$_sampleScript; |
| 400 | 424 | if(sampleScript && sampleScript.cached){ |
| 401 | 425 | fp.updateView(sampleScript.cached, false); |
| 402 | 426 |
| --- src/fossil.page.pikchrshow.js | |
| +++ src/fossil.page.pikchrshow.js | |
| @@ -13,12 +13,30 @@ | |
| 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 | previewLegend: E('#pikchrshow-output-wrapper > legend'), |
| @@ -275,10 +293,14 @@ | |
| 275 | } |
| 276 | if(P.e.taContent.value){ |
| 277 | /* Fill our "response" state so that renderPreview() can work */ |
| 278 | P.response.inputText = P.e.taContent.value; |
| 279 | P.response.raw = P.e.previewTarget.innerHTML; |
| 280 | if(needsPreview) P.preview(); |
| 281 | else{ |
| 282 | /*If it's from the server, it's already rendered, but this |
| 283 | gets all labels/headers in sync.*/ |
| 284 | P.renderPreview(); |
| @@ -323,12 +345,12 @@ | |
| 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"; |
| @@ -352,11 +374,12 @@ | |
| 352 | case 3: |
| 353 | label = "Raw SVG"; |
| 354 | f.showMarkupAlignment(false); |
| 355 | svg = f.getSvgNode(this.response.raw); |
| 356 | if(svg){ |
| 357 | this.e.taPreviewText.value = svg.outerHTML; |
| 358 | }else{ |
| 359 | this.e.taPreviewText.value = "ERROR parsing response HTML:\n"+ |
| 360 | this.response.raw; |
| 361 | console.error("svg parsed HTML nodes:",childs); |
| 362 | } |
| @@ -382,20 +405,21 @@ | |
| 382 | ]; |
| 383 | fp.target = this.e.previewTarget; |
| 384 | fp.updateView = function(c,isError){ |
| 385 | P.previewMode = 0; |
| 386 | P.response.raw = c; |
| 387 | P.response.isError = isError; |
| 388 | D.enable(fp.toDisable); |
| 389 | P.renderPreview(); |
| 390 | }; |
| 391 | } |
| 392 | D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); |
| 393 | D.addClass(this.e.markupAlignWrapper, 'hidden'); |
| 394 | D.addClass(this.e.previewCopyButton, 'disabled'); |
| 395 | const content = this.e.taContent.value.trim(); |
| 396 | this.response.raw = undefined; |
| 397 | this.response.inputText = content; |
| 398 | const sampleScript = fp.$_sampleScript; |
| 399 | delete fp.$_sampleScript; |
| 400 | if(sampleScript && sampleScript.cached){ |
| 401 | fp.updateView(sampleScript.cached, false); |
| 402 |
| --- src/fossil.page.pikchrshow.js | |
| +++ src/fossil.page.pikchrshow.js | |
| @@ -13,12 +13,30 @@ | |
| 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 | rawSvg: undefined /* plain-text SVG part of responses. Required |
| 20 | because the browser will convert \u00a0 to |
| 21 | if we extract the SVG from the DOM, |
| 22 | resulting in illegal SVG. */ |
| 23 | }; |
| 24 | |
| 25 | /** |
| 26 | If string r contains an SVG element, this returns that section |
| 27 | of the string, else it returns falsy. |
| 28 | */ |
| 29 | const getResponseSvg = function(r){ |
| 30 | const i0 = r.indexOf("<svg"); |
| 31 | if(i0>=0){ |
| 32 | const i1 = r.indexOf("</svg"); |
| 33 | return r.substring(i0,i1+6); |
| 34 | } |
| 35 | return ''; |
| 36 | }; |
| 37 | |
| 38 | F.onPageLoad(function() { |
| 39 | document.body.classList.add('pikchrshow'); |
| 40 | P.e = { /* various DOM elements we work with... */ |
| 41 | previewTarget: E('#pikchrshow-output'), |
| 42 | previewLegend: E('#pikchrshow-output-wrapper > legend'), |
| @@ -275,10 +293,14 @@ | |
| 293 | } |
| 294 | if(P.e.taContent.value){ |
| 295 | /* Fill our "response" state so that renderPreview() can work */ |
| 296 | P.response.inputText = P.e.taContent.value; |
| 297 | P.response.raw = P.e.previewTarget.innerHTML; |
| 298 | P.response.rawSvg = getResponseSvg( |
| 299 | P.response.raw /*note that this is already in the DOM, |
| 300 | which means that the browser has already mangled |
| 301 | \u00a0 to , so...*/.split(' ').join('\u00a0')); |
| 302 | if(needsPreview) P.preview(); |
| 303 | else{ |
| 304 | /*If it's from the server, it's already rendered, but this |
| 305 | gets all labels/headers in sync.*/ |
| 306 | P.renderPreview(); |
| @@ -323,12 +345,12 @@ | |
| 345 | case 0: |
| 346 | label = "SVG"; |
| 347 | f.showMarkupAlignment(false); |
| 348 | D.parseHtml(D.clearElement(preTgt), P.response.raw); |
| 349 | svg = preTgt.querySelector('svg.pikchr'); |
| 350 | if(svg && P.response.rawSvg){ /*for copy button*/ |
| 351 | this.e.taPreviewText.value = P.response.rawSvg; |
| 352 | F.pikchr.addSrcView(svg); |
| 353 | } |
| 354 | break; |
| 355 | case 1: |
| 356 | label = "Markdown"; |
| @@ -352,11 +374,12 @@ | |
| 374 | case 3: |
| 375 | label = "Raw SVG"; |
| 376 | f.showMarkupAlignment(false); |
| 377 | svg = f.getSvgNode(this.response.raw); |
| 378 | if(svg){ |
| 379 | this.e.taPreviewText.value = |
| 380 | P.response.rawSvg || "Error extracting SVG element."; |
| 381 | }else{ |
| 382 | this.e.taPreviewText.value = "ERROR parsing response HTML:\n"+ |
| 383 | this.response.raw; |
| 384 | console.error("svg parsed HTML nodes:",childs); |
| 385 | } |
| @@ -382,20 +405,21 @@ | |
| 405 | ]; |
| 406 | fp.target = this.e.previewTarget; |
| 407 | fp.updateView = function(c,isError){ |
| 408 | P.previewMode = 0; |
| 409 | P.response.raw = c; |
| 410 | P.response.rawSvg = getResponseSvg(c); |
| 411 | P.response.isError = isError; |
| 412 | D.enable(fp.toDisable); |
| 413 | P.renderPreview(); |
| 414 | }; |
| 415 | } |
| 416 | D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); |
| 417 | D.addClass(this.e.markupAlignWrapper, 'hidden'); |
| 418 | D.addClass(this.e.previewCopyButton, 'disabled'); |
| 419 | const content = this.e.taContent.value.trim(); |
| 420 | this.response.raw = this.response.rawSvg = undefined; |
| 421 | this.response.inputText = content; |
| 422 | const sampleScript = fp.$_sampleScript; |
| 423 | delete fp.$_sampleScript; |
| 424 | if(sampleScript && sampleScript.cached){ |
| 425 | fp.updateView(sampleScript.cached, false); |
| 426 |