| | @@ -1,240 +1,88 @@ |
| 1 | 1 | (function(F/*window.fossil object*/){ |
| 2 | 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 | | - })(); |
| 3 | + const D = F.dom, P = F.pikchr = {}; |
| 80 | 4 | |
| 81 | 5 | /** |
| 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. |
| 84 | 8 | |
| 85 | 9 | The first argument may be any of: |
| 86 | 10 | |
| 87 | | - - A single SVG element. |
| 11 | + - A single SVG.pikchr element. |
| 88 | 12 | |
| 89 | 13 | - A collection (with a forEach method) of such elements. |
| 90 | 14 | |
| 91 | 15 | - A CSS selector string for one or more such elements. |
| 92 | 16 | |
| 93 | 17 | - An array of such strings. |
| 94 | 18 | |
| 95 | 19 | Passing no value is equivalent to passing 'svg.pikchr'. |
| 96 | 20 | |
| 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. |
| 112 | 30 | |
| 113 | 31 | Returns this object. |
| 114 | 32 | |
| 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 | 33 | Each element will only be processed once by this routine, even if |
| 120 | 34 | it is passed to this function multiple times. Each processed |
| 121 | 35 | element gets a "data" attribute set to it to indicate that it was |
| 122 | 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> |
| 123 | 42 | */ |
| 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 | + } |
| 128 | 57 | }; |
| 129 | | - document.body.addEventListener('click', f.bodyClick, false); |
| 130 | | - } |
| 58 | + }; |
| 131 | 59 | if(!svg) svg = 'svg.pikchr'; |
| 132 | 60 | 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)); |
| 136 | 62 | return this; |
| 137 | 63 | }else if(svg.forEach){ |
| 138 | | - svg.forEach((e)=>f.call(this, e, opt)); |
| 64 | + svg.forEach((e)=>f.call(this, e)); |
| 139 | 65 | return this; |
| 140 | 66 | } |
| 141 | 67 | if(svg.dataset.pikchrProcessed){ |
| 142 | 68 | return this; |
| 143 | 69 | } |
| 144 | 70 | svg.dataset.pikchrProcessed = 1; |
| 145 | 71 | 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. */ |
| 150 | 75 | return this; |
| 151 | 76 | } |
| 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 | + } |
| 238 | 87 | }; |
| 239 | | - |
| 240 | 88 | })(window.fossil); |
| 241 | 89 | |