├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── icon.png ├── package-lock.json ├── package.json ├── src ├── extension.ts └── test │ ├── runTest.ts │ └── suite │ ├── extension.test.ts │ └── index.ts ├── tsconfig.json ├── tslint.json ├── vsc-extension-quickstart.md ├── webpack.config.js └── webview ├── assets ├── css │ ├── colorPicker.css │ └── main.css ├── images │ └── icon-label.png └── js │ ├── code.js │ ├── colorPicker.js │ ├── main.js │ ├── snapshot.js │ └── utils.js └── index.html /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "project": "tsconfig.json", 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "extends": ["prettier", "prettier/@typescript-eslint"], 14 | "rules": { 15 | "@typescript-eslint/class-name-casing": "warn", 16 | "@typescript-eslint/member-delimiter-style": [ 17 | "warn", 18 | { 19 | "multiline": { 20 | "delimiter": "semi", 21 | "requireLast": true 22 | }, 23 | "singleline": { 24 | "delimiter": "semi", 25 | "requireLast": false 26 | } 27 | } 28 | ], 29 | "@typescript-eslint/semi": ["warn", "always"], 30 | "curly": "warn", 31 | "eqeqeq": ["warn", "always"], 32 | "no-redeclare": "warn", 33 | "no-throw-literal": "warn", 34 | "no-unused-expressions": "warn" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | src/extension.js 3 | src/extension.js.map 4 | node_modules 5 | .vscode-test/ 6 | *.vsix 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 100, 4 | "useTabs": false, 5 | "tabWidth": 4 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/src/**/*.js"], 15 | "preLaunchTask": "npm: webpack" 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 25 | ], 26 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 27 | "preLaunchTask": "${defaultBuildTask}" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "out": false 4 | }, 5 | "search.exclude": { 6 | "out": true 7 | }, 8 | "typescript.tsc.autoDetect": "off", 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll.tslint": true 11 | }, 12 | "editor.formatOnSave": true, 13 | "editor.formatOnType": false, 14 | "files.eol": "\n" 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/test/** 5 | src/extension.ts 6 | .gitignore 7 | vsc-extension-quickstart.md 8 | **/tsconfig.json 9 | **/tslint.json 10 | **/*.map 11 | **/*.ts 12 | tsconfig.json 13 | webpack.config.js 14 | 15 | node_modules 16 | !node_modules/dom-to-image/dist 17 | !node_modules/file-saver/dist -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.2.1] 2 | 3 | - Fixed header opcions visualization 4 | 5 | ## [0.2.0] 6 | 7 | - Show editor line numbers 8 | 9 | ## [0.1.0] 10 | 11 | - Initial release 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Snapshot 2 | 3 | `Code Snapshot` is a VS Code extension that tries to make your life easier. When you are coding, you may want to take a photo of your work to share it in your social media or presentations. `Code Snapshot` is your tool! Open the extension, select the code that you want to share and take beautiful screenshots of your code. 4 | 5 | ## Features 6 | - Save screenshots of your code 7 | 8 | ## Usage 9 | 1. Open the command palette (Ctrl+Shift+P on Windows and Linux, Cmd+Shift+P on OS X) and search for `Code Snapshot`. 10 | 2. Select the code you'd like to screenshot. 11 | 3. Adjust the width of the screenshot if desired. 12 | 4. Click the shutter button to save the screenshot. 13 | 14 | **Tips**: 15 | - You can also start `Code Snapshot` by selecting the code, click on the right button, and selecting `Code Snapshot` 16 | 17 | ## Contributing 18 | The source for this extension is available on [github](https://github.com/robert-z/code-snapshot). Contributions are extremely welcome! If anyone feels that there is something missing or would like to suggest improvements please [open a new issue](https://github.com/robert-z/code-snapshot/issues) or send a pull request! 19 | 20 | ## Credits 21 | - [Anatolii Saienko (@tsayen)](https://github.com/tsayen) for his work on [DOM to image](https://github.com/tsayen/dom-to-image), a library which can turn arbitrary DOM node into a vector (SVG) or raster (PNG or JPEG) image, written in JavaScrip 22 | 23 | - [Eli Grey (@eligrey)](https://github.com/eligrey) for [FileSaver](https://github.com/eligrey/FileSaver.js), a solution to saving files on the client-side 24 | 25 | - [Artem (@komarovartem)](https://github.com/komarovartem) for [colorPicker](https://github.com/komarovartem/colorPicker), a simple JavaScript colorpicker which supports transparency and gradient. 26 | 27 | ## License 28 | [MIT](https://opensource.org/licenses/MIT) 29 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robert-z/code-snapshot/df54e06cfc9981f7c991d884d2287a5f12fc00b5/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-snapshot", 3 | "displayName": "Code Snapshot", 4 | "description": "Create your custom code snippet", 5 | "version": "0.2.1", 6 | "publisher": "robertz", 7 | "engines": { 8 | "vscode": "^1.41.0" 9 | }, 10 | "icon": "icon.png", 11 | "categories": [ 12 | "Other" 13 | ], 14 | "keywords": [ 15 | "snapshot", 16 | "snippet", 17 | "code", 18 | "screenshot", 19 | "javascript", 20 | "js", 21 | "jsx", 22 | "flow", 23 | "typescript", 24 | "ts", 25 | "json", 26 | "css", 27 | "less", 28 | "scss", 29 | "styled-components", 30 | "styled-jsx", 31 | "markdown", 32 | "md", 33 | "commonmark", 34 | "mdx", 35 | "php", 36 | "pug", 37 | "ruby", 38 | "swift", 39 | "html", 40 | "vue", 41 | "angular", 42 | "graphql", 43 | "yaml", 44 | "yml" 45 | ], 46 | "homepage": "https://marketplace.visualstudio.com/items?itemName=robertz.code-snapshot", 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/robert-z/code-snapshot" 50 | }, 51 | "bugs": { 52 | "url": "https://github.com/robert-z/code-snapshot/issues" 53 | }, 54 | "activationEvents": [ 55 | "onCommand:codesnapshot.init" 56 | ], 57 | "main": "./src/extension", 58 | "contributes": { 59 | "commands": [ 60 | { 61 | "command": "codesnapshot.init", 62 | "title": "Code Snapshot 📸" 63 | } 64 | ], 65 | "menus": { 66 | "editor/context": [ 67 | { 68 | "command": "codesnapshot.init" 69 | } 70 | ] 71 | } 72 | }, 73 | "scripts": { 74 | "test": "node ./out/test/runTests.js", 75 | "vscode:package": "vsce package", 76 | "vscode:publish": "vsce publish", 77 | "vscode:prepublish": "webpack --mode production", 78 | "watch": "tsc --watch -p ./", 79 | "webpack:watch": "webpack --mode development --watch", 80 | "webpack": "webpack --mode development" 81 | }, 82 | "devDependencies": { 83 | "@types/glob": "^7.1.1", 84 | "@types/mocha": "^5.2.7", 85 | "@types/node": "^12.11.7", 86 | "@types/vscode": "^1.41.0", 87 | "@typescript-eslint/eslint-plugin": "^2.18.0", 88 | "@typescript-eslint/parser": "^2.18.0", 89 | "copy-webpack-plugin": "^5.1.1", 90 | "eslint": "^6.8.0", 91 | "eslint-config-prettier": "^6.10.0", 92 | "glob": "^7.1.5", 93 | "mocha": "^6.2.2", 94 | "prettier": "^1.19.1", 95 | "ts-loader": "^6.2.1", 96 | "typescript": "^3.6.4", 97 | "vscode-test": "^1.2.2", 98 | "webpack": "^4.41.5", 99 | "webpack-cli": "^3.3.10" 100 | }, 101 | "dependencies": { 102 | "dom-to-image": "^2.6.0", 103 | "file-saver": "^2.0.2" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | 5 | const VIEW_TYPE = 'codeSnapshot'; 6 | const WEB_VIEW_TITLE = 'Code Snapshot'; 7 | 8 | const init = (context: vscode.ExtensionContext) => { 9 | const activeTextEditor = vscode.window.activeTextEditor; 10 | 11 | const panel = createPanel(context); 12 | 13 | const selectionHandler = vscode.window.onDidChangeTextEditorSelection(e => { 14 | if (hasTextSelected(e.textEditor.selection)) { 15 | update(panel); 16 | } 17 | }); 18 | 19 | panel.onDidDispose(() => selectionHandler.dispose()); 20 | 21 | if (hasTextSelected(activeTextEditor?.selection)) { 22 | update(panel); 23 | } 24 | }; 25 | 26 | const createPanel = (context: vscode.ExtensionContext): vscode.WebviewPanel => { 27 | const htmlTemplatePath = path.resolve(context.extensionPath, 'webview/index.html'); 28 | const iconPath = path.resolve(context.extensionPath, 'webview/assets/images/icon-label.png'); 29 | const panel = vscode.window.createWebviewPanel( 30 | VIEW_TYPE, 31 | WEB_VIEW_TITLE, 32 | vscode.ViewColumn.Two, 33 | { 34 | enableScripts: true, 35 | localResourceRoots: [vscode.Uri.file(context.extensionPath)] 36 | } 37 | ); 38 | 39 | panel.iconPath = vscode.Uri.file(iconPath); 40 | 41 | panel.webview.html = getTemplate(htmlTemplatePath, panel); 42 | 43 | return panel; 44 | }; 45 | 46 | const getTemplate = (htmlTemplatePath: string, panel: vscode.WebviewPanel): string => { 47 | const htmlContent = fs.readFileSync(htmlTemplatePath, 'utf-8'); 48 | return htmlContent 49 | .replace(/%CSP_SOURCE%/gu, panel.webview.cspSource) 50 | .replace(/(src|href)="([^"]*)"/gu, (_, match, src) => { 51 | let assetsPath = panel.webview.asWebviewUri( 52 | vscode.Uri.file(path.resolve(htmlTemplatePath, '..', src)) 53 | ); 54 | return `${match}="${assetsPath}"`; 55 | }); 56 | }; 57 | 58 | const update = (panel: vscode.WebviewPanel): void => { 59 | vscode.commands.executeCommand('editor.action.clipboardCopyAction'); 60 | 61 | panel.webview.postMessage({ 62 | type: 'updateCode' 63 | }); 64 | }; 65 | 66 | const hasTextSelected = (selection: vscode.Selection | undefined): Boolean => 67 | !!selection && !selection.isEmpty; 68 | 69 | export const activate = (context: vscode.ExtensionContext) => { 70 | return context.subscriptions.push( 71 | vscode.commands.registerCommand('codesnapshot.init', () => init(context)) 72 | ); 73 | }; 74 | 75 | export const deactivate = () => {}; 76 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.equal(-1, [1, 2, 3].indexOf(5)); 13 | assert.equal(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | }); 10 | mocha.useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } 16 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: 'node', 10 | entry: './src/extension.ts', 11 | output: { 12 | path: path.resolve(__dirname, 'src'), 13 | filename: 'extension.js', 14 | libraryTarget: 'commonjs2', 15 | devtoolModuleFilenameTemplate: '../[resource-path]' 16 | }, 17 | devtool: 'source-map', 18 | externals: { 19 | vscode: 'commonjs vscode' 20 | }, 21 | resolve: { 22 | extensions: ['.ts', '.js'] 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.ts$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { 31 | loader: 'ts-loader' 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | }; 38 | module.exports = config; 39 | -------------------------------------------------------------------------------- /webview/assets/css/colorPicker.css: -------------------------------------------------------------------------------- 1 | .clpi-wrapper { 2 | display: flex; 3 | align-items: center; 4 | position: relative; 5 | z-index: 2; 6 | } 7 | 8 | .clpi-thumb { 9 | width: 30px; 10 | height: 30px; 11 | flex-shrink: 0; 12 | margin-left: 10px; 13 | border: 1px solid #ced4da; 14 | border-radius: 3px; 15 | position: relative; 16 | cursor: pointer; 17 | } 18 | 19 | #clpi-m { 20 | position: fixed; 21 | z-index: 999999; 22 | width: 100%; 23 | height: 100%; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | bottom: 0; 28 | display: none; 29 | } 30 | #clpi-m.clpi-m-show { 31 | display: block; 32 | } 33 | 34 | #clpi-p { 35 | width: 308px; 36 | padding: 10px; 37 | box-shadow: 0 15px 35px rgba(50, 50, 93, 0.06), 0 5px 15px rgba(0, 0, 0, 0.07); 38 | border: 1px solid #ecefff; 39 | border-radius: 5px; 40 | position: absolute; 41 | background: #fff; 42 | box-sizing: border-box; 43 | } 44 | 45 | #clpi-w { 46 | display: flex; 47 | align-items: flex-start; 48 | position: relative; 49 | width: 214px; 50 | } 51 | 52 | #clpi-a { 53 | width: 200px; 54 | height: 200px; 55 | margin-right: 14px; 56 | background: red; 57 | position: relative; 58 | user-select: none; 59 | } 60 | 61 | #clpi-o { 62 | width: 14px; 63 | height: 14px; 64 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 1px 1px 3px 1px rgba(0, 0, 0, 0.15); 65 | background: rgba(255, 255, 255, 0.5); 66 | box-sizing: border-box; 67 | margin-top: -7px; 68 | margin-left: -7px; 69 | border: 2px solid #fff; 70 | border-radius: 100%; 71 | position: absolute; 72 | top: 200px; 73 | left: 0; 74 | pointer-events: none; 75 | z-index: 4; 76 | } 77 | 78 | .clpi-b { 79 | display: flex; 80 | justify-content: space-between; 81 | margin-top: 15px; 82 | } 83 | 84 | #clpi-a:after, 85 | #clpi-a:before, 86 | #clpi-e-alpha:after, 87 | #clpi-e-alpha:before, 88 | #clpi-l:before, 89 | .clpi-thumb:before, 90 | .clpi-thumb:after { 91 | content: ''; 92 | position: absolute; 93 | top: 0; 94 | left: 0; 95 | width: 100%; 96 | height: 100%; 97 | z-index: 2; 98 | pointer-events: none; 99 | } 100 | 101 | #clpi-a:after { 102 | background: linear-gradient(to top, black, rgba(0, 0, 0, 0)); 103 | } 104 | 105 | #clpi-a:before { 106 | background: linear-gradient(to right, white, rgba(255, 255, 255, 0)); 107 | } 108 | 109 | .clpi-r { 110 | transform: rotate(-90deg); 111 | display: flex; 112 | flex-wrap: wrap; 113 | width: 200px; 114 | height: 75px; 115 | transform-origin: top right; 116 | position: absolute; 117 | right: 0; 118 | top: 1px; 119 | } 120 | .clpi-r input { 121 | position: relative; 122 | width: 200px; 123 | height: 30px; 124 | margin: 0; 125 | z-index: 3; 126 | opacity: 0; 127 | cursor: pointer; 128 | } 129 | 130 | .clpi-e { 131 | height: 30px; 132 | position: relative; 133 | border: 1px solid #ecefff; 134 | } 135 | 136 | .clpi-c { 137 | position: absolute; 138 | top: -2px; 139 | left: 100%; 140 | height: 34px; 141 | width: 8px; 142 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); 143 | margin-left: -4px; 144 | border: 2px solid #fff; 145 | box-sizing: border-box; 146 | pointer-events: none; 147 | } 148 | 149 | .clpi-e-hsl { 150 | background: linear-gradient(to left, #FF0000 0%, #FF00FF 13%, #8000FF 25%, #0040FF 38%, #00FFFF 50%, #00FF40 63%, #0BED00 70%, #FFFF00 80%, #FF0000 100%); 151 | margin-bottom: 9px; 152 | } 153 | 154 | #clpi-e-alpha { 155 | background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, #000 100%); 156 | } 157 | 158 | #clpi-e-alpha:before, 159 | #clpi-l:before, 160 | .clpi-thumb:before { 161 | background-image: linear-gradient(45deg, #eee 25%, transparent 25%), linear-gradient(-45deg, #eee 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #eee 75%), linear-gradient(-45deg, transparent 75%, #eee 75%); 162 | background-size: 20px 20px; 163 | background-position: 0 0, 0 10px, 10px -10px, -10px 0px; 164 | z-index: -1; 165 | } 166 | 167 | .clpi-thumb:after { 168 | background: #fff; 169 | z-index: -2; 170 | } 171 | 172 | .clpi-u { 173 | width: 30px; 174 | height: 30px; 175 | background: linear-gradient(118deg, #21d4fd 0%, #b721ff 100%); 176 | cursor: pointer; 177 | border-radius: 90px; 178 | display: none; 179 | } 180 | 181 | .clpi-l-support .clpi-u { 182 | display: block; 183 | } 184 | 185 | .clpi-b-hex { 186 | position: relative; 187 | } 188 | .clpi-b-hex input { 189 | max-height: 100%; 190 | height: 30px; 191 | padding: 0 10px; 192 | box-sizing: border-box; 193 | -webkit-appearance: none; 194 | border: 1px solid #ddd; 195 | width: 150px; 196 | box-shadow: none; 197 | font-size: 13px; 198 | font-family: Arial; 199 | } 200 | 201 | .clpi-x { 202 | margin-top: 20px; 203 | display: none; 204 | } 205 | 206 | .clpi-l-active .clpi-x { 207 | display: flex; 208 | align-items: flex-end; 209 | } 210 | 211 | .clpi-z { 212 | width: 200px; 213 | } 214 | 215 | .clpi-v { 216 | display: flex; 217 | justify-content: space-between; 218 | } 219 | 220 | .clpi-o { 221 | background: #ddd; 222 | width: 14px; 223 | height: 14px; 224 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 1px 1px 3px 1px rgba(0, 0, 0, 0.15); 225 | box-sizing: border-box; 226 | border: 2px solid #fff; 227 | position: relative; 228 | margin-bottom: 10px; 229 | cursor: pointer; 230 | } 231 | 232 | .clpi-o-active { 233 | box-shadow: 0 0 0 2px #0cf, 1px 1px 3px 1px rgba(0, 0, 0, 0.3); 234 | } 235 | 236 | #clpi-l { 237 | position: relative; 238 | height: 30px; 239 | width: 100%; 240 | background: linear-gradient(to right, #000 0%, #fff 100%); 241 | border: 1px solid #eee; 242 | } 243 | 244 | .clpi-l-angle { 245 | margin-left: 14px; 246 | } 247 | .clpi-l-angle input { 248 | width: 32px; 249 | height: 32px; 250 | margin: 0; 251 | -webkit-appearance: none; 252 | border: 1px solid #ddd; 253 | border-radius: 90px; 254 | text-align: center; 255 | box-sizing: border-box; 256 | color: #999; 257 | font-size: 13px !important; 258 | font-family: Arial !important; 259 | padding: 0 !important; 260 | -webkit-appearance: none; 261 | -moz-appearance: textfield; 262 | appearance: none; 263 | } 264 | 265 | .clpi-l-angle input[type=number]::-webkit-inner-spin-button, 266 | .clpi-l-angle input[type=number]::-webkit-outer-spin-button { 267 | -webkit-appearance: none; 268 | margin: 0; 269 | } -------------------------------------------------------------------------------- /webview/assets/css/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | body { 7 | display: flex; 8 | flex-flow: column; 9 | align-items: center; 10 | padding: 20px; 11 | margin: 0; 12 | } 13 | .header { 14 | width: calc(100% - 4rem); 15 | margin: 20px 0; 16 | display: flex; 17 | flex-direction: row; 18 | justify-content: space-between; 19 | vertical-align: bottom; 20 | } 21 | .header__options { 22 | display: flex; 23 | flex-direction: row; 24 | } 25 | .header__size { 26 | display: flex; 27 | align-items: flex-end; 28 | } 29 | .header__background { 30 | display: flex; 31 | flex-direction: row-reverse; 32 | align-items: center; 33 | } 34 | .header__checkbox { 35 | display: flex; 36 | align-items: center; 37 | padding: 0 10px; 38 | margin-left: 25px; 39 | } 40 | .snapshot-container { 41 | width: calc(100% - 4rem); 42 | overflow: hidden; 43 | resize: horizontal; 44 | border-radius: 5px; 45 | background-position: 0px 0px, 10px 10px; 46 | background-size: 20px 20px; 47 | background-image: linear-gradient( 48 | 45deg, 49 | #eee 25%, 50 | transparent 25%, 51 | transparent 75%, 52 | #eee 75%, 53 | #eee 100% 54 | ), 55 | linear-gradient(45deg, #eee 25%, white 25%, white 75%, #eee 75%, #eee 100%); 56 | } 57 | .snapshot-container__background { 58 | width: 100%; 59 | height: 100%; 60 | display: flex; 61 | justify-content: center; 62 | flex-direction: column; 63 | align-items: center; 64 | background: #cfdef3; 65 | } 66 | .terminal { 67 | background: var(--vscode-editor-background); 68 | overflow: hidden; 69 | resize: horizontal; 70 | display: flex; 71 | flex-direction: column; 72 | padding: 18px; 73 | margin-top: 50px; 74 | margin-bottom: 50px; 75 | border-radius: 5px; 76 | box-shadow: rgba(0, 0, 0, 0.55) 0 20px 68px; 77 | width: calc(100% - 8rem); 78 | min-width: 54px; 79 | min-height: 14px; 80 | } 81 | .terminal__header { 82 | margin-bottom: 15px; 83 | } 84 | .terminal__code-snippet > div { 85 | display: flex; 86 | flex-flow: column nowrap; 87 | justify-content: center; 88 | max-width: 100%; 89 | white-space: pre-wrap !important; 90 | word-break: break-all; 91 | } 92 | .terminal__code-snippet > div > div.editorLine { 93 | width: calc(100% - var(--editor-line-padding-left)); 94 | padding-left: var(--editor-line-padding-left); 95 | min-height: var(--editor-line-min-height); 96 | display: flex; 97 | flex-wrap: wrap; 98 | position: relative; 99 | } 100 | .terminal__code-snippet > div > div.editorLine > .editorLineNumber { 101 | color: var(--vscode-editorLineNumber-foreground); 102 | width: var(--editor-line-number-width); 103 | font-family: var(--vscode-editor-font-family); 104 | text-align: right; 105 | white-space: nowrap; 106 | flex: none; 107 | position: absolute; 108 | top: 0; 109 | bottom: 0; 110 | left: 0; 111 | } 112 | .editorLineNumber__test { 113 | font-family: var(--vscode-editor-font-family) !important; 114 | font-size: calc(var(--vscode-editor-font-size) * 1px) !important; 115 | display: inline-block; 116 | } 117 | .shoot { 118 | width: 40px; 119 | height: 40px; 120 | padding: 0; 121 | cursor: pointer; 122 | margin: 20px 0; 123 | transition: all 800ms ease-in-out; 124 | } 125 | .shoot:hover { 126 | -webkit-transform: rotate(360deg); 127 | transform: rotate(360deg); 128 | } 129 | .shoot svg { 130 | display: flex; 131 | align-self: center; 132 | } 133 | .shoot:hover svg #border { 134 | fill: #000; 135 | } 136 | /*Checkboxes styles*/ 137 | input[type='checkbox'] { 138 | display: none; 139 | } 140 | 141 | input[type='checkbox'] + label { 142 | display: block; 143 | position: relative; 144 | padding-left: 35px; 145 | margin-bottom: 20px; 146 | font: 14px/20px 'Open Sans', Arial, sans-serif; 147 | color: #ddd; 148 | cursor: pointer; 149 | -webkit-user-select: none; 150 | -moz-user-select: none; 151 | -ms-user-select: none; 152 | } 153 | 154 | input[type='checkbox'] + label:last-child { 155 | margin-bottom: 0; 156 | } 157 | 158 | input[type='checkbox'] + label:before { 159 | content: ''; 160 | display: block; 161 | width: 20px; 162 | height: 20px; 163 | border: 1px solid #6cc0e5; 164 | position: absolute; 165 | left: 0; 166 | top: -1px; 167 | opacity: 0.6; 168 | -webkit-transition: all 0.12s, border-color 0.08s; 169 | transition: all 0.12s, border-color 0.08s; 170 | } 171 | 172 | input[type='checkbox']:checked + label:before { 173 | width: 10px; 174 | top: -5px; 175 | left: 5px; 176 | border-radius: 0; 177 | opacity: 1; 178 | border-top-color: transparent; 179 | border-left-color: transparent; 180 | -webkit-transform: rotate(45deg); 181 | transform: rotate(45deg); 182 | } 183 | 184 | .clpi { 185 | display: none; 186 | } 187 | .clpi-thumb { 188 | width: 20px; 189 | height: 20px; 190 | margin-left: 0px; 191 | border-radius: 0px; 192 | margin-right: 15px; 193 | } 194 | -------------------------------------------------------------------------------- /webview/assets/images/icon-label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robert-z/code-snapshot/df54e06cfc9981f7c991d884d2287a5f12fc00b5/webview/assets/images/icon-label.png -------------------------------------------------------------------------------- /webview/assets/js/code.js: -------------------------------------------------------------------------------- 1 | import { setProperty } from './utils.js'; 2 | 3 | const terminalCodeSnippetNode = document.querySelector('.terminal__code-snippet'); 4 | let lineNumberWidth; 5 | let minHeight; 6 | const PADDING_LEFT_EXTRA_PX = 15; 7 | 8 | const getHtml = clip => clip.getData('text/html'); 9 | 10 | const setupTerminal = node => { 11 | node.innerHTML = replaceBrByDiv(node.innerHTML); 12 | 13 | const lines = node.querySelectorAll('div > div'); 14 | 15 | lines.forEach((row, index) => { 16 | row.classList.add('editorLine'); 17 | const lineNumber = document.createElement('div'); 18 | lineNumber.classList.add('editorLineNumber'); 19 | lineNumber.textContent = index + 1; 20 | row.insertBefore(lineNumber, row.firstChild); 21 | }); 22 | 23 | lineNumberWidth = computeEdeditorLineNumberWidth(lines.length); 24 | minHeight = computeMinLineHeight(node); 25 | 26 | setProperty('editor-line-number-width', lineNumberWidth + 'px'); 27 | setProperty('editor-line-min-height', minHeight + 'px'); 28 | setProperty('editor-line-padding-left', lineNumberWidth + PADDING_LEFT_EXTRA_PX + 'px'); 29 | }; 30 | 31 | const computeMinLineHeight = node => { 32 | const elementStyle = window.getComputedStyle(node.querySelector('div')); 33 | return parseInt(elementStyle.getPropertyValue('line-height')); 34 | }; 35 | 36 | const computeEdeditorLineNumberWidth = text => { 37 | const div = document.body.appendChild(document.createElement('div')); 38 | div.classList.add('editorLineNumber__test'); 39 | div.textContent = text; 40 | const width = div.clientWidth; 41 | div.remove(); 42 | return width; 43 | }; 44 | 45 | const replaceBrByDiv = str => { 46 | return str.replace(/
/gi, '
'); 47 | }; 48 | 49 | export const pasteCode = clip => { 50 | const code = getHtml(clip); 51 | terminalCodeSnippetNode.innerHTML = code; 52 | setupTerminal(terminalCodeSnippetNode); 53 | }; 54 | 55 | export const showLineNumbers = () => { 56 | const editorLineNumbers = document.querySelectorAll('.editorLineNumber'); 57 | editorLineNumbers.forEach(element => (element.style.display = 'block')); 58 | setProperty('editor-line-padding-left', lineNumberWidth + PADDING_LEFT_EXTRA_PX + 'px'); 59 | }; 60 | 61 | export const hideLineNumbers = () => { 62 | const editorLineNumbers = document.querySelectorAll('.editorLineNumber'); 63 | editorLineNumbers.forEach(element => (element.style.display = 'none')); 64 | setProperty('editor-line-padding-left', '0px'); 65 | }; 66 | -------------------------------------------------------------------------------- /webview/assets/js/colorPicker.js: -------------------------------------------------------------------------------- 1 | document.body.insertAdjacentHTML('afterbegin', '
'); 2 | 3 | var colorPicker = { 4 | input: null, 5 | thumb: null, 6 | value: '', 7 | h: 0, 8 | s: 0, 9 | l: 0, 10 | alpha: 1, 11 | 12 | gradientActive: false, 13 | gradientControl: null, 14 | gradientAngle: 0, 15 | gradientColor: document.getElementById('clpi-l'), 16 | gradientAngleInut: document.getElementById('clpi-l-angle'), 17 | 18 | mask: document.getElementById('clpi-m'), 19 | picker: document.getElementById('clpi-p'), 20 | 21 | currentColor: document.getElementById('clpi-c'), 22 | 23 | area: document.getElementById('clpi-a'), 24 | areaPosition: 0, 25 | 26 | pointer: document.getElementById('clpi-o'), 27 | pointerMove: false, 28 | 29 | hslInput: document.getElementById('clpi-i-hsl'), 30 | hslRangeControl: document.getElementById('clpi-c-hsl'), 31 | alphaRange: document.getElementById('clpi-e-alpha'), 32 | alphaInput: document.getElementById('clpi-i-alpha'), 33 | alphaRangeControl: document.getElementById('clpi-c-alpha'), 34 | 35 | cssClassControlActive: 'clpi-o-active', 36 | 37 | init: function (node) { 38 | let parent = node.closest('.clpi-wrapper'), 39 | input = parent.querySelector('input'), 40 | thumb = parent.querySelector('div'); 41 | 42 | this.input = input; 43 | this.thumb = thumb; 44 | 45 | if (input.matches('.clpi-gradient')) { 46 | this.picker.classList.add('clpi-l-support'); 47 | } 48 | 49 | this.mask.classList.add('clpi-m-show'); 50 | 51 | let pos = input.getBoundingClientRect(), 52 | height = this.picker.getBoundingClientRect().height; 53 | 54 | let setToBottom = pos.top + pos.height + height > window.innerHeight; 55 | 56 | if (setToBottom) { 57 | setToBottom = pos.top - height - 5; 58 | } else { 59 | setToBottom = pos.top + pos.height + 5; 60 | } 61 | 62 | this.picker.style.top = setToBottom + 'px'; 63 | this.picker.style.left = pos.left + 'px'; 64 | 65 | let val = input.value; 66 | 67 | if (val.length < 3) return; 68 | 69 | if (val.includes('gradient')) { 70 | this.initGradient(val); 71 | } else if (val.includes('rgb')) { 72 | this.initRgb(val); 73 | } else { 74 | this.initHex(val); 75 | } 76 | }, 77 | 78 | initGradient: function (val) { 79 | this.gradientActive = true; 80 | this.picker.classList.add('clpi-l-active'); 81 | 82 | val = val.replace('linear-gradient(', '').slice(0, -1); 83 | 84 | let angle = val.split(','); 85 | angle = angle[0].replace(/[^\d,]/g, ''); 86 | 87 | if (angle) { 88 | this.gradientAngleInut.value = parseInt(angle); 89 | this.gradientAngle = parseInt(angle); 90 | } 91 | 92 | let controls = val.match(/\((.*?)\)/g); 93 | 94 | // in case custom gradient input 95 | if (!controls) return; 96 | 97 | controls.forEach(function (v, k) { 98 | 99 | let rgb = v.replace(/[^\d,]/g, '').split(',').map(function (v) { 100 | return parseFloat(v) 101 | }); 102 | 103 | if (rgb.length > 3) { 104 | rgb[3] = rgb[3] / 100; 105 | } 106 | 107 | document.querySelectorAll('.clpi-o')[k].style.background = rgb.length > 3 ? 'rgba(' + rgb.join(',') + ')' : 'rgb(' + rgb.join(',') + ')'; 108 | 109 | controls[k] = rgb; 110 | }); 111 | 112 | this.setGradientControl(document.querySelector('.clpi-o')); 113 | }, 114 | 115 | initRgb: function (val) { 116 | let rgb = val.replace(/[^\d,]/g, '').split(',').map(function (v) { 117 | return parseFloat(v) 118 | }); 119 | 120 | if (rgb.length > 3) { 121 | rgb[3] = rgb[3] / 100; 122 | } 123 | 124 | this.update(rgb); 125 | }, 126 | 127 | initHex: function (val) { 128 | this.setCurrent(val); 129 | }, 130 | 131 | change: function () { 132 | let n = this.hslRgb(this.h, this.s, this.l), 133 | hex = this.rgbHex(n); 134 | 135 | this.currentColor.value = hex; 136 | this.area.style.background = 'hsl(' + parseInt(this.h * 360) + ', 100%, 50%)'; 137 | this.alphaRange.style.background = 'linear-gradient(to right, rgba(' + n.join(',') + ', 0) 0%, rgb(' + n.join(',') + ') 100%)'; 138 | 139 | if (this.alpha == 1) { 140 | this.value = hex; 141 | } else { 142 | this.value = 'rgba(' + n.join(',') + ',' + this.alpha + ' )'; 143 | } 144 | 145 | if (this.gradientActive) { 146 | this.gradientControl.style.background = this.value; 147 | 148 | let grColors = []; 149 | 150 | for (var item of document.querySelectorAll('.clpi-o')) { 151 | let bg = item.style.background; 152 | bg = bg ? bg : '#000'; 153 | 154 | // firefox fix 155 | bg = bg.replace(' none repeat scroll 0% 0%', ''); 156 | 157 | grColors.push(bg); 158 | } 159 | 160 | this.gradientColor.style.background = 'linear-gradient(to right, ' + grColors[0] + ' 0%, ' + grColors[1] + ' 100%)'; 161 | 162 | let angle = this.gradientAngle ? this.gradientAngle + 'deg' : 'to bottom'; 163 | 164 | this.value = 'linear-gradient(' + angle + ', ' + grColors[0] + ' 0%, ' + grColors[1] + ' 100%)'; 165 | 166 | } 167 | 168 | this.thumb.style.background = this.value; 169 | 170 | this.input.value = this.value; 171 | 172 | let that = this; 173 | 174 | window.dispatchEvent(new CustomEvent('colorPickerTick', { 175 | detail: { 176 | el: that.input, 177 | } 178 | })); 179 | }, 180 | 181 | triggerChange: function() { 182 | let that = this; 183 | 184 | window.dispatchEvent(new CustomEvent('colorPickerChange', { 185 | detail: { 186 | el: that.input, 187 | } 188 | })); 189 | }, 190 | 191 | update: function (rgb) { 192 | let hsl = this.rgbHsl(rgb[0], rgb[1], rgb[2]); 193 | 194 | this.hslInput.value = hsl[0]; 195 | 196 | this.setHslRange(hsl[0]); 197 | 198 | if (rgb.length > 3) { 199 | this.alphaInput.value = rgb[3]; 200 | this.alpha = rgb[3]; 201 | this.setAlphaRange(rgb[3]); 202 | } else { 203 | this.alphaInput.value = 1; 204 | this.alpha = 1; 205 | this.setAlphaRange(1); 206 | } 207 | 208 | this.h = hsl[0]; 209 | this.s = hsl[1]; 210 | this.l = hsl[2]; 211 | 212 | hsl[2] = hsl[2] * 200 - 200; 213 | hsl[2] = hsl[2] < 0 ? hsl[2] * -1 : hsl[2]; 214 | 215 | this.pointer.style.top = hsl[2] + 'px'; 216 | this.pointer.style.left = hsl[1] * 200 + 'px'; 217 | 218 | this.change(); 219 | }, 220 | 221 | close: function () { 222 | for (var item of document.querySelectorAll('.clpi-o')) { 223 | item.classList.remove(this.cssClassControlActive); 224 | } 225 | 226 | this.mask.classList.remove('clpi-m-show'); 227 | this.picker.classList.remove('clpi-l-active'); 228 | this.picker.classList.remove('clpi-l-support'); 229 | 230 | this.reset(); 231 | }, 232 | 233 | reset: function () { 234 | this.h = 0; 235 | this.s = 0; 236 | this.l = 0; 237 | this.alpha = 1; 238 | this.value = ''; 239 | 240 | this.gradientActive = false; 241 | this.gradientAngle = 0; 242 | 243 | this.pointer.removeAttribute('style'); 244 | this.alphaRangeControl.removeAttribute('style'); 245 | this.hslRangeControl.removeAttribute('style'); 246 | this.currentColor.value = ''; 247 | this.gradientAngleInut.value = ''; 248 | this.hslInput.value = 1; 249 | this.alphaInput.value = 1; 250 | }, 251 | 252 | setHsl: function (v) { 253 | this.h = v; 254 | this.setHslRange(v); 255 | this.change(); 256 | }, 257 | 258 | setHslRange: function (v) { 259 | this.hslRangeControl.style.left = v * 100 + '%'; 260 | }, 261 | 262 | setAlpha: function (v) { 263 | this.alpha = v; 264 | this.setAlphaRange(v); 265 | this.change(); 266 | }, 267 | 268 | setAlphaRange: function (v) { 269 | this.alphaRangeControl.style.left = v * 100 + '%'; 270 | }, 271 | 272 | setCurrent: function (hex) { 273 | if (hex.length < 6) return; 274 | 275 | let rgb = this.hexRgb(hex); 276 | 277 | if (!rgb) return; 278 | 279 | this.update(rgb); 280 | 281 | this.triggerChange(); 282 | }, 283 | 284 | setGradientControl: function (e) { 285 | this.gradientControl = e; 286 | 287 | for (var item of document.querySelectorAll('.clpi-o')) { 288 | item.classList.remove(this.cssClassControlActive); 289 | } 290 | 291 | this.gradientControl.classList.add(this.cssClassControlActive); 292 | 293 | 294 | let bg = this.gradientControl.style.background; 295 | 296 | if (bg) { 297 | // firefox fix 298 | bg = bg.replace(' none repeat scroll 0% 0%', ''); 299 | let rgb = bg.replace(/[^\d,]/g, '').split(',').map(function (v) { 300 | return parseFloat(v) 301 | }); 302 | 303 | if (typeof rgb[3] !== 'undefined') { 304 | rgb[3] = rgb[3] / 100; 305 | } 306 | 307 | this.update(rgb); 308 | } 309 | }, 310 | 311 | setGradientAngle: function (v) { 312 | this.gradientAngle = v; 313 | this.change(); 314 | this.triggerChange(); 315 | }, 316 | 317 | gradientShowToggle: function () { 318 | for (var item of document.querySelectorAll('.clpi-o')) { 319 | item.classList.remove(this.cssClassControlActive); 320 | } 321 | 322 | this.picker.classList.toggle('clpi-l-active'); 323 | this.gradientActive = !this.gradientActive; 324 | 325 | if (this.gradientActive) { 326 | this.gradientControl = document.querySelector('.clpi-o'); 327 | this.gradientControl.classList.add(this.cssClassControlActive); 328 | } 329 | 330 | this.change(); 331 | this.triggerChange(); 332 | }, 333 | 334 | startDrag: function (e) { 335 | this.pointerMove = true; 336 | this.areaPosition = this.area.getBoundingClientRect(); 337 | this.drag(e); 338 | }, 339 | 340 | drag: function (e) { 341 | if (this.pointerMove) { 342 | let y = e.clientY - this.areaPosition.top, 343 | x = e.clientX - this.areaPosition.left; 344 | 345 | y = y < 0 ? 0 : y; 346 | y = y > 200 ? 200 : y; 347 | 348 | x = x < 0 ? 0 : x; 349 | x = x > 200 ? 200 : x; 350 | 351 | this.pointer.style.top = y + 'px'; 352 | this.pointer.style.left = x + 'px'; 353 | 354 | x = x / 200; 355 | y = y / 200 - 1; 356 | 357 | x = -x > 0 ? -x : x; 358 | y = -y > 0 ? -y : y; 359 | 360 | this.s = parseFloat(x.toFixed(2)); 361 | this.l = parseFloat(y.toFixed(2)); 362 | 363 | this.change(); 364 | } 365 | }, 366 | 367 | stopDrag: function (e) { 368 | if (this.pointerMove === true) { 369 | this.triggerChange(); 370 | } 371 | this.pointerMove = false; 372 | }, 373 | 374 | hslRgb: function (h, s, v) { 375 | var r, g, b, i, f, p, q, t; 376 | 377 | i = Math.floor(h * 6); 378 | f = h * 6 - i; 379 | p = v * (1 - s); 380 | q = v * (1 - f * s); 381 | t = v * (1 - (1 - f) * s); 382 | 383 | switch (i % 6) { 384 | case 0: 385 | r = v, g = t, b = p; 386 | break; 387 | case 1: 388 | r = q, g = v, b = p; 389 | break; 390 | case 2: 391 | r = p, g = v, b = t; 392 | break; 393 | case 3: 394 | r = p, g = q, b = v; 395 | break; 396 | case 4: 397 | r = t, g = p, b = v; 398 | break; 399 | case 5: 400 | r = v, g = p, b = q; 401 | break; 402 | } 403 | 404 | return [ 405 | Math.round(r * 255), 406 | Math.round(g * 255), 407 | Math.round(b * 255) 408 | ]; 409 | }, 410 | 411 | rgbHsl: function (red, green, blue) { 412 | var rr, gg, bb, 413 | r = arguments[0] / 255, 414 | g = arguments[1] / 255, 415 | b = arguments[2] / 255, 416 | h, s, 417 | v = Math.max(r, g, b), 418 | diff = v - Math.min(r, g, b), 419 | diffc = function (c) { 420 | return (v - c) / 6 / diff + 1 / 2; 421 | }; 422 | 423 | if (diff == 0) { 424 | h = s = 0; 425 | } else { 426 | s = diff / v; 427 | rr = diffc(r); 428 | gg = diffc(g); 429 | bb = diffc(b); 430 | 431 | if (r === v) { 432 | h = bb - gg; 433 | } else if (g === v) { 434 | h = (1 / 3) + rr - bb; 435 | } else if (b === v) { 436 | h = (2 / 3) + gg - rr; 437 | } 438 | if (h < 0) { 439 | h += 1; 440 | } else if (h > 1) { 441 | h -= 1; 442 | } 443 | } 444 | 445 | return [parseFloat(h.toFixed(3)), parseFloat(s.toFixed(3)), parseFloat(v.toFixed(3))] 446 | }, 447 | 448 | rgbHex: function (rgb) { 449 | var hex = [rgb[0].toString(16), rgb[1].toString(16), rgb[2].toString(16)]; 450 | 451 | hex.forEach(function (v, k) { 452 | if (v.length === 1) { 453 | hex[k] = '0' + v; 454 | } 455 | }); 456 | 457 | return '#' + hex.join(''); 458 | }, 459 | 460 | hexRgb: function (hex) { 461 | let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 462 | hex = hex.replace(shorthandRegex, function (m, r, g, b) { 463 | return r + r + g + g + b + b; 464 | }); 465 | 466 | let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 467 | 468 | return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null; 469 | }, 470 | 471 | 472 | wrap: function (item) { 473 | let wrapper = document.createElement('div'), 474 | thumb = document.createElement('div'), 475 | value = item.value; 476 | wrapper.className = 'clpi-wrapper'; 477 | thumb.className = 'clpi-thumb'; 478 | 479 | if (value.length) { 480 | thumb.style.background = value; 481 | } 482 | 483 | item.parentNode.appendChild(wrapper); 484 | 485 | wrapper.appendChild(item); 486 | 487 | return wrapper.appendChild(thumb); 488 | }, 489 | 490 | initAll: function () { 491 | for (var item of document.querySelectorAll('.clpi:not(.clpi-initialized)')) { 492 | item.classList.add('clpi-initialized'); 493 | 494 | this.wrap(item); 495 | } 496 | }, 497 | 498 | setup: function () { 499 | let that = this; 500 | 501 | this.hslInput.addEventListener('input', function (e) { 502 | that.setHsl(e.target.value); 503 | }); 504 | 505 | this.hslInput.addEventListener('change', function (e) { 506 | that.triggerChange(); 507 | }); 508 | 509 | this.alphaRange.addEventListener('input', function (e) { 510 | that.setAlpha(e.target.value); 511 | }); 512 | 513 | this.alphaRange.addEventListener('change', function (e) { 514 | that.triggerChange(); 515 | }); 516 | 517 | this.currentColor.addEventListener('input', function (e) { 518 | that.setCurrent(e.target.value); 519 | }); 520 | 521 | this.gradientAngleInut.addEventListener('input', function (e) { 522 | that.setGradientAngle(e.target.value); 523 | }); 524 | 525 | this.area.addEventListener('mousedown', function (e) { 526 | that.startDrag(e); 527 | }); 528 | 529 | this.mask.addEventListener('mousedown', function (e) { 530 | if (e.target.matches('#clpi-m')) { 531 | that.close(); 532 | } 533 | }); 534 | 535 | document.addEventListener('mousemove', function (e) { 536 | that.drag(e); 537 | }); 538 | 539 | document.addEventListener('mouseup', function (e) { 540 | that.stopDrag(e); 541 | }); 542 | 543 | document.addEventListener('click', function (e) { 544 | if (e.target.matches('.clpi') || e.target.matches('.clpi-thumb')) { 545 | colorPicker.init(e.target); 546 | } 547 | 548 | if (e.target.matches('.clpi-o')) { 549 | colorPicker.setGradientControl(e.target); 550 | } 551 | 552 | if (e.target.matches('.clpi-u')) { 553 | colorPicker.gradientShowToggle(); 554 | } 555 | }); 556 | 557 | this.initAll(); 558 | }, 559 | 560 | }; 561 | 562 | colorPicker.setup(); -------------------------------------------------------------------------------- /webview/assets/js/main.js: -------------------------------------------------------------------------------- 1 | import { pasteCode, showLineNumbers, hideLineNumbers } from './code.js'; 2 | import { takeSnapshot } from './snapshot.js'; 3 | 4 | (() => { 5 | const snapshotContainerNode = document.querySelector('.snapshot-container'); 6 | const snapshotContainerBackgroundNode = document.querySelector( 7 | '.snapshot-container__background' 8 | ); 9 | const terminalNode = document.querySelector('.terminal'); 10 | const sizeNode = document.querySelector('.header__size'); 11 | const shootNode = document.querySelector('.shoot'); 12 | const showLineNumbersNode = document.querySelector('#show-line-numbers'); 13 | 14 | window.addEventListener('message', ({ data: { type } }) => { 15 | switch (type) { 16 | case 'updateCode': 17 | document.execCommand('paste'); 18 | break; 19 | } 20 | }); 21 | 22 | document.addEventListener('paste', event => { 23 | pasteCode(event.clipboardData); 24 | }); 25 | 26 | shootNode.addEventListener('click', event => { 27 | takeSnapshot(); 28 | }); 29 | 30 | showLineNumbersNode.addEventListener('change', event => { 31 | const checkbox = event.target; 32 | 33 | if (checkbox.checked) { 34 | showLineNumbers(); 35 | } else { 36 | hideLineNumbers(); 37 | } 38 | }); 39 | 40 | window.addEventListener('colorPickerChange', function(data) { 41 | const color = data.detail.el.value; 42 | snapshotContainerBackgroundNode.style.backgroundColor = color; 43 | }); 44 | 45 | colorPicker.initAll(); 46 | 47 | if (ResizeObserver) { 48 | const resizeObserver = new ResizeObserver(() => { 49 | let width = Math.round(snapshotContainerNode.offsetWidth) * 2; 50 | let height = Math.round(snapshotContainerNode.offsetHeight) * 2; 51 | 52 | sizeNode.textContent = width + 'x' + height; 53 | }); 54 | 55 | resizeObserver.observe(snapshotContainerNode); 56 | resizeObserver.observe(terminalNode); 57 | } 58 | })(); 59 | -------------------------------------------------------------------------------- /webview/assets/js/snapshot.js: -------------------------------------------------------------------------------- 1 | const snapshotContainerNode = document.querySelector('.snapshot-container'); 2 | const snapshotContainerBackgroundNode = document.querySelector('.snapshot-container__background'); 3 | const terminalNode = document.querySelector('.terminal'); 4 | 5 | export const takeSnapshot = () => { 6 | snapshotContainerNode.style.resize = 'none'; 7 | terminalNode.style.resize = 'none'; 8 | 9 | domtoimage 10 | .toBlob(snapshotContainerBackgroundNode, { 11 | width: snapshotContainerBackgroundNode.offsetWidth * 2, 12 | height: snapshotContainerBackgroundNode.offsetHeight * 2, 13 | style: { 14 | transform: 'scale(2)', 15 | 'transform-origin': 'center', 16 | background: '#e0eafc', 17 | background: 'linear-gradient(to left, #e0eafc, #cfdef3);' 18 | } 19 | }) 20 | .then(function(blob) { 21 | snapshotContainerNode.style.resize = ''; 22 | terminalNode.style.resize = ''; 23 | window.saveAs(blob, 'code-snapshot.png'); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /webview/assets/js/utils.js: -------------------------------------------------------------------------------- 1 | export const setProperty = (property, value) => { 2 | document.body.style.setProperty('--' + property, value); 3 | }; 4 | -------------------------------------------------------------------------------- /webview/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 14 | 15 |
16 | 17 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | --------------------------------------------------------------------------------