├── .gitignore ├── .vscode └── launch.json ├── .vscodeignore ├── README.md ├── changelog.md ├── demo ├── 1.png ├── 2.png ├── 3.png └── usage.gif ├── icon.png ├── jsconfig.json ├── package.json ├── src └── extension.js ├── webview ├── dom2image.js ├── index.html ├── index.js └── vivus.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | *.vsix 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Polacode", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 10 | "stopOnEntry": false 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | demo 3 | *.vsix 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

Polacode — Polaroid for your code 📸

3 |

4 | 5 | 6 | ![usage](./demo/usage.gif) 7 | 8 | ## Why? 9 | 10 | You have spent countless hours finding the perfect [JavaScript grammar](https://marketplace.visualstudio.com/search?term=javascript%20grammar&target=VSCode&category=All%20categories&sortBy=Relevance), matching it with a [sleek-looking VS Code theme](https://marketplace.visualstudio.com/search?target=VSCode&category=Themes&sortBy=Downloads), trying out all the [best programming fonts](https://www.slant.co/topics/67/~best-programming-fonts). 11 | 12 | You take three days porting over [your theme](https://github.com/wesbos/cobalt2-vscode) before starting to use VS Code. 13 | You shell out $200 for [italic cursive html attributes](https://www.typography.com/blog/introducing-operator). 14 | 15 | The code has to look right. 16 | 17 | ## Tips 18 | 19 | - Resize the snippet / container by dragging the lowerright corner 20 | - Use `polacode.target`, `polacode.shadow`, `polacode.transparentBackground` and `polacode.backgroundColor` to control image appearance 21 | 22 | ## Demo 23 | 24 | [Nord](https://github.com/arcticicestudio/nord-visual-studio-code) + [Input Mono](http://input.fontbureau.com) 25 | 26 | ![demo1](https://raw.githubusercontent.com/octref/polacode/master/demo/1.png) 27 | 28 | [Monokai Pro](https://marketplace.visualstudio.com/items?itemName=monokai.theme-monokai-pro-vscode) + [Operator Mono](https://www.typography.com/blog/introducing-operator) 29 | 30 | ![demo2](https://raw.githubusercontent.com/octref/polacode/master/demo/2.png) 31 | 32 | [Material Theme Palenight](https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme) + [Fira Code](https://github.com/tonsky/FiraCode) 33 | 34 | ![demo3](https://raw.githubusercontent.com/octref/polacode/master/demo/3.png) 35 | 36 | ## Credit 37 | 38 | Thanks to [@tsayen](https://github.com/tsayen) for making [dom-to-image](https://github.com/tsayen/dom-to-image), which Polacode is using for generating the images. 39 | 40 | Thanks to [Dawn Labs](https://dawnlabs.io) for making [Carbon](https://carbon.now.sh) that inspired Polacode. 41 | 42 | Many color are taken from the elegant [Nord](https://github.com/arcticicestudio/nord) theme by [@arcticicestudio](https://github.com/arcticicestudio). 43 | 44 | Download button animation is made with [Vivus](https://github.com/maxwellito/vivus). 45 | 46 | ## Contribution 47 | 48 | Contribution is not very welcome. 49 | Please open an issue first so I can stop you from complicating the UX. 50 | 51 | ## License 52 | 53 | MIT -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 0.3.4 | 2019-09-27 4 | 5 | - Fix Polacode not loading background color correctly. [#125](https://github.com/octref/polacode/issues/125). 6 | 7 | ### 0.3.3 | 2019-09-24 8 | 9 | - 🙌 Fix Polacode not loading `font-family` correctly. Thanks to contribution from [@kufii](https://github.com/kufii). [#121](https://github.com/octref/polacode/pull/121). 10 | 11 | ### 0.3.2 | 2019-05-10 12 | 13 | - Vertically center code in the snippet. #108. 14 | 15 | ### 0.3.1 | 2019-05-07 16 | 17 | - Dispose selection sync after closing Polacode window so selection no longer jams clipboard. #107. 18 | 19 | ### 0.3.0 | 2019-05-07 20 | 21 | - Major rewrite. See features in [#105](https://github.com/octref/polacode/pull/105). 22 | - Long token wrapping. Thanks to [OhYee](https://github.com/OhYee). [#104](https://github.com/octref/polacode/pull/104). 23 | - Improved vertical alignment of snippet. Thanks to [vxna](https://github.com/vxna). [#106](https://github.com/octref/polacode/pull/106). 24 | 25 | ### 0.2.2 | 2018-02-19 26 | 27 | - Remove "Pasted content is invalid" check. Will do proper HTML validation in [#30](https://github.com/octref/polacode/issues/30). 28 | 29 | ### 0.2.1 | 2018-02-19 30 | 31 | - Fix an issue where Polacode incorrectly reports "Pasted content is invalid". 32 | 33 | ### 0.2.0 | 2018-02-16 34 | 35 | - Add warning when pasted content is not valid HTML copy-pasted from VS Code. 36 | - Remove backdrop for snippet with light background. 37 | - Find the line with smallest indentation and detract that indentation from all lines. 38 | - Add `polacode.shoot` command that you can bind command to. 39 | - Remember last used image save path. 40 | - Wrap when code is longer than Polacode preview. 41 | - Initialize with correct fontFamily. 42 | 43 | ### 0.1.2 | 2018-02-16 44 | 45 | - Update readme with some tip and explanation. 46 | - Make default filename `code.png`. 47 | 48 | ### 0.1.1 | 2018-02-15 49 | 50 | - Fix a Windows path issue. 51 | - Use correct background for snippet when pasting. 52 | 53 | ### 0.1.0 | 2018-02-15 54 | 55 | - Initial release -------------------------------------------------------------------------------- /demo/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octref/polacode/8d17e92c63ad6785283d142fe4da82e9d840f557/demo/1.png -------------------------------------------------------------------------------- /demo/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octref/polacode/8d17e92c63ad6785283d142fe4da82e9d840f557/demo/2.png -------------------------------------------------------------------------------- /demo/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octref/polacode/8d17e92c63ad6785283d142fe4da82e9d840f557/demo/3.png -------------------------------------------------------------------------------- /demo/usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octref/polacode/8d17e92c63ad6785283d142fe4da82e9d840f557/demo/usage.gif -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octref/polacode/8d17e92c63ad6785283d142fe4da82e9d840f557/icon.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "moduleResolution": "node" 6 | } 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polacode", 3 | "displayName": "Polacode", 4 | "description": "📸 Polaroid for your code", 5 | "version": "0.3.4", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/octref/polacode.git" 9 | }, 10 | "publisher": "pnp", 11 | "keywords": [ 12 | "polacode", 13 | "polaroid", 14 | "screenshot", 15 | "snippet", 16 | "share" 17 | ], 18 | "galleryBanner": { 19 | "color": "#fbfbfb", 20 | "theme": "light" 21 | }, 22 | "icon": "icon.png", 23 | "categories": [ 24 | "Other" 25 | ], 26 | "engines": { 27 | "vscode": "^1.32.0" 28 | }, 29 | "activationEvents": [ 30 | "onCommand:polacode.activate", 31 | "onWebviewPanel:polacode" 32 | ], 33 | "main": "./src/extension", 34 | "contributes": { 35 | "commands": [ 36 | { 37 | "command": "polacode.activate", 38 | "title": "Polacode 📸" 39 | } 40 | ], 41 | "configuration": { 42 | "title": "Polacode", 43 | "properties": { 44 | "polacode.shadow": { 45 | "type": "string", 46 | "description": "Shadow of the snippet node. Use any value for CSS `box-shadow`", 47 | "default": "rgba(0, 0, 0, 0.55) 0px 20px 68px" 48 | }, 49 | "polacode.transparentBackground": { 50 | "type": "boolean", 51 | "description": "Transparent background for containers", 52 | "default": false 53 | }, 54 | "polacode.backgroundColor": { 55 | "type": "string", 56 | "description": "Background color of snippet container. Use any value for CSS `background-color`", 57 | "format": "color-hex", 58 | "default": "#f2f2f2" 59 | }, 60 | "polacode.target": { 61 | "type": "string", 62 | "description": "Shoot with or without container", 63 | "default": "container", 64 | "enum": [ 65 | "container", 66 | "snippet" 67 | ], 68 | "enumDescriptions": [ 69 | "Shoot with the container.", 70 | "Shoot with the snippet alone. If you want transparent padding, use `container` with `\"polacode.transparentBackground\": true`" 71 | ] 72 | } 73 | } 74 | } 75 | }, 76 | "devDependencies": { 77 | "@types/node": "^11.12.0", 78 | "@types/vscode": "^1.32.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/extension.js: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const { homedir } = require('os') 5 | 6 | const writeSerializedBlobToFile = (serializeBlob, fileName) => { 7 | const bytes = new Uint8Array(serializeBlob.split(',')) 8 | fs.writeFileSync(fileName, Buffer.from(bytes)) 9 | } 10 | 11 | const P_TITLE = 'Polacode 📸' 12 | 13 | /** 14 | * @param {vscode.ExtensionContext} context 15 | */ 16 | function activate(context) { 17 | const htmlPath = path.resolve(context.extensionPath, 'webview/index.html') 18 | 19 | let lastUsedImageUri = vscode.Uri.file(path.resolve(homedir(), 'Desktop/code.png')) 20 | let panel 21 | 22 | vscode.window.registerWebviewPanelSerializer('polacode', { 23 | async deserializeWebviewPanel(_panel, state) { 24 | panel = _panel 25 | panel.webview.html = getHtmlContent(htmlPath) 26 | panel.webview.postMessage({ 27 | type: 'restore', 28 | innerHTML: state.innerHTML, 29 | bgColor: context.globalState.get('polacode.bgColor', '#2e3440') 30 | }) 31 | const selectionListener = setupSelectionSync() 32 | panel.onDidDispose(() => { 33 | selectionListener.dispose() 34 | }) 35 | setupMessageListeners() 36 | } 37 | }) 38 | 39 | vscode.commands.registerCommand('polacode.activate', () => { 40 | panel = vscode.window.createWebviewPanel('polacode', P_TITLE, 2, { 41 | enableScripts: true, 42 | localResourceRoots: [vscode.Uri.file(path.join(context.extensionPath, 'webview'))] 43 | }) 44 | 45 | panel.webview.html = getHtmlContent(htmlPath) 46 | 47 | const selectionListener = setupSelectionSync() 48 | panel.onDidDispose(() => { 49 | selectionListener.dispose() 50 | }) 51 | 52 | setupMessageListeners() 53 | 54 | const fontFamily = vscode.workspace.getConfiguration('editor').fontFamily 55 | const bgColor = context.globalState.get('polacode.bgColor', '#2e3440') 56 | panel.webview.postMessage({ 57 | type: 'init', 58 | fontFamily, 59 | bgColor 60 | }) 61 | 62 | syncSettings() 63 | }) 64 | 65 | vscode.workspace.onDidChangeConfiguration(e => { 66 | if (e.affectsConfiguration('polacode') || e.affectsConfiguration('editor')) { 67 | syncSettings() 68 | } 69 | }) 70 | 71 | function setupMessageListeners() { 72 | panel.webview.onDidReceiveMessage(({ type, data }) => { 73 | switch (type) { 74 | case 'shoot': 75 | vscode.window 76 | .showSaveDialog({ 77 | defaultUri: lastUsedImageUri, 78 | filters: { 79 | Images: ['png'] 80 | } 81 | }) 82 | .then(uri => { 83 | if (uri) { 84 | writeSerializedBlobToFile(data.serializedBlob, uri.fsPath) 85 | lastUsedImageUri = uri 86 | } 87 | }) 88 | break 89 | case 'getAndUpdateCacheAndSettings': 90 | panel.webview.postMessage({ 91 | type: 'restoreBgColor', 92 | bgColor: context.globalState.get('polacode.bgColor', '#2e3440') 93 | }) 94 | 95 | syncSettings() 96 | break 97 | case 'updateBgColor': 98 | context.globalState.update('polacode.bgColor', data.bgColor) 99 | break 100 | case 'invalidPasteContent': 101 | vscode.window.showInformationMessage( 102 | 'Pasted content is invalid. Only copy from VS Code and check if your shortcuts for copy/paste have conflicts.' 103 | ) 104 | break 105 | } 106 | }) 107 | } 108 | 109 | function syncSettings() { 110 | const settings = vscode.workspace.getConfiguration('polacode') 111 | const editorSettings = vscode.workspace.getConfiguration('editor', null) 112 | panel.webview.postMessage({ 113 | type: 'updateSettings', 114 | shadow: settings.get('shadow'), 115 | transparentBackground: settings.get('transparentBackground'), 116 | backgroundColor: settings.get('backgroundColor'), 117 | target: settings.get('target'), 118 | ligature: editorSettings.get('fontLigatures') 119 | }) 120 | } 121 | 122 | function setupSelectionSync() { 123 | return vscode.window.onDidChangeTextEditorSelection(e => { 124 | if (e.selections[0] && !e.selections[0].isEmpty) { 125 | vscode.commands.executeCommand('editor.action.clipboardCopyWithSyntaxHighlightingAction') 126 | panel.postMessage({ 127 | type: 'update' 128 | }) 129 | } 130 | }) 131 | } 132 | } 133 | 134 | function getHtmlContent(htmlPath) { 135 | const htmlContent = fs.readFileSync(htmlPath, 'utf-8') 136 | return htmlContent.replace(/script src="([^"]*)"/g, (match, src) => { 137 | const realSource = 'vscode-resource:' + path.resolve(htmlPath, '..', src) 138 | return `script src="${realSource}"` 139 | }) 140 | } 141 | 142 | exports.activate = activate 143 | -------------------------------------------------------------------------------- /webview/dom2image.js: -------------------------------------------------------------------------------- 1 | /*! dom-to-image 10-06-2017 */ 2 | !function(a){"use strict";function b(a,b){function c(a){return b.bgcolor&&(a.style.backgroundColor=b.bgcolor),b.width&&(a.style.width=b.width+"px"),b.height&&(a.style.height=b.height+"px"),b.style&&Object.keys(b.style).forEach(function(c){a.style[c]=b.style[c]}),a}return b=b||{},g(b),Promise.resolve(a).then(function(a){return i(a,b.filter,!0)}).then(j).then(k).then(c).then(function(c){return l(c,b.width||q.width(a),b.height||q.height(a))})}function c(a,b){return h(a,b||{}).then(function(b){return b.getContext("2d").getImageData(0,0,q.width(a),q.height(a)).data})}function d(a,b){return h(a,b||{}).then(function(a){return a.toDataURL()})}function e(a,b){return b=b||{},h(a,b).then(function(a){return a.toDataURL("image/jpeg",b.quality||1)})}function f(a,b){return h(a,b||{}).then(q.canvasToBlob)}function g(a){"undefined"==typeof a.imagePlaceholder?v.impl.options.imagePlaceholder=u.imagePlaceholder:v.impl.options.imagePlaceholder=a.imagePlaceholder,"undefined"==typeof a.cacheBust?v.impl.options.cacheBust=u.cacheBust:v.impl.options.cacheBust=a.cacheBust}function h(a,c){function d(a){var b=document.createElement("canvas");if(b.width=c.width||q.width(a),b.height=c.height||q.height(a),c.bgcolor){var d=b.getContext("2d");d.fillStyle=c.bgcolor,d.fillRect(0,0,b.width,b.height)}return b}return b(a,c).then(q.makeImage).then(q.delay(100)).then(function(b){var c=d(a);return c.getContext("2d").drawImage(b,0,0),c})}function i(a,b,c){function d(a){return a instanceof HTMLCanvasElement?q.makeImage(a.toDataURL()):a.cloneNode(!1)}function e(a,b,c){function d(a,b,c){var d=Promise.resolve();return b.forEach(function(b){d=d.then(function(){return i(b,c)}).then(function(b){b&&a.appendChild(b)})}),d}var e=a.childNodes;return 0===e.length?Promise.resolve(b):d(b,q.asArray(e),c).then(function(){return b})}function f(a,b){function c(){function c(a,b){function c(a,b){q.asArray(a).forEach(function(c){b.setProperty(c,a.getPropertyValue(c),a.getPropertyPriority(c))})}a.cssText?b.cssText=a.cssText:c(a,b)}c(window.getComputedStyle(a),b.style)}function d(){function c(c){function d(a,b,c){function d(a){var b=a.getPropertyValue("content");return a.cssText+" content: "+b+";"}function e(a){function b(b){return b+": "+a.getPropertyValue(b)+(a.getPropertyPriority(b)?" !important":"")}return q.asArray(a).map(b).join("; ")+";"}var f="."+a+":"+b,g=c.cssText?d(c):e(c);return document.createTextNode(f+"{"+g+"}")}var e=window.getComputedStyle(a,c),f=e.getPropertyValue("content");if(""!==f&&"none"!==f){var g=q.uid();b.className=b.className+" "+g;var h=document.createElement("style");h.appendChild(d(g,c,e)),b.appendChild(h)}}[":before",":after"].forEach(function(a){c(a)})}function e(){a instanceof HTMLTextAreaElement&&(b.innerHTML=a.value),a instanceof HTMLInputElement&&b.setAttribute("value",a.value)}function f(){b instanceof SVGElement&&(b.setAttribute("xmlns","http://www.w3.org/2000/svg"),b instanceof SVGRectElement&&["width","height"].forEach(function(a){var c=b.getAttribute(a);c&&b.style.setProperty(a,c)}))}return b instanceof Element?Promise.resolve().then(c).then(d).then(e).then(f).then(function(){return b}):b}return c||!b||b(a)?Promise.resolve(a).then(d).then(function(c){return e(a,c,b)}).then(function(b){return f(a,b)}):Promise.resolve()}function j(a){return s.resolveAll().then(function(b){var c=document.createElement("style");return a.appendChild(c),c.appendChild(document.createTextNode(b)),a})}function k(a){return t.inlineAll(a).then(function(){return a})}function l(a,b,c){return Promise.resolve(a).then(function(a){return a.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),(new XMLSerializer).serializeToString(a)}).then(q.escapeXhtml).then(function(a){return''+a+""}).then(function(a){return''+a+""}).then(function(a){return"data:image/svg+xml;charset=utf-8,"+a})}function m(){function a(){var a="application/font-woff",b="image/jpeg";return{woff:a,woff2:a,ttf:"application/font-truetype",eot:"application/vnd.ms-fontobject",png:"image/png",jpg:b,jpeg:b,gif:"image/gif",tiff:"image/tiff",svg:"image/svg+xml"}}function b(a){var b=/\.([^\.\/]*?)$/g.exec(a);return b?b[1]:""}function c(c){var d=b(c).toLowerCase();return a()[d]||""}function d(a){return a.search(/^(data:)/)!==-1}function e(a){return new Promise(function(b){for(var c=window.atob(a.toDataURL().split(",")[1]),d=c.length,e=new Uint8Array(d),f=0;f 2 | 3 | 7 | 70 | 71 | 72 | 73 |
74 |
console.log('0. Run command `Polacode 📸 `')
console.log('1. Copy some code')
console.log('2. Paste into Polacode view')
console.log('3. Click the button 📸 ')
75 |
76 | 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /webview/index.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | const vscode = acquireVsCodeApi() 3 | 4 | let target = 'container' 5 | let transparentBackground = false 6 | let backgroundColor = '#f2f2f2' 7 | 8 | vscode.postMessage({ 9 | type: 'getAndUpdateCacheAndSettings' 10 | }) 11 | 12 | const snippetNode = document.getElementById('snippet') 13 | const snippetContainerNode = document.getElementById('snippet-container') 14 | const obturateur = document.getElementById('save') 15 | 16 | snippetContainerNode.style.opacity = '1' 17 | const oldState = vscode.getState(); 18 | if (oldState && oldState.innerHTML) { 19 | snippetNode.innerHTML = oldState.innerHTML 20 | } 21 | 22 | const getInitialHtml = fontFamily => { 23 | const cameraWithFlashEmoji = String.fromCodePoint(128248) 24 | const monoFontStack = `${fontFamily},SFMono-Regular,Consolas,DejaVu Sans Mono,Ubuntu Mono,Liberation Mono,Menlo,Courier,monospace` 25 | return `
console.log('0. Run command \`Polacode ${cameraWithFlashEmoji}\`')
console.log('1. Copy some code')
console.log('2. Paste into Polacode view')
console.log('3. Click the button ${cameraWithFlashEmoji}')
` 26 | } 27 | 28 | const serializeBlob = (blob, cb) => { 29 | const fileReader = new FileReader() 30 | 31 | fileReader.onload = () => { 32 | const bytes = new Uint8Array(fileReader.result) 33 | cb(Array.from(bytes).join(',')) 34 | } 35 | function getBrightness(color) { 36 | const rgb = this.toRgb() 37 | return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 38 | } 39 | 40 | fileReader.readAsArrayBuffer(blob) 41 | } 42 | 43 | function shoot(serializedBlob) { 44 | vscode.postMessage({ 45 | type: 'shoot', 46 | data: { 47 | serializedBlob 48 | } 49 | }) 50 | } 51 | 52 | function getBrightness(hexColor) { 53 | const rgb = parseInt(hexColor.slice(1), 16) 54 | const r = (rgb >> 16) & 0xff 55 | const g = (rgb >> 8) & 0xff 56 | const b = (rgb >> 0) & 0xff 57 | return (r * 299 + g * 587 + b * 114) / 1000 58 | } 59 | function isDark(hexColor) { 60 | return getBrightness(hexColor) < 128 61 | } 62 | function getSnippetBgColor(html) { 63 | const match = html.match(/background-color: (#[a-fA-F0-9]+)/) 64 | return match ? match[1] : undefined; 65 | } 66 | 67 | function updateEnvironment(snippetBgColor) { 68 | // update snippet bg color 69 | document.getElementById('snippet').style.backgroundColor = snippetBgColor 70 | 71 | // update backdrop color 72 | if (isDark(snippetBgColor)) { 73 | snippetContainerNode.style.backgroundColor = '#f2f2f2' 74 | } else { 75 | snippetContainerNode.style.background = 'none' 76 | } 77 | } 78 | 79 | function getMinIndent(code) { 80 | const arr = code.split('\n') 81 | 82 | let minIndentCount = Number.MAX_VALUE 83 | for (let i = 0; i < arr.length; i++) { 84 | const wsCount = arr[i].search(/\S/) 85 | if (wsCount !== -1) { 86 | if (wsCount < minIndentCount) { 87 | minIndentCount = wsCount 88 | } 89 | } 90 | } 91 | 92 | return minIndentCount 93 | } 94 | 95 | function stripInitialIndent(html, indent) { 96 | const doc = new DOMParser().parseFromString(html, 'text/html') 97 | const initialSpans = doc.querySelectorAll('div > div span:first-child') 98 | for (let i = 0; i < initialSpans.length; i++) { 99 | initialSpans[i].textContent = initialSpans[i].textContent.slice(indent) 100 | } 101 | return doc.body.innerHTML 102 | } 103 | 104 | document.addEventListener('paste', e => { 105 | const innerHTML = e.clipboardData.getData('text/html') 106 | 107 | const code = e.clipboardData.getData('text/plain') 108 | const minIndent = getMinIndent(code) 109 | 110 | const snippetBgColor = getSnippetBgColor(innerHTML) 111 | if (snippetBgColor) { 112 | vscode.postMessage({ 113 | type: 'updateBgColor', 114 | data: { 115 | bgColor: snippetBgColor 116 | } 117 | }) 118 | updateEnvironment(snippetBgColor) 119 | } 120 | 121 | if (minIndent !== 0) { 122 | snippetNode.innerHTML = stripInitialIndent(innerHTML, minIndent) 123 | } else { 124 | snippetNode.innerHTML = innerHTML 125 | } 126 | 127 | vscode.setState({ innerHTML }) 128 | }) 129 | 130 | obturateur.addEventListener('click', () => { 131 | if (target === 'container') { 132 | shootAll() 133 | } else { 134 | shootSnippet() 135 | } 136 | }) 137 | 138 | function shootAll() { 139 | const width = snippetContainerNode.offsetWidth * 2 140 | const height = snippetContainerNode.offsetHeight * 2 141 | const config = { 142 | width, 143 | height, 144 | style: { 145 | transform: 'scale(2)', 146 | 'transform-origin': 'center', 147 | background: getRgba(backgroundColor, transparentBackground) 148 | } 149 | } 150 | 151 | // Hide resizer before capture 152 | snippetNode.style.resize = 'none' 153 | snippetContainerNode.style.resize = 'none' 154 | 155 | domtoimage.toBlob(snippetContainerNode, config).then(blob => { 156 | snippetNode.style.resize = '' 157 | snippetContainerNode.style.resize = '' 158 | serializeBlob(blob, serializedBlob => { 159 | shoot(serializedBlob) 160 | }) 161 | }) 162 | } 163 | 164 | function shootSnippet() { 165 | const width = snippetNode.offsetWidth * 2 166 | const height = snippetNode.offsetHeight * 2 167 | const config = { 168 | width, 169 | height, 170 | style: { 171 | transform: 'scale(2)', 172 | 'transform-origin': 'center', 173 | padding: 0, 174 | background: 'none' 175 | } 176 | } 177 | 178 | // Hide resizer before capture 179 | snippetNode.style.resize = 'none' 180 | snippetContainerNode.style.resize = 'none' 181 | 182 | domtoimage.toBlob(snippetContainerNode, config).then(blob => { 183 | snippetNode.style.resize = '' 184 | snippetContainerNode.style.resize = '' 185 | serializeBlob(blob, serializedBlob => { 186 | shoot(serializedBlob) 187 | }) 188 | }) 189 | } 190 | 191 | let isInAnimation = false 192 | 193 | obturateur.addEventListener('mouseover', () => { 194 | if (!isInAnimation) { 195 | isInAnimation = true 196 | 197 | new Vivus( 198 | 'save', 199 | { 200 | duration: 40, 201 | onReady: () => { 202 | obturateur.className = 'obturateur filling' 203 | } 204 | }, 205 | () => { 206 | setTimeout(() => { 207 | isInAnimation = false 208 | obturateur.className = 'obturateur' 209 | }, 700) 210 | } 211 | ) 212 | } 213 | }) 214 | 215 | window.addEventListener('message', e => { 216 | if (e) { 217 | if (e.data.type === 'init') { 218 | const { fontFamily, bgColor } = e.data 219 | 220 | const initialHtml = getInitialHtml(fontFamily) 221 | snippetNode.innerHTML = initialHtml 222 | vscode.setState({ innerHTML: initialHtml }) 223 | 224 | // update backdrop color, using bgColor from last pasted snippet 225 | // cannot deduce from initialHtml since it's always using Nord color 226 | if (isDark(bgColor)) { 227 | snippetContainerNode.style.backgroundColor = '#f2f2f2' 228 | } else { 229 | snippetContainerNode.style.background = 'none' 230 | } 231 | 232 | } else if (e.data.type === 'update') { 233 | document.execCommand('paste') 234 | } else if (e.data.type === 'restore') { 235 | snippetNode.innerHTML = e.data.innerHTML 236 | updateEnvironment(e.data.bgColor) 237 | } else if (e.data.type === 'restoreBgColor') { 238 | updateEnvironment(e.data.bgColor) 239 | } else if (e.data.type === 'updateSettings') { 240 | snippetNode.style.boxShadow = e.data.shadow 241 | target = e.data.target 242 | transparentBackground = e.data.transparentBackground 243 | snippetContainerNode.style.backgroundColor = e.data.backgroundColor 244 | backgroundColor = e.data.backgroundColor 245 | if (e.data.ligature) { 246 | snippetNode.style.fontVariantLigatures = 'normal' 247 | } else { 248 | snippetNode.style.fontVariantLigatures = 'none' 249 | } 250 | } 251 | } 252 | }) 253 | })() 254 | 255 | function getRgba(hex, transparentBackground) { 256 | const bigint = parseInt(hex.slice(1), 16); 257 | const r = (bigint >> 16) & 255; 258 | const g = (bigint >> 8) & 255; 259 | const b = bigint & 255; 260 | const a = transparentBackground ? 0 : 1 261 | return `rgba(${r}, ${g}, ${b}, ${a})` 262 | } -------------------------------------------------------------------------------- /webview/vivus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vivus - JavaScript library to make drawing animation on SVG 3 | * @version v0.4.2 4 | * @link https://github.com/maxwellito/vivus 5 | * @license MIT 6 | */ 7 | "use strict";!function(){function t(t){if("undefined"==typeof t)throw new Error('Pathformer [constructor]: "element" parameter is required');if(t.constructor===String&&(t=document.getElementById(t),!t))throw new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID');if(!(t instanceof window.SVGElement||t instanceof window.SVGGElement||/^svg$/i.test(t.nodeName)))throw new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement');this.el=t,this.scan(t)}function e(t,e,n){r(),this.isReady=!1,this.setElement(t,e),this.setOptions(e),this.setCallback(n),this.isReady&&this.init()}t.prototype.TYPES=["line","ellipse","circle","polygon","polyline","rect"],t.prototype.ATTR_WATCH=["cx","cy","points","r","rx","ry","x","x1","x2","y","y1","y2"],t.prototype.scan=function(t){for(var e,r,n,i,a=t.querySelectorAll(this.TYPES.join(",")),o=0;oo?s:o,0),i/2),s=Math.min(Math.max(0>s?o:s,0),a/2),e.d="M "+(r+o)+","+n+" L "+(r+i-o)+","+n+" A "+o+","+s+",0,0,1,"+(r+i)+","+(n+s)+" L "+(r+i)+","+(n+a-s)+" A "+o+","+s+",0,0,1,"+(r+i-o)+","+(n+a)+" L "+(r+o)+","+(n+a)+" A "+o+","+s+",0,0,1,"+r+","+(n+a-s)+" L "+r+","+(n+s)+" A "+o+","+s+",0,0,1,"+(r+o)+","+n}else e.d="M"+r+" "+n+" L"+(r+i)+" "+n+" L"+(r+i)+" "+(n+a)+" L"+r+" "+(n+a)+" Z";return e},t.prototype.polylineToPath=function(t){var e,r,n={},i=t.points.trim().split(" ");if(-1===t.points.indexOf(",")){var a=[];for(e=0;e=this.duration)throw new Error("Vivus [constructor]: delay must be shorter than duration")},e.prototype.setCallback=function(t){if(t&&t.constructor!==Function)throw new Error('Vivus [constructor]: "callback" parameter must be a function');this.callback=t||function(){}},e.prototype.mapping=function(){var t,e,r,n,i,o,s,h;for(h=o=s=0,e=this.el.querySelectorAll("path"),t=0;t1?e.length-1:1),this.reverseStack&&this.map.reverse(),t=0;t=this.frameLength))return this.trace(),this.handle=n(function(){t.drawer()}),void 0;this.stop(),this.currentFrame=this.frameLength,this.trace(),this.selfDestroy&&this.destroy()}this.callback(this),this.instanceCallback&&(this.instanceCallback(this),this.instanceCallback=null)},e.prototype.trace=function(){var t,e,r,n;for(n=this.animTimingFunction(this.currentFrame/this.frameLength)*this.frameLength,t=0;t=o+a*e&&s>=r},e.prototype.getViewportH=function(){var t=this.docElem.clientHeight,e=window.innerHeight;return e>t?e:t},e.prototype.scrollY=function(){return window.pageYOffset||this.docElem.scrollTop},r=function(){e.prototype.docElem||(e.prototype.docElem=window.document.documentElement,n=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),i=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t)}}())},a=function(t,e){var r=parseInt(t,10);return r>=0?r:e},"function"==typeof define&&define.amd?define([],function(){return e}):"object"==typeof exports?module.exports=e:window.Vivus=e}(); -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^11.12.0": 6 | version "11.12.0" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.0.tgz#ec5594728811dc2797e42396cfcdf786f2052c12" 8 | integrity sha512-Lg00egj78gM+4aE0Erw05cuDbvX9sLJbaaPwwRtdCdAMnIudqrQZ0oZX98Ek0yiSK/A2nubHgJfvII/rTT2Dwg== 9 | 10 | "@types/vscode@^1.32.0": 11 | version "1.32.0" 12 | resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.32.0.tgz#e0a57de5fc8690a8a3a473996a6b6dfbccc28fbd" 13 | integrity sha512-fpHR6iE38V3+2ezMwopt726uRYKK9c89OQDO+t8VEUXNJCaYvnMFbHYgxXvQ/jOvP2ZanlL6r6joRZlsUowzoA== 14 | --------------------------------------------------------------------------------