|
| 1 | +embed(); |
| 2 | + |
| 3 | +function embed() { |
| 4 | + const sourceURL = new URL(document.currentScript.src); |
| 5 | + const params = sourceURL.searchParams; |
| 6 | + const target = new URL(params.get("target")); |
| 7 | + const style = params.get("style"); |
| 8 | + const trickyDarkStyle = ["an-old-hope", "androidstudio", "arta", "codepen-embed", "darcula", "dracula", "far", "gml", "hopscotch", "hybrid", "monokai", "monokai-sublime", "nord", "obsidian", "ocean", "railscasts", "rainbow", "shades-of-purple", "sunburst", "vs2015", "xt256", "zenburn"]; // dark styles without 'dark', 'black' or 'night' in its name |
| 9 | + const isDarkStyle = style.includes("dark") || style.includes("black") || style.includes("night") || trickyDarkStyle.includes(style); |
| 10 | + const showBorder = params.get("showBorder") === "on"; |
| 11 | + const showLineNumbers = params.get("showLineNumbers") === "on"; |
| 12 | + const showFileMeta = params.get("showFileMeta") === "on"; |
| 13 | + const showCopy = params.get("showCopy") === "on"; |
| 14 | + const lineSplit = target.hash.split("-"); |
| 15 | + const startLine = target.hash !== "" && lineSplit[0].replace("#L", "") || -1; |
| 16 | + const endLine = target.hash !== "" && lineSplit.length > 1 && lineSplit[1].replace("L", "") || startLine; |
| 17 | + const tabSize = target.searchParams.get("ts") || 8; |
| 18 | + const pathSplit = target.pathname.split("/"); |
| 19 | + const user = pathSplit[1]; |
| 20 | + const repository = pathSplit[2]; |
| 21 | + const branch = pathSplit[4]; |
| 22 | + const file = pathSplit.slice(5, pathSplit.length).join("/"); |
| 23 | + const fileExtension = file.split('.').length > 1 ? file.split('.')[file.split('.').length - 1] : 'txt'; |
| 24 | + const rawFileURL = `https://raw.githubusercontent.com/${user}/${repository}/${branch}/${file}`; |
| 25 | + // The id where code will be embeded. In order to support a single `target` embedded for multiple times, |
| 26 | + // we use a random string to avoid duplicated id. |
| 27 | + const containerId = Math.random().toString(36).substring(2); |
| 28 | + |
| 29 | + // Reserving space for code area should be done in early time |
| 30 | + // or the div may not be found later |
| 31 | + document.write(` |
| 32 | +<style>.lds-ring{margin:1rem auto;position:relative;width:60px;height:60px}.lds-ring div{box-sizing:border-box;display:block;position:absolute;width:48px;height:48px;margin:6px;border:6px solid #fff;border-radius:50%;animation:lds-ring 1.2s cubic-bezier(0.5,0,0.5,1) infinite;border-color:#888 transparent transparent transparent}.lds-ring div:nth-child(1){animation-delay:-.45s}.lds-ring div:nth-child(2){animation-delay:-.3s}.lds-ring div:nth-child(3){animation-delay:-.15s}@keyframes lds-ring{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}</style> |
| 33 | +<div id="${containerId}" class="emgithub-container"><div class="lds-ring"><div></div><div></div><div></div><div></div></div></div> |
| 34 | +<style>.hljs-ln-numbers{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:right;color:#ccc;vertical-align:top}.hljs-ln td.hljs-ln-numbers{padding-right:1.25rem}</style> |
| 35 | +<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/${style}.min.css"> |
| 36 | +<style> |
| 37 | +.emgithub-container .file-meta { |
| 38 | + padding: 0.75rem; |
| 39 | + border-radius: 0 0 0.3rem 0.3rem; |
| 40 | + font: 12px -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, |
| 41 | + sans-serif, Apple Color Emoji, Segoe UI Emoji; |
| 42 | +} |
| 43 | +
|
| 44 | +.emgithub-container .file-meta-light { |
| 45 | + color: #586069; |
| 46 | + background-color: #f7f7f7; |
| 47 | +} |
| 48 | +
|
| 49 | +.emgithub-container .file-meta-dark { |
| 50 | + color: #f7f7f7; |
| 51 | + background-color: #586069; |
| 52 | +} |
| 53 | +
|
| 54 | +.emgithub-container .file-meta a { |
| 55 | + font-weight: 600; |
| 56 | + text-decoration: none; |
| 57 | + border: 0; |
| 58 | +} |
| 59 | +
|
| 60 | +.emgithub-container .file-meta-light a { |
| 61 | + color: #666; |
| 62 | +} |
| 63 | +
|
| 64 | +.emgithub-container .file-meta-dark a { |
| 65 | + color: #fff; |
| 66 | +} |
| 67 | +
|
| 68 | +/* hide content for small device */ |
| 69 | +@media (max-width: 575.98px) { |
| 70 | + .emgithub-container .hide-in-phone { |
| 71 | + display: none; |
| 72 | + } |
| 73 | +} |
| 74 | +
|
| 75 | +.emgithub-container { |
| 76 | + position: relative; |
| 77 | +} |
| 78 | +
|
| 79 | +.emgithub-container .toolbar { |
| 80 | + position: absolute; |
| 81 | + right: 0px; |
| 82 | + padding: 0.3rem; |
| 83 | +} |
| 84 | +
|
| 85 | +.emgithub-container .copy-btn { |
| 86 | + display: none; |
| 87 | + border: 1px solid black; |
| 88 | + border-radius: 3px; |
| 89 | + padding: 0.4rem; |
| 90 | + font: bold 1em monospace; |
| 91 | + text-decoration: none; |
| 92 | +} |
| 93 | +
|
| 94 | +.emgithub-container .copy-btn-light { |
| 95 | + color: #586069; |
| 96 | + background-color: #f7f7f7; |
| 97 | +} |
| 98 | +
|
| 99 | +.emgithub-container .copy-btn-dark { |
| 100 | + color: #f7f7f7; |
| 101 | + background-color: #586069; |
| 102 | +} |
| 103 | +
|
| 104 | +.emgithub-container:hover .copy-btn { |
| 105 | + display: block; |
| 106 | +} |
| 107 | +
|
| 108 | +.emgithub-container .copy-btn-light:hover { |
| 109 | + color: #f7f7f7; |
| 110 | + background-color: #586069; |
| 111 | +} |
| 112 | +
|
| 113 | +.emgithub-container .copy-btn-dark:hover { |
| 114 | + color: #586069; |
| 115 | + background-color: #f7f7f7; |
| 116 | +} |
| 117 | +
|
| 118 | +.emgithub-container .copy-btn-light:active { |
| 119 | + /* darken #586069 by 20% https://www.cssfontstack.com/oldsites/hexcolortool/ */ |
| 120 | + background-color: #252d36; |
| 121 | +} |
| 122 | +
|
| 123 | +.emgithub-container .copy-btn-dark:active { |
| 124 | + /* darken #f7f7f7 by 20% */ |
| 125 | + background-color: #c4c4c4; |
| 126 | +} |
| 127 | +</style> |
| 128 | +`); |
| 129 | + |
| 130 | + const HLJSURL = "https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"; |
| 131 | + const HLJSNumURL = "https://cdn.jsdelivr.net/npm/[email protected]/dist/highlightjs-line-numbers.min.js"; |
| 132 | + const loadHLJS = (typeof hljs != "undefined" && typeof hljs.highlightBlock != "undefined") ? |
| 133 | + Promise.resolve() : loadScript(HLJSURL); |
| 134 | + let loadHLJSNum; |
| 135 | + if (showLineNumbers) { |
| 136 | + // hljs-num should be loaded only after hljs is loaded |
| 137 | + loadHLJSNum = loadHLJS.then(() => |
| 138 | + (typeof hljs != "undefined" && typeof hljs.lineNumbersBlock != "undefined") ? |
| 139 | + Promise.resolve() : loadScript(HLJSNumURL) |
| 140 | + ) |
| 141 | + } |
| 142 | + |
| 143 | + const fetchFile = fetch(rawFileURL).then((response) => { |
| 144 | + if (response.ok) { |
| 145 | + return response.text(); |
| 146 | + } else { |
| 147 | + return Promise.reject(`${response.status} ${response.statusText}`); |
| 148 | + } |
| 149 | + }); |
| 150 | + |
| 151 | + Promise.all(showLineNumbers ? [fetchFile, loadHLJS, loadHLJSNum] : [fetchFile, loadHLJS]).then((result) => { |
| 152 | + const targetDiv = document.getElementById(containerId); |
| 153 | + embedCodeToTarget(targetDiv, result[0], showBorder, showLineNumbers, showFileMeta, showCopy, isDarkStyle, target.href, rawFileURL, fileExtension, startLine, endLine, tabSize, sourceURL.origin); |
| 154 | + }).catch((error) => { |
| 155 | + const errorMsg = `Failed to process ${rawFileURL} |
| 156 | +${error}`; |
| 157 | + const targetDiv = document.getElementById(containerId); |
| 158 | + embedCodeToTarget(targetDiv, errorMsg, showBorder, showLineNumbers, showFileMeta, showCopy, isDarkStyle, target.href, rawFileURL, 'plaintext', -1, -1, tabSize, sourceURL.origin); |
| 159 | + }); |
| 160 | +} |
| 161 | + |
| 162 | +function loadScript(src) { |
| 163 | + return new Promise((resolve, reject) => { |
| 164 | + const script = document.createElement('script'); |
| 165 | + script.src = src; |
| 166 | + script.onload = resolve; |
| 167 | + script.onerror = reject; |
| 168 | + document.head.appendChild(script); |
| 169 | + }); |
| 170 | +} |
| 171 | + |
| 172 | + |
| 173 | +function embedCodeToTarget(targetDiv, codeText, showBorder, showLineNumbers, showFileMeta, showCopy, isDarkStyle, fileURL, rawFileURL, lang, startLine, endLine, tabSize, serviceProvider) { |
| 174 | + targetDiv.innerHTML = ""; |
| 175 | + targetDiv.style.margin = "1em 0"; |
| 176 | + |
| 177 | + const code = document.createElement("code"); |
| 178 | + code.style.padding = "1rem"; |
| 179 | + |
| 180 | + if (showFileMeta) { |
| 181 | + code.style.borderRadius = "0.3rem 0.3rem 0 0"; |
| 182 | + } else { |
| 183 | + code.style.borderRadius = "0.3rem"; |
| 184 | + } |
| 185 | + if (showBorder) { |
| 186 | + if (!isDarkStyle) { |
| 187 | + code.style.border = "1px solid #ddd"; |
| 188 | + } else { |
| 189 | + code.style.border = "1px solid #555"; |
| 190 | + } |
| 191 | + } |
| 192 | + code.classList.add(lang); |
| 193 | + if (codeText[codeText.length -1] === "\n") { |
| 194 | + // First remove the ending newline |
| 195 | + codeText = codeText.slice(0,-1); |
| 196 | + } |
| 197 | + if (startLine > 0) { |
| 198 | + codeTextSplit = codeText.split("\n"); |
| 199 | + codeText = codeTextSplit.slice(startLine - 1, endLine).join("\n"); |
| 200 | + } |
| 201 | + // Then add the newline back |
| 202 | + codeText = codeText + "\n"; |
| 203 | + code.textContent = codeText; |
| 204 | + if (typeof hljs != "undefined" && typeof hljs.highlightBlock != "undefined") { |
| 205 | + hljs.highlightBlock(code); |
| 206 | + } |
| 207 | + if (typeof hljs != "undefined" && typeof hljs.lineNumbersBlock != "undefined" && showLineNumbers) { |
| 208 | + hljs.lineNumbersBlock(code, { |
| 209 | + singleLine: true, |
| 210 | + startFrom: startLine > 0 ? Number.parseInt(startLine) : 1 |
| 211 | + }); |
| 212 | + } |
| 213 | + |
| 214 | + if(showCopy) { |
| 215 | + const toolbar = document.createElement('div'); |
| 216 | + toolbar.classList.add('toolbar'); |
| 217 | + |
| 218 | + const copyButton = document.createElement('a'); |
| 219 | + copyButton.classList.add('copy-btn'); |
| 220 | + if(isDarkStyle) { |
| 221 | + copyButton.classList.add('copy-btn-dark'); |
| 222 | + } else { |
| 223 | + copyButton.classList.add('copy-btn-light'); |
| 224 | + } |
| 225 | + copyButton.href = 'javascript:void(0);' |
| 226 | + copyButton.innerHTML = 'Copy'; |
| 227 | + copyButton.addEventListener('click', function(e) { |
| 228 | + e.preventDefault(); |
| 229 | + e.cancelBubble = true; |
| 230 | + copyTextToClipboard(codeText); |
| 231 | + }); |
| 232 | + |
| 233 | + toolbar.appendChild(copyButton); |
| 234 | + targetDiv.appendChild(toolbar); |
| 235 | + } |
| 236 | + |
| 237 | + // Not use a real `pre` to avoid style being overwritten |
| 238 | + // Simulate a real one by using its default style |
| 239 | + const customPre = document.createElement("div"); |
| 240 | + customPre.style.whiteSpace = "pre"; |
| 241 | + customPre.style.tabSize = tabSize; |
| 242 | + customPre.appendChild(code); |
| 243 | + targetDiv.appendChild(customPre); |
| 244 | + |
| 245 | + if (showFileMeta) { |
| 246 | + const meta = document.createElement("div"); |
| 247 | + const rawFileURLSplit = rawFileURL.split("/"); |
| 248 | + meta.innerHTML = `<a target="_blank" href="${rawFileURL}" style="float:right">view raw</a> |
| 249 | +<a target="_blank" href="${fileURL}">${rawFileURLSplit[rawFileURLSplit.length - 1]}</a> |
| 250 | +delivered <span class="hide-in-phone">with ❤ </span>by <a target="_blank" href="${serviceProvider}">EmGithub</a>`; |
| 251 | + meta.classList.add("file-meta"); |
| 252 | + if (!isDarkStyle) { |
| 253 | + meta.classList.add("file-meta-light"); |
| 254 | + if (showBorder) { |
| 255 | + meta.style.border = "1px solid #ddd"; |
| 256 | + meta.style.borderTop = "0"; |
| 257 | + } |
| 258 | + } else { |
| 259 | + meta.classList.add("file-meta-dark"); |
| 260 | + if (showBorder) { |
| 261 | + meta.style.border = "1px solid #555"; |
| 262 | + meta.style.borderTop = "0"; |
| 263 | + } |
| 264 | + } |
| 265 | + targetDiv.appendChild(meta); |
| 266 | + } |
| 267 | +} |
| 268 | + |
| 269 | +// https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript |
| 270 | +function copyTextToClipboard(text) { |
| 271 | + if (!navigator.clipboard) { |
| 272 | + fallbackCopyTextToClipboard(text); |
| 273 | + return; |
| 274 | + } |
| 275 | + navigator.clipboard.writeText(text) |
| 276 | +} |
| 277 | + |
| 278 | +function fallbackCopyTextToClipboard(text) { |
| 279 | + const textArea = document.createElement("textarea"); |
| 280 | + textArea.value = text; |
| 281 | + textArea.style.position = "fixed"; //avoid scrolling to bottom |
| 282 | + document.body.appendChild(textArea); |
| 283 | + textArea.focus(); |
| 284 | + textArea.select(); |
| 285 | + |
| 286 | + try { |
| 287 | + document.execCommand('copy'); |
| 288 | + } catch (err) { |
| 289 | + console.error('fallbackCopyTextToClipboard: Oops, unable to copy', err); |
| 290 | + } |
| 291 | + document.body.removeChild(textArea); |
| 292 | +} |
0 commit comments