├── dist ├── scripts │ └── .gitkeep ├── styles │ └── .gitkeep ├── images │ ├── icon.png │ ├── icon-128.png │ ├── icon-16.png │ └── icon-48.png └── manifest.json ├── .gitignore ├── src ├── scripts │ ├── background.js │ ├── util.js │ └── main.js ├── data │ └── styles.json └── styles │ └── main.scss ├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── webpack.mix.js ├── LICENSE ├── package.json └── readme.md /dist/scripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/styles/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/codecopy/HEAD/dist/images/icon.png -------------------------------------------------------------------------------- /dist/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/codecopy/HEAD/dist/images/icon-128.png -------------------------------------------------------------------------------- /dist/images/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/codecopy/HEAD/dist/images/icon-16.png -------------------------------------------------------------------------------- /dist/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/codecopy/HEAD/dist/images/icon-48.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xpi 2 | *.zip 3 | node_modules 4 | .idea 5 | dist/scripts/* 6 | dist/styles/* 7 | !/**/.gitkeep -------------------------------------------------------------------------------- /src/scripts/background.js: -------------------------------------------------------------------------------- 1 | import 'webext-dynamic-content-scripts'; 2 | import addDomainPermissionToggle from 'webext-domain-permission-toggle'; 3 | 4 | addDomainPermissionToggle(); 5 | -------------------------------------------------------------------------------- /src/data/styles.json: -------------------------------------------------------------------------------- 1 | { 2 | "large": [ 3 | "github.com", 4 | "gist.github.com", 5 | "medium.com", 6 | "www.npmjs.com", 7 | "developer.mozilla.org", 8 | "tableless.com.br", 9 | "laracasts.com", 10 | "docs.rs", 11 | "www.digitalocean.com", 12 | "developer.github.com" 13 | ], 14 | "xlarge": [ 15 | "nodejs.org" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [package.json] 19 | indent_size = 2 -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [ 10.x, 12.x, 14.x ] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm install 24 | - run: npm start 25 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for your application, as well as bundling up your JS files. 11 | | 12 | */ 13 | 14 | mix.js('src/scripts/main.js', 'dist/scripts') 15 | .js('src/scripts/background.js', 'dist/scripts') 16 | .sass('src/styles/main.scss', 'dist/styles'); 17 | 18 | // Disable mix-manifest.json 19 | Mix.manifest.refresh = _ => void 0 20 | -------------------------------------------------------------------------------- /src/scripts/util.js: -------------------------------------------------------------------------------- 1 | import styles from '../data/styles'; 2 | 3 | export const htmlButton = ` 4 | `; 9 | 10 | export function getSiteStyle() { 11 | let currentStyle 12 | 13 | Object.keys(styles).forEach((style) => { 14 | if (styles[style].indexOf(document.location.hostname) !== -1) { 15 | currentStyle = style; 16 | } 17 | }); 18 | 19 | return currentStyle || 'small'; 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Zeno Rocha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | .codecopy { 2 | @import "@primer/css/tooltips/index.scss"; 3 | @import "@primer/css/buttons/index.scss"; 4 | 5 | position: relative; 6 | overflow: visible; 7 | 8 | .codecopy-btn { 9 | @extend .btn; 10 | @extend .btn-sm; 11 | 12 | box-shadow: none; 13 | min-height: initial; 14 | 15 | transition: opacity 0.3s ease-in-out; 16 | opacity: 0; 17 | 18 | position: absolute; 19 | z-index: 1; 20 | 21 | .codecopy-btn-icon { 22 | border-radius: 0; 23 | margin-top: -3px; 24 | position: relative; 25 | top: 3px; 26 | padding: 0; 27 | vertical-align: initial; 28 | min-height: initial; 29 | } 30 | 31 | &:hover, 32 | &:focus { 33 | box-shadow: none; 34 | } 35 | } 36 | 37 | &:hover .codecopy-btn { 38 | opacity: 1; 39 | } 40 | } 41 | 42 | .codecopy.codecopy-small .codecopy-btn { 43 | padding: 2px 6px; 44 | right: 0; 45 | top: 0; 46 | } 47 | 48 | .codecopy.codecopy-large .codecopy-btn { 49 | padding: 3px 6px; 50 | right: 5px; 51 | top: 5px; 52 | } 53 | 54 | .codecopy.codecopy-xlarge .codecopy-btn { 55 | padding: 3px 6px; 56 | right: 30px; 57 | top: 10px; 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codecopy", 3 | "version": "1.3.0", 4 | "description": "Because copy to clipboard buttons should exist on every code snippet", 5 | "main": "src/scripts/main.js", 6 | "repository": "zenorocha/codecopy", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@primer/css": "^15.2.0", 10 | "clipboard": "^2.0.6", 11 | "webext-domain-permission-toggle": "^1.0.1", 12 | "webext-dynamic-content-scripts": "^7.0.0" 13 | }, 14 | "devDependencies": { 15 | "cross-env": "^7.0.2", 16 | "laravel-mix": "^5.0.7", 17 | "resolve-url-loader": "^3.1.0", 18 | "sass": "^1.27.0", 19 | "sass-loader": "^8.0.2", 20 | "vue-template-compiler": "^2.6.12" 21 | }, 22 | "scripts": { 23 | "start": "npm run build", 24 | "build": "npm run prod", 25 | "dev": "npm run development", 26 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 27 | "watch": "npm run development -- --watch", 28 | "prod": "npm run production", 29 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 30 | "package": "npm run package:blink && npm run package:gecko", 31 | "package:blink": "cd dist && zip -r ../codecopy.zip * && cd ..", 32 | "package:gecko": "cd dist && zip -r ../codecopy.xpi * && cd .." 33 | } 34 | } -------------------------------------------------------------------------------- /src/scripts/main.js: -------------------------------------------------------------------------------- 1 | import ClipboardJS from 'clipboard'; 2 | import {htmlButton, getSiteStyle} from './util'; 3 | 4 | // Get button style based on the current page 5 | 6 | const siteStyle = getSiteStyle(); 7 | 8 | // Scan for code snippets and append buttons 9 | 10 | const snippets = document.querySelectorAll('pre'); 11 | 12 | snippets.forEach((snippet) => { 13 | const parent = snippet.parentNode; 14 | const wrapper = document.createElement('div'); 15 | 16 | parent.replaceChild(wrapper, snippet); 17 | wrapper.appendChild(snippet); 18 | 19 | wrapper.classList.add('codecopy', `codecopy-${siteStyle}`); 20 | wrapper.firstChild.insertAdjacentHTML('beforebegin', htmlButton); 21 | }); 22 | 23 | // Add copy to clipboard functionality and user feedback 24 | 25 | const clipboard = new ClipboardJS('.codecopy-btn', { 26 | target: (trigger) => { 27 | return trigger.parentNode; 28 | } 29 | }); 30 | 31 | clipboard.on('success', (e) => { 32 | e.trigger.setAttribute('aria-label', 'Copied!'); 33 | e.clearSelection(); 34 | }); 35 | 36 | // Replace tooltip message when mouse leaves button 37 | // and prevent page refresh after click button 38 | 39 | const btns = document.querySelectorAll('.codecopy-btn'); 40 | 41 | btns.forEach((btn) => { 42 | btn.addEventListener('mouseleave', (e) => { 43 | e.target.setAttribute('aria-label', 'Copy to clipboard'); 44 | e.target.blur(); 45 | }); 46 | 47 | btn.addEventListener('click', (e) => { 48 | e.preventDefault() 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "CodeCopy", 4 | "description": "Because copy to clipboard buttons should exist on every code snippet", 5 | "homepage_url": "https://github.com/zenorocha/codecopy", 6 | "version": "1.3.0", 7 | "author": "Zeno Rocha", 8 | "applications": { 9 | "gecko": { 10 | "id": "codecopy@clipboardjs.com", 11 | "strict_min_version": "45.0" 12 | } 13 | }, 14 | "icons": { 15 | "16": "images/icon-16.png", 16 | "48": "images/icon-48.png", 17 | "128": "images/icon-128.png" 18 | }, 19 | "browser_action": { 20 | "default_icon": { 21 | "16": "images/icon-16.png", 22 | "48": "images/icon-48.png", 23 | "128": "images/icon-128.png" 24 | } 25 | }, 26 | "permissions": [ 27 | "contextMenus", 28 | "activeTab" 29 | ], 30 | "optional_permissions": [ 31 | "http://*/*", 32 | "https://*/*" 33 | ], 34 | "background": { 35 | "scripts": [ 36 | "scripts/background.js" 37 | ] 38 | }, 39 | "content_scripts": [ 40 | { 41 | "css": [ 42 | "styles/main.css" 43 | ], 44 | "js": [ 45 | "scripts/main.js" 46 | ], 47 | "matches": [ 48 | "https://developer.mozilla.org/*", 49 | "https://medium.com/*", 50 | "https://www.npmjs.com/*", 51 | "https://github.com/*/*", 52 | "https://gist.github.com/*", 53 | "https://developer.github.com/*", 54 | "https://stackexchange.com/*", 55 | "https://*.stackexchange.com/*", 56 | "https://serverfault.com/*", 57 | "https://superuser.com/*", 58 | "https://askubuntu.com/*", 59 | "https://stackoverflow.com/*", 60 | "https://www.digitalocean.com/community/*", 61 | "https://nodejs.org/api/*", 62 | "https://css-tricks.com/*" 63 | ] 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # CodeCopy 2 | 3 | > Because copy to clipboard buttons should exist on every code snippet. 4 | 5 | ![Banner](https://cloud.githubusercontent.com/assets/398893/25569224/c3e7a724-2dc7-11e7-9ff4-7861c7876028.png) 6 | 7 | ## Install 8 | 9 | This browser extension available for: 10 | 11 | | Chrome logo | Firefox logo | 12 | |:---:|:---:| 13 | | [Chrome](https://chrome.google.com/webstore/detail/codecopy/fkbfebkcoelajmhanocgppanfoojcdmg) | [Firefox](https://addons.mozilla.org/en-US/firefox/addon/codecopy/) | 14 | 15 | ## Supported sites 16 | 17 | Works with: 18 | 19 | * [GitHub](https://github.com/) 20 | * [MDN](https://developer.mozilla.org/) 21 | * [Gist](https://gist.github.com/) 22 | * [StackOverflow](http://stackoverflow.com/) 23 | * [StackExchange](https://stackexchange.com/sites) 24 | * [npm](https://www.npmjs.com/) 25 | * [Medium](https://medium.com/) 26 | 27 | [And more](https://github.com/zenorocha/codecopy/blob/7c638611f7ad01d923361f7fedfe3933b35e114c/dist/manifest.json#L27). 28 | 29 | If you want to add a new site, feel free to send a pull request :) 30 | 31 | ### Custom domains 32 | 33 | You can also enable CodeCopy on custom domains or Github Enterprise. 34 | Right click on CodeCopy's icon in the the toolbar and select **Enable CodeCopy on this domain**. 35 | 36 | ## Preview 37 | 38 | ![Preview](https://cloud.githubusercontent.com/assets/398893/25569031/5840911a-2dc3-11e7-8445-a5b0867ceec7.png) 39 | 40 | ## Setup 41 | 42 | Install dependencies: 43 | 44 | ``` 45 | npm install 46 | ``` 47 | 48 | Compile scripts and styles: 49 | 50 | ``` 51 | npm start 52 | ``` 53 | 54 | ## Testing 55 | 56 | ###### Chrome 57 | 58 | 1. Navigate to `chrome://extensions` 59 | 60 | 2. Click on `Load unpacked extension...` 61 | 62 | 3. Select the `dist` folder 63 | 64 | ###### Firefox 65 | 66 | 1. Navigate to `about:debugging` 67 | 68 | 2. Click on `Load temporary Add-on` 69 | 70 | 3. Select the `manifest.json` inside the `dist` folder 71 | 72 | ###### Opera 73 | 74 | 1. Navigate to `extensions` 75 | 76 | 2. Click on `Developer Mode` 77 | 78 | 3. Click on `Load unpacked extension...` 79 | 80 | 4. Select the `dist` folder 81 | 82 | ## License 83 | 84 | [MIT License](http://zenorocha.mit-license.org/) © Zeno Rocha 85 | --------------------------------------------------------------------------------