├── .gitignore ├── .layouts ├── 404.html ├── canvas.html ├── docs.html ├── head.html └── nav.html ├── 404.md ├── CNAME ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── _config.yml ├── assets ├── apple-touch-icon.png ├── canvas.js ├── card.png ├── icon.svg ├── prism.js └── style.css ├── docs └── apps.md ├── favicon.ico ├── logo.svg ├── readme.md ├── sample.canvas └── spec └── 1.0.md /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-cache 4 | .jekyll-metadata 5 | .obsidian 6 | vendor 7 | .DS_Store -------------------------------------------------------------------------------- /.layouts/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% include head.html %} 4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 26 | 27 | 28 |
404
29 | {{ content }} 30 |
31 | 32 | 33 |
34 |
35 |

Learn more:

36 | 41 |
42 |
43 |
44 | 45 | 58 | 59 |
60 |
61 | 62 | 63 | 64 | 65 |
66 |
67 |
68 | 69 |
70 | 71 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /.layouts/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% include head.html %} 4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
spec/1.0
24 | 29 |
30 | 31 | 32 |
readme
33 | {{ content }} 34 |
35 | 36 | 37 |
38 |
39 |

Learn more:

40 | 45 |
46 |
47 | 48 | 52 |
53 | 54 | 67 | 68 |
69 |
70 | 71 | 72 | 73 | 74 |
75 |
76 |
77 | 78 |
79 | 80 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /.layouts/docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% include head.html %} 4 | 5 | 6 | {% include nav.html %} 7 | 8 |
9 | {{ content }} 10 |
11 | 12 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.layouts/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ site.title }} — {{ page.title }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.layouts/nav.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /404.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Not found 3 | permalink: /404.html 4 | layout: 404 5 | --- 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |

404

15 | 16 |

Whoops. You've found an unknown part of this infinite canvas. Head back home.

17 |
18 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | jsoncanvas.org -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "github-pages", group: :jekyll_plugins 4 | # If you have any plugins, put them here! 5 | group :jekyll_plugins do 6 | gem "jekyll-feed", "~> 0.12" 7 | end 8 | 9 | gem "webrick", "~> 1.8" 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (7.1.3.2) 5 | base64 6 | bigdecimal 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | connection_pool (>= 2.2.5) 9 | drb 10 | i18n (>= 1.6, < 2) 11 | minitest (>= 5.1) 12 | mutex_m 13 | tzinfo (~> 2.0) 14 | addressable (2.8.6) 15 | public_suffix (>= 2.0.2, < 6.0) 16 | base64 (0.2.0) 17 | bigdecimal (3.1.6) 18 | coffee-script (2.4.1) 19 | coffee-script-source 20 | execjs 21 | coffee-script-source (1.12.2) 22 | colorator (1.1.0) 23 | commonmarker (0.23.10) 24 | concurrent-ruby (1.2.3) 25 | connection_pool (2.4.1) 26 | dnsruby (1.70.0) 27 | simpleidn (~> 0.2.1) 28 | drb (2.2.0) 29 | ruby2_keywords 30 | em-websocket (0.5.3) 31 | eventmachine (>= 0.12.9) 32 | http_parser.rb (~> 0) 33 | ethon (0.16.0) 34 | ffi (>= 1.15.0) 35 | eventmachine (1.2.7) 36 | execjs (2.9.1) 37 | faraday (2.9.0) 38 | faraday-net_http (>= 2.0, < 3.2) 39 | faraday-net_http (3.1.0) 40 | net-http 41 | ffi (1.16.3) 42 | forwardable-extended (2.6.0) 43 | gemoji (4.1.0) 44 | github-pages (231) 45 | github-pages-health-check (= 1.18.2) 46 | jekyll (= 3.9.5) 47 | jekyll-avatar (= 0.8.0) 48 | jekyll-coffeescript (= 1.2.2) 49 | jekyll-commonmark-ghpages (= 0.4.0) 50 | jekyll-default-layout (= 0.1.5) 51 | jekyll-feed (= 0.17.0) 52 | jekyll-gist (= 1.5.0) 53 | jekyll-github-metadata (= 2.16.1) 54 | jekyll-include-cache (= 0.2.1) 55 | jekyll-mentions (= 1.6.0) 56 | jekyll-optional-front-matter (= 0.3.2) 57 | jekyll-paginate (= 1.1.0) 58 | jekyll-readme-index (= 0.3.0) 59 | jekyll-redirect-from (= 0.16.0) 60 | jekyll-relative-links (= 0.6.1) 61 | jekyll-remote-theme (= 0.4.3) 62 | jekyll-sass-converter (= 1.5.2) 63 | jekyll-seo-tag (= 2.8.0) 64 | jekyll-sitemap (= 1.4.0) 65 | jekyll-swiss (= 1.0.0) 66 | jekyll-theme-architect (= 0.2.0) 67 | jekyll-theme-cayman (= 0.2.0) 68 | jekyll-theme-dinky (= 0.2.0) 69 | jekyll-theme-hacker (= 0.2.0) 70 | jekyll-theme-leap-day (= 0.2.0) 71 | jekyll-theme-merlot (= 0.2.0) 72 | jekyll-theme-midnight (= 0.2.0) 73 | jekyll-theme-minimal (= 0.2.0) 74 | jekyll-theme-modernist (= 0.2.0) 75 | jekyll-theme-primer (= 0.6.0) 76 | jekyll-theme-slate (= 0.2.0) 77 | jekyll-theme-tactile (= 0.2.0) 78 | jekyll-theme-time-machine (= 0.2.0) 79 | jekyll-titles-from-headings (= 0.5.3) 80 | jemoji (= 0.13.0) 81 | kramdown (= 2.4.0) 82 | kramdown-parser-gfm (= 1.1.0) 83 | liquid (= 4.0.4) 84 | mercenary (~> 0.3) 85 | minima (= 2.5.1) 86 | nokogiri (>= 1.13.6, < 2.0) 87 | rouge (= 3.30.0) 88 | terminal-table (~> 1.4) 89 | github-pages-health-check (1.18.2) 90 | addressable (~> 2.3) 91 | dnsruby (~> 1.60) 92 | octokit (>= 4, < 8) 93 | public_suffix (>= 3.0, < 6.0) 94 | typhoeus (~> 1.3) 95 | html-pipeline (2.14.3) 96 | activesupport (>= 2) 97 | nokogiri (>= 1.4) 98 | http_parser.rb (0.8.0) 99 | i18n (1.14.1) 100 | concurrent-ruby (~> 1.0) 101 | jekyll (3.9.5) 102 | addressable (~> 2.4) 103 | colorator (~> 1.0) 104 | em-websocket (~> 0.5) 105 | i18n (>= 0.7, < 2) 106 | jekyll-sass-converter (~> 1.0) 107 | jekyll-watch (~> 2.0) 108 | kramdown (>= 1.17, < 3) 109 | liquid (~> 4.0) 110 | mercenary (~> 0.3.3) 111 | pathutil (~> 0.9) 112 | rouge (>= 1.7, < 4) 113 | safe_yaml (~> 1.0) 114 | jekyll-avatar (0.8.0) 115 | jekyll (>= 3.0, < 5.0) 116 | jekyll-coffeescript (1.2.2) 117 | coffee-script (~> 2.2) 118 | coffee-script-source (~> 1.12) 119 | jekyll-commonmark (1.4.0) 120 | commonmarker (~> 0.22) 121 | jekyll-commonmark-ghpages (0.4.0) 122 | commonmarker (~> 0.23.7) 123 | jekyll (~> 3.9.0) 124 | jekyll-commonmark (~> 1.4.0) 125 | rouge (>= 2.0, < 5.0) 126 | jekyll-default-layout (0.1.5) 127 | jekyll (>= 3.0, < 5.0) 128 | jekyll-feed (0.17.0) 129 | jekyll (>= 3.7, < 5.0) 130 | jekyll-gist (1.5.0) 131 | octokit (~> 4.2) 132 | jekyll-github-metadata (2.16.1) 133 | jekyll (>= 3.4, < 5.0) 134 | octokit (>= 4, < 7, != 4.4.0) 135 | jekyll-include-cache (0.2.1) 136 | jekyll (>= 3.7, < 5.0) 137 | jekyll-mentions (1.6.0) 138 | html-pipeline (~> 2.3) 139 | jekyll (>= 3.7, < 5.0) 140 | jekyll-optional-front-matter (0.3.2) 141 | jekyll (>= 3.0, < 5.0) 142 | jekyll-paginate (1.1.0) 143 | jekyll-readme-index (0.3.0) 144 | jekyll (>= 3.0, < 5.0) 145 | jekyll-redirect-from (0.16.0) 146 | jekyll (>= 3.3, < 5.0) 147 | jekyll-relative-links (0.6.1) 148 | jekyll (>= 3.3, < 5.0) 149 | jekyll-remote-theme (0.4.3) 150 | addressable (~> 2.0) 151 | jekyll (>= 3.5, < 5.0) 152 | jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) 153 | rubyzip (>= 1.3.0, < 3.0) 154 | jekyll-sass-converter (1.5.2) 155 | sass (~> 3.4) 156 | jekyll-seo-tag (2.8.0) 157 | jekyll (>= 3.8, < 5.0) 158 | jekyll-sitemap (1.4.0) 159 | jekyll (>= 3.7, < 5.0) 160 | jekyll-swiss (1.0.0) 161 | jekyll-theme-architect (0.2.0) 162 | jekyll (> 3.5, < 5.0) 163 | jekyll-seo-tag (~> 2.0) 164 | jekyll-theme-cayman (0.2.0) 165 | jekyll (> 3.5, < 5.0) 166 | jekyll-seo-tag (~> 2.0) 167 | jekyll-theme-dinky (0.2.0) 168 | jekyll (> 3.5, < 5.0) 169 | jekyll-seo-tag (~> 2.0) 170 | jekyll-theme-hacker (0.2.0) 171 | jekyll (> 3.5, < 5.0) 172 | jekyll-seo-tag (~> 2.0) 173 | jekyll-theme-leap-day (0.2.0) 174 | jekyll (> 3.5, < 5.0) 175 | jekyll-seo-tag (~> 2.0) 176 | jekyll-theme-merlot (0.2.0) 177 | jekyll (> 3.5, < 5.0) 178 | jekyll-seo-tag (~> 2.0) 179 | jekyll-theme-midnight (0.2.0) 180 | jekyll (> 3.5, < 5.0) 181 | jekyll-seo-tag (~> 2.0) 182 | jekyll-theme-minimal (0.2.0) 183 | jekyll (> 3.5, < 5.0) 184 | jekyll-seo-tag (~> 2.0) 185 | jekyll-theme-modernist (0.2.0) 186 | jekyll (> 3.5, < 5.0) 187 | jekyll-seo-tag (~> 2.0) 188 | jekyll-theme-primer (0.6.0) 189 | jekyll (> 3.5, < 5.0) 190 | jekyll-github-metadata (~> 2.9) 191 | jekyll-seo-tag (~> 2.0) 192 | jekyll-theme-slate (0.2.0) 193 | jekyll (> 3.5, < 5.0) 194 | jekyll-seo-tag (~> 2.0) 195 | jekyll-theme-tactile (0.2.0) 196 | jekyll (> 3.5, < 5.0) 197 | jekyll-seo-tag (~> 2.0) 198 | jekyll-theme-time-machine (0.2.0) 199 | jekyll (> 3.5, < 5.0) 200 | jekyll-seo-tag (~> 2.0) 201 | jekyll-titles-from-headings (0.5.3) 202 | jekyll (>= 3.3, < 5.0) 203 | jekyll-watch (2.2.1) 204 | listen (~> 3.0) 205 | jemoji (0.13.0) 206 | gemoji (>= 3, < 5) 207 | html-pipeline (~> 2.2) 208 | jekyll (>= 3.0, < 5.0) 209 | kramdown (2.4.0) 210 | rexml 211 | kramdown-parser-gfm (1.1.0) 212 | kramdown (~> 2.0) 213 | liquid (4.0.4) 214 | listen (3.8.0) 215 | rb-fsevent (~> 0.10, >= 0.10.3) 216 | rb-inotify (~> 0.9, >= 0.9.10) 217 | mercenary (0.3.6) 218 | mini_portile2 (2.8.5) 219 | minima (2.5.1) 220 | jekyll (>= 3.5, < 5.0) 221 | jekyll-feed (~> 0.9) 222 | jekyll-seo-tag (~> 2.1) 223 | minitest (5.22.2) 224 | mutex_m (0.2.0) 225 | net-http (0.4.1) 226 | uri 227 | nokogiri (1.16.2) 228 | mini_portile2 (~> 2.8.2) 229 | racc (~> 1.4) 230 | nokogiri (1.16.2-aarch64-linux) 231 | racc (~> 1.4) 232 | nokogiri (1.16.2-arm-linux) 233 | racc (~> 1.4) 234 | nokogiri (1.16.2-arm64-darwin) 235 | racc (~> 1.4) 236 | nokogiri (1.16.2-x86-linux) 237 | racc (~> 1.4) 238 | nokogiri (1.16.2-x86_64-darwin) 239 | racc (~> 1.4) 240 | nokogiri (1.16.2-x86_64-linux) 241 | racc (~> 1.4) 242 | octokit (4.25.1) 243 | faraday (>= 1, < 3) 244 | sawyer (~> 0.9) 245 | pathutil (0.16.2) 246 | forwardable-extended (~> 2.6) 247 | public_suffix (5.0.4) 248 | racc (1.7.3) 249 | rb-fsevent (0.11.2) 250 | rb-inotify (0.10.1) 251 | ffi (~> 1.0) 252 | rexml (3.2.6) 253 | rouge (3.30.0) 254 | ruby2_keywords (0.0.5) 255 | rubyzip (2.3.2) 256 | safe_yaml (1.0.5) 257 | sass (3.7.4) 258 | sass-listen (~> 4.0.0) 259 | sass-listen (4.0.0) 260 | rb-fsevent (~> 0.9, >= 0.9.4) 261 | rb-inotify (~> 0.9, >= 0.9.7) 262 | sawyer (0.9.2) 263 | addressable (>= 2.3.5) 264 | faraday (>= 0.17.3, < 3) 265 | simpleidn (0.2.1) 266 | unf (~> 0.1.4) 267 | terminal-table (1.8.0) 268 | unicode-display_width (~> 1.1, >= 1.1.1) 269 | typhoeus (1.4.1) 270 | ethon (>= 0.9.0) 271 | tzinfo (2.0.6) 272 | concurrent-ruby (~> 1.0) 273 | unf (0.1.4) 274 | unf_ext 275 | unf_ext (0.0.9.1) 276 | unicode-display_width (1.8.0) 277 | uri (0.13.0) 278 | webrick (1.8.1) 279 | 280 | PLATFORMS 281 | aarch64-linux 282 | aarch64-linux-android 283 | aarch64-linux-musl 284 | arm-linux 285 | arm-linux-androideabi 286 | arm-linux-musleabihf 287 | arm64-darwin 288 | x86-linux 289 | x86-linux-android 290 | x86-linux-musl 291 | x86_64-darwin 292 | x86_64-linux 293 | x86_64-linux-android 294 | x86_64-linux-musl 295 | 296 | DEPENDENCIES 297 | github-pages 298 | jekyll-feed (~> 0.12) 299 | webrick (~> 1.8) 300 | 301 | BUNDLED WITH 302 | 2.5.6 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Obsidian.md 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | name: 'JSON Canvas' 2 | title: 'JSON Canvas' 3 | url: 'https://jsoncanvas.org' 4 | description: 'An open file format for infinite canvas data.' 5 | exclude: ['.obsidian'] 6 | 7 | github: [metadata] 8 | 9 | baseurl: '' 10 | 11 | layouts_dir: .layouts 12 | includes_dir: .layouts 13 | 14 | use_html_extension: false 15 | 16 | permalink: pretty 17 | relative_permalinks: false 18 | 19 | defaults: 20 | - scope: 21 | path: "**/*" 22 | values: 23 | layout: "canvas" 24 | - scope: 25 | path: "spec/**/*.md" 26 | values: 27 | layout: "docs" 28 | - scope: 29 | path: "docs/**/*.md" 30 | values: 31 | layout: "docs" -------------------------------------------------------------------------------- /assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obsidianmd/jsoncanvas/6a703e28021df8fdda8217c097162a86a3a23c53/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/canvas.js: -------------------------------------------------------------------------------- 1 | // Initial state of the canvas 2 | let scale, panOffsetX, panOffsetY; 3 | 4 | const ZOOM_SPEED = 0.1; 5 | const minScale = 0.35; 6 | const maxScale = 1.25; 7 | const container = document.getElementById('canvas-nodes'); 8 | 9 | let isDragging = false; 10 | let isSpacePressed = false; 11 | let isPanning = false; 12 | 13 | let startX = 0; 14 | let startY = 0; 15 | let lastTouchX = 0; 16 | let lastTouchY = 0; 17 | let touchStartPanX = 0; 18 | let touchStartPanY = 0; 19 | 20 | function adjustCanvasToViewport() { 21 | const nodes = document.querySelectorAll('.node'); 22 | let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; 23 | 24 | nodes.forEach(node => { 25 | const x = parseInt(node.style.left, 10); 26 | const y = parseInt(node.style.top, 10); 27 | const width = node.offsetWidth; 28 | const height = node.offsetHeight; 29 | 30 | minX = Math.min(minX, x); 31 | maxX = Math.max(maxX, x + width); 32 | minY = Math.min(minY, y); 33 | maxY = Math.max(maxY, y + height); 34 | }); 35 | 36 | const boundingBoxWidth = maxX - minX; 37 | const boundingBoxHeight = maxY - minY; 38 | const viewportWidth = window.innerWidth; 39 | const viewportHeight = window.innerHeight; 40 | 41 | const scaleX = viewportWidth / (boundingBoxWidth + 80); 42 | const scaleY = viewportHeight / (boundingBoxHeight + 80); 43 | scale = Math.min(scaleX, scaleY, 1); // Ensure the scale is not more than 1 44 | 45 | panOffsetX = (viewportWidth - boundingBoxWidth * scale) / 2 - minX * scale; 46 | panOffsetY = (viewportHeight - boundingBoxHeight * scale) / 2 - minY * scale; 47 | 48 | // Apply the calculated scale and pan offsets 49 | applyPanAndZoom(); 50 | 51 | document.getElementById('canvas-nodes').style.opacity = 1; 52 | document.getElementById('canvas-edges').style.opacity = 1; 53 | } 54 | 55 | document.addEventListener('DOMContentLoaded', adjustCanvasToViewport); 56 | 57 | // Zoom 58 | window.addEventListener('wheel', (e) => { 59 | if (e.ctrlKey || e.metaKey) { 60 | if (e.deltaY > 0) { 61 | scale = Math.max(scale - ZOOM_SPEED, minScale); 62 | } else { 63 | scale = Math.min(scale + ZOOM_SPEED, maxScale); 64 | } 65 | 66 | document.body.style.setProperty('--scale', scale); 67 | e.preventDefault(); 68 | } 69 | }, {passive: false}); 70 | 71 | // Buttons 72 | document.getElementById('zoom-in').addEventListener('click', function() { 73 | scale = Math.min(scale + ZOOM_SPEED, maxScale); 74 | document.body.style.setProperty('--scale', scale); 75 | }); 76 | 77 | document.getElementById('zoom-out').addEventListener('click', function() { 78 | scale = Math.max(scale - ZOOM_SPEED, minScale); 79 | document.body.style.setProperty('--scale', scale); 80 | }); 81 | 82 | document.getElementById('zoom-reset').addEventListener('click', function() { 83 | adjustCanvasToViewport(); 84 | }); 85 | 86 | document.getElementById('toggle-output').addEventListener('click', function() { 87 | const output = document.getElementById('output'); 88 | output.classList.toggle('hidden'); 89 | }); 90 | 91 | document.querySelector('.close-output').addEventListener('click', function() { 92 | const output = document.getElementById('output'); 93 | output.classList.toggle('hidden'); 94 | }); 95 | 96 | document.querySelector('.button-copy').addEventListener('click', function() { 97 | const positionsOutput = document.getElementById('positionsOutput').textContent; 98 | navigator.clipboard.writeText(positionsOutput).catch(err => { 99 | console.error('Error copying canvas data: ', err); 100 | }); 101 | }); 102 | 103 | document.querySelector('.button-download').addEventListener('click', function() { 104 | const positionsOutput = document.getElementById('positionsOutput').textContent; 105 | const blob = new Blob([positionsOutput], { type: 'text/plain' }); 106 | const url = URL.createObjectURL(blob); 107 | const a = document.createElement('a'); 108 | a.href = url; 109 | a.download = 'sample.canvas'; 110 | document.body.appendChild(a); 111 | a.click(); 112 | document.body.removeChild(a); 113 | URL.revokeObjectURL(url); 114 | }); 115 | 116 | // Very simplified Markdown conversion 117 | function htmlToMarkdown(html) { 118 | let markdown = html.replace(//gi, "\n"); 119 | markdown = markdown.replace(/([^<]+)<\/a>/gi, "[$2]($1)"); 120 | markdown = markdown.replace(/