├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .nvmrc ├── DEVELOPMENT_NOTES.md ├── LICENSE ├── README.md ├── design ├── appicon.ai ├── chrome-promo │ ├── browser-screenshot.png │ ├── chrome-promo.ai │ ├── extension-screenshot.png │ ├── flat-browser.ai │ └── font │ │ └── Nunito.zip ├── github-repo-thumbnail.ai ├── how-to.psd ├── icon-large.svg ├── icon-small.svg └── right-click.ai ├── gulpfile.babel.js ├── package-lock.json ├── package.json ├── resources ├── appicon.png └── chrome-promo │ ├── large.png │ ├── marquee.png │ ├── screenshot-1.png │ ├── screenshot-2.png │ └── small.png └── src ├── assets ├── css │ ├── code-copier │ │ └── _animation.scss │ ├── options.scss │ ├── popup │ │ ├── _color.scss │ │ ├── _common.scss │ │ ├── _reset.scss │ │ ├── _variables.scss │ │ ├── components │ │ │ ├── _content.scss │ │ │ ├── _header.scss │ │ │ ├── _historyBlock.scss │ │ │ ├── _howTo.scss │ │ │ ├── _messageBlock.scss │ │ │ ├── _settingsButton.scss │ │ │ ├── _simplebar.scss │ │ │ └── _tooltip.scss │ │ └── main.scss │ └── web.scss ├── images │ ├── how-to.png │ └── icons │ │ ├── delete.svg │ │ ├── external-link.svg │ │ ├── icon128.png │ │ ├── icon16.png │ │ ├── icon24.png │ │ ├── icon32.png │ │ ├── icon36.png │ │ ├── icon48.png │ │ ├── right-click.svg │ │ └── settings.svg └── js │ ├── content-script.js │ ├── lib │ └── code-copier.js │ ├── options.js │ ├── popup.js │ ├── popup │ ├── App.js │ ├── components │ │ ├── contentContainer.js │ │ ├── header.js │ │ ├── historyBlock.js │ │ ├── howTo.js │ │ ├── messageBlock.js │ │ └── settingsButton.js │ ├── contents │ │ └── index.js │ ├── pages │ │ └── landing.js │ └── services │ │ ├── history.js │ │ └── storage.js │ └── utils │ ├── format.js │ └── uuid.js ├── index.html ├── manifest.json └── options.html /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": 3 | { 4 | "browser": true, 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true, 8 | "webextensions": true, 9 | }, 10 | "extends": ["eslint:recommended", "airbnb-base"], 11 | "parserOptions": 12 | { 13 | "sourceType": "module", 14 | "ecmaVersion": 9 15 | }, 16 | "rules": 17 | { 18 | "indent": [ 19 | "error", 20 | 2, 21 | { 22 | "SwitchCase": 1 23 | } 24 | ], 25 | "linebreak-style": [ 26 | "error", 27 | "unix" 28 | ], 29 | "quotes": [ 30 | "error", 31 | "single" 32 | ], 33 | "semi": [ 34 | "error", 35 | "always" 36 | ], 37 | "no-console": [ 38 | "off" 39 | ], 40 | "comma-dangle": [ 41 | "error", 42 | "never" 43 | ], 44 | "func-names": [ 45 | "error", "as-needed" 46 | ], 47 | "prefer-arrow-callback": [ 48 | "off" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.pem 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.16.1 2 | -------------------------------------------------------------------------------- /DEVELOPMENT_NOTES.md: -------------------------------------------------------------------------------- 1 | # Code Copier # 2 | 3 | ### Development Note ### 4 | All JS files are packed using Gulp + Browserify + Babelify. To setup Vue which works without webpack, vue template compiler is used. `vue/dist/vue.esm.js` is imported instead of directly importing `vue`. 5 | 6 | ### Installation ### 7 | 8 | * `npm install` 9 | 10 | ### Dev Server ### 11 | 12 | * `npm run dev` 13 | 14 | ### Build Production ### 15 | 16 | All the build files can be found in `/dist` folder. 17 | 18 | * `npm run build` 19 | 20 | ### Debug ### 21 | * Chrome 22 | 1. Go to `chrome://extensions/` page 23 | 2. Enable development mode on upper right coner 24 | 3. Load unpack and select `/dist` folder 25 | 26 | * Firefox 27 | 1. Go to `about:debugging#/runtime/this-firefox` page 28 | 2. Choose `Load Temporary Add-on` 29 | 3. Select `manifest.json` inside `/dist` folder 30 | 31 | ### Pack Extension ### 32 | * Chrome: Zip all content inside `/dist` folder after build, upload the zip file to webstore 33 | * Firefox: TBC 34 | 35 | `npm run pack` has been set to simplify the process. 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ice Lam 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Copier # 2 | > Google Chrome and Mozilla Firefox extension to copy contents inside `` and `
` tags with right click.
 3 | 
 4 | ![Screen Preview](https://github.com/icelam/code-copier/raw/master/resources/chrome-promo/large.png)
 5 | 
 6 | ## Features ##
 7 | * Right click on `` and `
` tags to copy
 8 | * Up to 10 copy history
 9 | * Go back to the website where the code snippets from
10 | * Disable plugin on user defined host names
11 | 
12 | ![Screen Preview](https://github.com/icelam/code-copier/raw/master/resources/chrome-promo/screenshot-1.png)
13 | ![Screen Preview](https://github.com/icelam/code-copier/raw/master/resources/chrome-promo/screenshot-2.png)
14 | 
15 | ## Download Link ##
16 | * [Download Chrome version](https://chrome.google.com/webstore/detail/code-copier/polidkhcaghmmijeemenkiloblpdfphp)
17 | * [Download Firefox version](https://addons.mozilla.org/en-US/firefox/addon/code-copier/)
18 | 
19 | ## Development Notes ##
20 | * Setup: `npm install`
21 | * Development: `npm run dev`
22 | * Build: `npm run build`
23 | * Build and zip: `npm run pack`
24 | 


--------------------------------------------------------------------------------
/design/appicon.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/appicon.ai


--------------------------------------------------------------------------------
/design/chrome-promo/browser-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/chrome-promo/browser-screenshot.png


--------------------------------------------------------------------------------
/design/chrome-promo/chrome-promo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/chrome-promo/chrome-promo.ai


--------------------------------------------------------------------------------
/design/chrome-promo/extension-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/chrome-promo/extension-screenshot.png


--------------------------------------------------------------------------------
/design/chrome-promo/flat-browser.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/chrome-promo/flat-browser.ai


--------------------------------------------------------------------------------
/design/chrome-promo/font/Nunito.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/chrome-promo/font/Nunito.zip


--------------------------------------------------------------------------------
/design/github-repo-thumbnail.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/github-repo-thumbnail.ai


--------------------------------------------------------------------------------
/design/how-to.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/how-to.psd


--------------------------------------------------------------------------------
/design/icon-large.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 5 | 
11 | 
13 | 
15 | 
17 | 
18 | 	
20 | 	
22 | 	
24 | 	
26 | 	
28 | 	
30 | 	
32 | 	
34 | 	
36 | 	
38 | 	
40 | 	
42 | 	
44 | 	
46 | 	
48 | 	
50 | 	
52 | 
53 | 
54 | 


--------------------------------------------------------------------------------
/design/icon-small.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 5 | 
11 | 
13 | 
15 | 
17 | 
18 | 	
21 | 	
23 | 	
26 | 
27 | 
28 | 


--------------------------------------------------------------------------------
/design/right-click.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/design/right-click.ai


--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
  1 | import gulp from 'gulp';
  2 | import { merge } from 'event-stream';
  3 | import browserify from 'browserify';
  4 | import watchify from 'watchify';
  5 | import source from 'vinyl-source-stream';
  6 | import buffer from 'vinyl-buffer';
  7 | import gulpLoadPlugins from 'gulp-load-plugins';
  8 | 
  9 | const $ = gulpLoadPlugins();
 10 | 
 11 | const sourceFolder = 'src/assets/js/';
 12 | const distFolder = 'dist/assets/js';
 13 | const files = [
 14 |   'content-script.js',
 15 |   'popup.js',
 16 |   'options.js'
 17 | ];
 18 | 
 19 | const buildDev = () => {
 20 |   const tasks = files.map(
 21 |     (file) => {
 22 |       let bundler = browserify({
 23 |         entries: `${sourceFolder}${file}`,
 24 |         debug: true
 25 |       });
 26 | 
 27 |       const bundle = () => bundler
 28 |         .transform('babelify', {
 29 |           sourceMaps: false,
 30 |           presets: ['@babel/preset-env'],
 31 |           plugins: [
 32 |             ['@babel/transform-runtime'],
 33 |             ['transform-vue-template']
 34 |           ],
 35 |           global: true,
 36 |           ignore: [/[/\\]core-js/, /@babel[/\\]runtime/],
 37 |           compact: false
 38 |         })
 39 |         .bundle()
 40 |         .on('error', (error) => {
 41 |           console.error(`Error: ${error.toString()}`);
 42 |         })
 43 |         .pipe(source(file))
 44 |         .pipe(buffer())
 45 |         .pipe($.sourcemaps.init({ loadMaps: true }))
 46 |         .pipe($.sourcemaps.write('./'))
 47 |         .pipe(gulp.dest(distFolder));
 48 | 
 49 |       bundler = watchify(bundler);
 50 | 
 51 |       bundler.on('update', () => {
 52 |         bundle();
 53 |         console.log(`Building ${sourceFolder}${file}...`);
 54 |       });
 55 | 
 56 |       bundler.on('bytes', (bytes) => {
 57 |         console.log(`${sourceFolder}${file} build finish! (Size: ${bytes / 1024}kb)`);
 58 |       });
 59 | 
 60 |       return bundle();
 61 |     }
 62 |   );
 63 | 
 64 |   return merge(...tasks);
 65 | };
 66 | 
 67 | const buildProd = () => {
 68 |   const tasks = files.map(
 69 |     file => browserify({
 70 |       entries: `${sourceFolder}${file}`
 71 |     })
 72 |       .transform('babelify', {
 73 |         presets: ['@babel/preset-env'],
 74 |         plugins: [
 75 |           ['@babel/transform-runtime'],
 76 |           ['transform-vue-template']
 77 |         ],
 78 |         comments: false,
 79 |         global: true,
 80 |         ignore: [/[\/\\]core-js/, /@babel[\/\\]runtime/],
 81 |         compact: false
 82 |       })
 83 |       .bundle()
 84 |       .on('error', (error) => {
 85 |         console.error(`Error: ${error.toString()}`);
 86 |       })
 87 |       .pipe(source(file))
 88 |       .pipe(buffer())
 89 |       .pipe($.uglify({
 90 |         mangle: false,
 91 |         output: {
 92 |           ascii_only: true
 93 |         }
 94 |       }))
 95 |       .pipe(gulp.dest(distFolder))
 96 |   );
 97 | 
 98 |   return merge(...tasks);
 99 | };
100 | 
101 | gulp.task('dev-js', (done) => {
102 |   buildDev();
103 |   done();
104 |   console.log(`Watching ${sourceFolder}`);
105 | });
106 | 
107 | gulp.task('build-js', (done) => {
108 |   buildProd();
109 |   done();
110 | });
111 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "code-copier",
 3 |   "version": "1.0.2",
 4 |   "description": "Code Copier - Chrome Extension to copy contents inside 
 and  tags",
 5 |   "private": true,
 6 |   "author": "Ice Lam",
 7 |   "license": "ISC",
 8 |   "scripts": {
 9 |     "clean": "rimraf ./dist",
10 |     "dev:files": "cpx './src/**/*.{html,json}' ./dist --watch --verbose",
11 |     "dev:images": "cpx './src/assets/images/**/*' ./dist/assets/images --watch --verbose",
12 |     "dev:js": "./node_modules/.bin/gulp dev-js",
13 |     "dev:build-css": "node-sass ./src/assets/css --output ./dist/assets/css --include-path node_modules --source-map ./dist/assets/css",
14 |     "dev:watch-css": "node-sass ./src/assets/css --output ./dist/assets/css --include-path node_modules --watch --recursive --source-map ./dist/assets/css",
15 |     "dev:css": "npm run dev:build-css && npm run dev:watch-css",
16 |     "dev": "npm run clean && concurrently \"npm run dev:js\" \"npm run dev:css\" \"npm run dev:files\" \"npm run dev:images\"",
17 |     "build:files": "cpx './src/**/*.{html,json}' ./dist",
18 |     "build:images": "cpx './src/assets/images/**/*' ./dist/assets/images",
19 |     "build:js": "./node_modules/.bin/gulp build-js",
20 |     "build:css": "node-sass ./src/assets/css --output ./dist/assets/css --quiet --include-path node_modules --output-style compressed",
21 |     "build": "npm run clean && npm run build:js && npm run build:css && npm run build:files && npm run build:images",
22 |     "pack": "npm run build && cd ./dist && zip -r -FS ../app/code-copier.zip *"
23 |   },
24 |   "devDependencies": {
25 |     "@babel/cli": "^7.6.4",
26 |     "@babel/core": "^7.6.4",
27 |     "@babel/plugin-transform-runtime": "^7.6.2",
28 |     "@babel/preset-env": "^7.6.3",
29 |     "@babel/register": "^7.6.2",
30 |     "babel-plugin-transform-vue-template": "^0.4.2",
31 |     "babelify": "^10.0.0",
32 |     "browserify": "^16.5.0",
33 |     "concurrently": "^5.0.0",
34 |     "cpx": "^1.5.0",
35 |     "eslint": "^5.7.0",
36 |     "eslint-config-airbnb-base": "^13.1.0",
37 |     "eslint-import-resolver-webpack": "^0.11.1",
38 |     "eslint-plugin-import": "^2.16.0",
39 |     "event-stream": "^4.0.1",
40 |     "gulp": "^4.0.2",
41 |     "gulp-load-plugins": "^2.0.1",
42 |     "node-sass": "^4.13.1",
43 |     "rimraf": "^3.0.0",
44 |     "vinyl-buffer": "^1.0.1",
45 |     "vinyl-source-stream": "^2.0.0",
46 |     "vue-template-compiler": "^2.6.11",
47 |     "watchify": "^3.11.1"
48 |   },
49 |   "dependencies": {
50 |     "@babel/runtime": "^7.6.3",
51 |     "gulp-sourcemaps": "^2.6.5",
52 |     "gulp-uglify": "^3.0.2",
53 |     "simplebar-vue": "^1.3.4",
54 |     "vue": "^2.6.11",
55 |     "webextension-polyfill": "^0.5.0"
56 |   }
57 | }
58 | 


--------------------------------------------------------------------------------
/resources/appicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/resources/appicon.png


--------------------------------------------------------------------------------
/resources/chrome-promo/large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/resources/chrome-promo/large.png


--------------------------------------------------------------------------------
/resources/chrome-promo/marquee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/resources/chrome-promo/marquee.png


--------------------------------------------------------------------------------
/resources/chrome-promo/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/resources/chrome-promo/screenshot-1.png


--------------------------------------------------------------------------------
/resources/chrome-promo/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/resources/chrome-promo/screenshot-2.png


--------------------------------------------------------------------------------
/resources/chrome-promo/small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/resources/chrome-promo/small.png


--------------------------------------------------------------------------------
/src/assets/css/code-copier/_animation.scss:
--------------------------------------------------------------------------------
1 | .code-copier--blink {
2 |   animation: blinker 0.5s linear 1;
3 | }
4 | 
5 | @keyframes blinker {
6 |   50% { opacity: 0; }
7 | }
8 | 


--------------------------------------------------------------------------------
/src/assets/css/options.scss:
--------------------------------------------------------------------------------
1 | textarea {
2 |   width: 100%;
3 |   min-width: 100%;
4 |   margin: 1em 0;
5 | }
6 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/_color.scss:
--------------------------------------------------------------------------------
 1 | $color-white: #ffffff;
 2 | 
 3 | $color-grey-98: #fafafa;
 4 | $color-grey-94: #f0f0f0;
 5 | $color-grey-93: #ededed;
 6 | $color-grey-86: #dddddd;
 7 | $color-grey-60: #999999;
 8 | $color-grey-40: #666666;
 9 | $color-grey-20: #333333;
10 | 
11 | $color-light-blue-grey: #bebdc3;
12 | $color-mid-blue-grey: #92909a;
13 | $color-blue-grey: #5d5b68;
14 | 
15 | $color-blue: #3fb6e9;
16 | $color-mid-blue: #27aae1; // Primary
17 | $color-dark-blue: #2598d7;
18 | 
19 | $color-primary-yellow: #ffce07;
20 | $color-secondary-yellow: #ffb400;
21 | 
22 | // base
23 | $color-body: $color-grey-98;
24 | $color-body-text: $color-grey-40;
25 | $color-link: $color-blue;
26 | $color-link-hover: $color-mid-blue;
27 | $color-hr: $color-grey-93;
28 | 
29 | // header
30 | $color-header-background: $color-primary-yellow;
31 | $color-header-title: $color-dark-blue;
32 | $color-header-text: $color-grey-40;
33 | 
34 | // message block
35 | $color-message-text: $color-grey-60;
36 | 
37 | // how to
38 | $color-how-to-text: $color-grey-60;
39 | 
40 | // history block
41 | $color-history-info: $color-mid-blue-grey;
42 | $color-code-block-border: $color-grey-86;
43 | $color-code-block-background: $color-white;
44 | $color-code-block-text: $color-blue-grey;
45 | 
46 | // tooltip
47 | $color-tooltip-background: $color-grey-20;
48 | $color-tooltip-text: $color-white;
49 | 
50 | // simplebar
51 | $color-scrollbar: $color-grey-40;
52 | 
53 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/_common.scss:
--------------------------------------------------------------------------------
 1 | @import './color';
 2 | @import './variables';
 3 | 
 4 | body {
 5 |   width: $body-width;
 6 |   overflow-x: hidden; /* Firefox scrollbar hack */
 7 |   max-height: 435px;
 8 |   background-color: $color-body;
 9 |   color: $color-body-text;
10 | }
11 | 
12 | .container {
13 |   padding: 1.25em;
14 | }
15 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/_reset.scss:
--------------------------------------------------------------------------------
 1 | @import './color';
 2 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700&display=swap');
 3 | 
 4 | html,
 5 | body {
 6 |   margin: 0;
 7 |   padding: 0;
 8 |   font-family: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
 9 |   font-size: 16px;
10 | }
11 | 
12 | a {
13 |   text-decoration: none;
14 |   color: $color-link;
15 |   transition: all .2s ease-out;
16 | 
17 |   &:visited {
18 |     text-decoration: none;
19 |     color: $color-link;
20 |   }
21 | 
22 |   &:focus,
23 |   &:hover,
24 |   &:active {
25 |     text-decoration: none;
26 |     color: $color-link-hover;
27 |   }
28 | }
29 | 
30 | p {
31 |   margin: 1em 0;
32 |   line-height: 1.375em;
33 | }
34 | 
35 | ul,
36 | ol {
37 |   margin: 1em 0;
38 | }
39 | 
40 | img {
41 |   max-width: 100%;
42 | }
43 | 
44 | hr {
45 |   background-color: $color-hr;
46 |   border: 0;
47 |   clear: both;
48 |   color: transparent;
49 |   height: 1px;
50 |   margin: 1em 0;
51 |   padding: 0;
52 | }
53 | 
54 | h1 {
55 |   font-size: 1.75em;
56 |   line-height: 1.2em;
57 |   margin: 1.516em 0 0.75em 0;
58 | }
59 | 
60 | h2 {
61 |   font-size: 1.5em;
62 |   line-height: 1.3em;
63 |   margin: 1.7em 0 0.85em 0;
64 | }
65 | 
66 | h3 {
67 |   font-size: 1.375em;
68 |   line-height: 1.3em;
69 |   margin: 1.8em 0 0.9em 0;
70 | }
71 | 
72 | h4 {
73 |   font-size: 1.25em;
74 |   line-height: 1.4em;
75 |   margin: 2em 0 1em 0;
76 | }
77 | 
78 | h5 {
79 |   font-size: 1.125em;
80 |   line-height: 1.5em;
81 |   margin: 2.2em 0 1.1em 0;
82 | }
83 | 
84 | h6 {
85 |   font-size: 1em;
86 |   line-height: 1.5em;
87 |   margin: 2.5em 0 1.25em 0;
88 | }
89 | 
90 | h1, h2, h3, h4, h5, h6 {
91 |   &:first-child {
92 |     margin-top: 0;
93 |   }
94 | }
95 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/_variables.scss:
--------------------------------------------------------------------------------
1 | $body-width: 375px;
2 | 
3 | $shadow-1: 0 1px 3px rgba(0, 0, 0, 0.03), 0 1px 2px rgba(0, 0, 0, 0.06);
4 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_content.scss:
--------------------------------------------------------------------------------
1 | .content {
2 |   margin-top: 6.1875em;
3 | }
4 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_header.scss:
--------------------------------------------------------------------------------
 1 | @import '../color';
 2 | @import '../variables';
 3 | 
 4 | .header {
 5 |   background-color: $color-header-background;
 6 |   color: $color-header-text;
 7 |   box-sizing: border-box;
 8 |   width: $body-width;
 9 |   position: fixed;
10 |   left: 0;
11 |   top: 0;
12 |   z-index: 999999;
13 |   box-shadow: $shadow-1;
14 | 
15 |   &__title {
16 |     color: $color-header-title;
17 |     margin: 0 0 0.25em 0;
18 |     font-weight: bold;
19 |   }
20 | 
21 |   &__subtitle {
22 |     margin: 0;
23 |     font-size: 0.875em;
24 |   }
25 | }
26 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_historyBlock.scss:
--------------------------------------------------------------------------------
 1 | @import '../color';
 2 | @import '../variables';
 3 | 
 4 | @import './simplebar';
 5 | 
 6 | .history-block {
 7 |   &__title {
 8 |     margin: 0 0 0.5em 0;
 9 |     display: flex;
10 |     flex-direction: row;
11 |     align-items: center;
12 |   }
13 | 
14 |   &__info, &__actions {
15 |     font-size: 0.75em;
16 |     color: $color-history-info;
17 |     margin: 0;
18 |   }
19 | 
20 |   &__info {
21 |     flex: 1 1 auto;
22 |   }
23 | 
24 |   &__actions {
25 |     flex: 0 0 auto;
26 |     white-space: nowrap;
27 |     text-align: right;
28 | 
29 |     &__button {
30 |       cursor: pointer;
31 |       background-color: transparent;
32 |       outline: none;
33 |       border: 0;
34 |       -moz-box-shadow: none;
35 |       -webkit-box-shadow: none;
36 |       box-shadow: none;
37 |       -webkit-appearance:none;
38 |       appearance: none;
39 |       -webkit-border-radius: 0;
40 |       -moz-border-radius: 0;
41 |       border-radius: 0;
42 |       display: inline-block;
43 |       text-decoration: none;
44 |       margin: 0 0.25em;
45 |       padding: 0;
46 |       font-family: inherit;
47 |       font-size: inherit;
48 |       text-align: center;
49 | 
50 |       img {
51 |         display: block;
52 |         width: auto;
53 |         height: 1em;
54 |         filter: invert(63%) sepia(5%) saturate(434%) hue-rotate(212deg) brightness(91%) contrast(86%);
55 | 
56 |         &:hover {
57 |           filter: invert(38%) sepia(4%) saturate(1146%) hue-rotate(209deg) brightness(91%) contrast(90%);
58 |         }
59 |       }
60 |     }
61 |   }
62 | 
63 |   &__content {
64 |     color: $color-code-block-text;
65 |     background: $color-code-block-background;
66 |     box-shadow: $shadow-1;
67 |     border-radius: 0.25em;
68 | 
69 |     pre {
70 |       display: block;
71 |       padding: 1em;
72 |       margin: 0;
73 |       font-size: 0.875em;
74 |       font-family: Courier, monospace;
75 |     }
76 |   }
77 | 
78 |   & + & {
79 |     margin-top: 1.25em;
80 |   }
81 | }
82 | 
83 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_howTo.scss:
--------------------------------------------------------------------------------
 1 | @import '../color';
 2 | 
 3 | .how-to {
 4 |   color: $color-how-to-text;
 5 |   text-align: center;
 6 | 
 7 |   &__image {
 8 |     box-shadow: $shadow-1;
 9 |     border-radius: 0.25em;
10 | 
11 |     & img {
12 |       width: 100%;
13 |       border-radius: 0.25em;
14 |       display: block;
15 |     }
16 |   }
17 | 
18 |   p {
19 |     margin: 1em 0 0 0;
20 |   }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_messageBlock.scss:
--------------------------------------------------------------------------------
1 | @import '../color';
2 | 
3 | .message-block {
4 |   margin: 7.5em 0;
5 |   text-align: center;
6 |   color: $color-message-text;
7 | }
8 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_settingsButton.scss:
--------------------------------------------------------------------------------
 1 | .settings-button {
 2 |   position: fixed;
 3 |   right: 0.5rem;
 4 |   top: 0.5rem;
 5 |   z-index: 9999999;
 6 |   cursor: pointer;
 7 |   background-color: transparent;
 8 |   outline: none;
 9 |   border: 0;
10 |   -moz-box-shadow: none;
11 |   -webkit-box-shadow: none;
12 |   box-shadow: none;
13 |   -webkit-appearance:none;
14 |   appearance: none;
15 |   -webkit-border-radius: 0;
16 |   -moz-border-radius: 0;
17 |   border-radius: 0;
18 |   display: inline-block;
19 |   text-decoration: none;
20 |   margin: 0;
21 |   padding: 0;
22 |   font-family: inherit;
23 |   font-size: inherit;
24 |   text-align: center;
25 |   opacity: 0.8;
26 | 
27 |   img {
28 |     display: block;
29 |     width: auto;
30 |     height: 0.75em;
31 |     filter: invert(52%) sepia(84%) saturate(1654%) hue-rotate(21deg) brightness(93%) contrast(96%);
32 | 
33 |     &:hover {
34 |       filter: invert(36%) sepia(29%) saturate(1785%) hue-rotate(18deg) brightness(98%) contrast(97%);
35 |     }
36 |   }
37 | }
38 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_simplebar.scss:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Custom theme for node module Simplebar
  3 |  */
  4 | 
  5 | @import '../color';
  6 | 
  7 | [data-simplebar] {
  8 |   position: relative;
  9 |   flex-direction: column;
 10 |   flex-wrap: wrap;
 11 |   justify-content: flex-start;
 12 |   align-content: flex-start;
 13 |   align-items: flex-start
 14 | }
 15 | 
 16 | .simplebar-wrapper {
 17 |   overflow: hidden;
 18 |   width: inherit;
 19 |   height: inherit;
 20 |   max-width: inherit;
 21 |   max-height: inherit
 22 | }
 23 | 
 24 | .simplebar-mask {
 25 |   direction: inherit;
 26 |   position: absolute;
 27 |   overflow: hidden;
 28 |   padding: 0;
 29 |   margin: 0;
 30 |   left: 0;
 31 |   top: 0;
 32 |   bottom: 0;
 33 |   right: 0;
 34 |   width: auto !important;
 35 |   height: auto !important;
 36 |   z-index: 0
 37 | }
 38 | 
 39 | .simplebar-offset {
 40 |   direction: inherit !important;
 41 |   box-sizing: inherit !important;
 42 |   resize: none !important;
 43 |   position: absolute;
 44 |   top: 0;
 45 |   left: 0;
 46 |   bottom: 0;
 47 |   right: 0;
 48 |   padding: 0;
 49 |   margin: 0;
 50 |   -webkit-overflow-scrolling: touch
 51 | }
 52 | 
 53 | .simplebar-content-wrapper {
 54 |   direction: inherit;
 55 |   box-sizing: border-box !important;
 56 |   position: relative;
 57 |   display: block;
 58 |   height: 100%;
 59 |   width: auto;
 60 |   visibility: visible;
 61 |   max-width: 100%;
 62 |   max-height: 100%;
 63 |   scrollbar-width: none
 64 | }
 65 | 
 66 | .simplebar-content-wrapper::-webkit-scrollbar,
 67 | .simplebar-hide-scrollbar::-webkit-scrollbar {
 68 |   display: none
 69 | }
 70 | 
 71 | .simplebar-content:after,
 72 | .simplebar-content:before {
 73 |   content: ' ';
 74 |   display: table
 75 | }
 76 | 
 77 | .simplebar-placeholder {
 78 |   max-height: 100%;
 79 |   max-width: 100%;
 80 |   width: 100%;
 81 |   pointer-events: none
 82 | }
 83 | 
 84 | .simplebar-height-auto-observer-wrapper {
 85 |   box-sizing: inherit !important;
 86 |   height: 100%;
 87 |   width: 100%;
 88 |   max-width: 1px;
 89 |   position: relative;
 90 |   float: left;
 91 |   max-height: 1px;
 92 |   overflow: hidden;
 93 |   z-index: -1;
 94 |   padding: 0;
 95 |   margin: 0;
 96 |   pointer-events: none;
 97 |   flex-grow: inherit;
 98 |   flex-shrink: 0;
 99 |   flex-basis: 0
100 | }
101 | 
102 | .simplebar-height-auto-observer {
103 |   box-sizing: inherit;
104 |   display: block;
105 |   opacity: 0;
106 |   position: absolute;
107 |   top: 0;
108 |   left: 0;
109 |   height: 1000%;
110 |   width: 1000%;
111 |   min-height: 1px;
112 |   min-width: 1px;
113 |   overflow: hidden;
114 |   pointer-events: none;
115 |   z-index: -1
116 | }
117 | 
118 | .simplebar-track {
119 |   z-index: 1;
120 |   position: absolute;
121 |   right: 0;
122 |   bottom: 0;
123 |   pointer-events: none;
124 |   overflow: hidden
125 | }
126 | 
127 | [data-simplebar].simplebar-dragging .simplebar-content {
128 |   pointer-events: none;
129 |   user-select: none;
130 |   -webkit-user-select: none
131 | }
132 | 
133 | [data-simplebar].simplebar-dragging .simplebar-track {
134 |   pointer-events: all
135 | }
136 | 
137 | .simplebar-scrollbar {
138 |   position: absolute;
139 |   right: 2px;
140 |   width: 7px;
141 |   min-height: 10px
142 | }
143 | 
144 | .simplebar-scrollbar:before {
145 |   position: absolute;
146 |   content: '';
147 |   background: $color-scrollbar;
148 |   border-radius: 7px;
149 |   left: 0;
150 |   right: 0;
151 |   opacity: 0;
152 |   transition: opacity .2s linear
153 | }
154 | 
155 | .simplebar-scrollbar.simplebar-visible:before {
156 |   opacity: .5;
157 |   transition: opacity 0s linear
158 | }
159 | 
160 | .simplebar-track.simplebar-vertical {
161 |   top: 0;
162 |   width: 11px
163 | }
164 | 
165 | .simplebar-track.simplebar-vertical .simplebar-scrollbar:before {
166 |   top: 2px;
167 |   bottom: 2px
168 | }
169 | 
170 | .simplebar-track.simplebar-horizontal {
171 |   left: 0;
172 |   height: 11px
173 | }
174 | 
175 | .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before {
176 |   height: 100%;
177 |   left: 2px;
178 |   right: 2px
179 | }
180 | 
181 | .simplebar-track.simplebar-horizontal .simplebar-scrollbar {
182 |   right: auto;
183 |   left: 0;
184 |   top: 2px;
185 |   height: 7px;
186 |   min-height: 0;
187 |   min-width: 10px;
188 |   width: auto
189 | }
190 | 
191 | [data-simplebar-direction=rtl] .simplebar-track.simplebar-vertical {
192 |   right: auto;
193 |   left: 0
194 | }
195 | 
196 | .hs-dummy-scrollbar-size {
197 |   direction: rtl;
198 |   position: fixed;
199 |   opacity: 0;
200 |   visibility: hidden;
201 |   height: 500px;
202 |   width: 500px;
203 |   overflow-y: hidden;
204 |   overflow-x: scroll
205 | }
206 | 
207 | .simplebar-hide-scrollbar {
208 |   position: fixed;
209 |   left: 0;
210 |   visibility: hidden;
211 |   overflow-y: scroll;
212 |   scrollbar-width: none
213 | }
214 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/components/_tooltip.scss:
--------------------------------------------------------------------------------
 1 | @import '../color';
 2 | 
 3 | [data-tooltip] {
 4 |   position: relative;
 5 | 
 6 |   &:before,
 7 |   &:after {
 8 |     z-index: 999999;
 9 |     visibility: hidden;
10 |     opacity: 0;
11 |     pointer-events: none;
12 |     transition: all 0.3s cubic-bezier(.25,.8,.25,1);
13 |   }
14 | 
15 |   &:before {
16 |     position: absolute;
17 |     bottom: 150%;
18 |     left: 50%;
19 |     margin: 0 0 0.3125rem 0;
20 |     padding: 0.25rem 0.5rem;
21 |     transform: translateX(-50%);
22 |     white-space: nowrap;
23 |     border-radius: 0.25em;
24 |     background-color: rgba($color-tooltip-background, 0.9);
25 |     color: $color-tooltip-text;
26 |     content: attr(data-tooltip);
27 |     text-align: center;
28 |     font-size: 0.75rem;
29 |     line-height: 1.2;
30 |   }
31 | 
32 |   &:after {
33 |     position: absolute;
34 |     bottom: 150%;
35 |     left: 50%;
36 |     margin: 0 0 0 -0.3125rem;
37 |     width: 0;
38 |     border-top: 0.3125rem solid rgba($color-tooltip-background, 0.9);
39 |     border-right: 0.3125rem solid transparent;
40 |     border-left: 0.3125rem solid transparent;
41 |     content: " ";
42 |     font-size: 0;
43 |     line-height: 0;
44 |   }
45 | 
46 |   &:hover:before,
47 |   &:hover:after {
48 |     visibility: visible;
49 |     opacity: 1;
50 |   }
51 | }
52 | 


--------------------------------------------------------------------------------
/src/assets/css/popup/main.scss:
--------------------------------------------------------------------------------
 1 | @import './color';
 2 | @import './reset';
 3 | @import './common';
 4 | @import './components/header';
 5 | @import './components/content';
 6 | @import './components/tooltip';
 7 | @import './components/messageBlock';
 8 | @import './components/howTo';
 9 | @import './components/historyBlock';
10 | @import './components/settingsButton';
11 | 
12 | @import '../code-copier/animation';
13 | 


--------------------------------------------------------------------------------
/src/assets/css/web.scss:
--------------------------------------------------------------------------------
1 | @import './code-copier/animation';
2 | 


--------------------------------------------------------------------------------
/src/assets/images/how-to.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/src/assets/images/how-to.png


--------------------------------------------------------------------------------
/src/assets/images/icons/delete.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 5 | 
10 | 
11 | 


--------------------------------------------------------------------------------
/src/assets/images/icons/external-link.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 5 | 
 9 | 
10 | 


--------------------------------------------------------------------------------
/src/assets/images/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/src/assets/images/icons/icon128.png


--------------------------------------------------------------------------------
/src/assets/images/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/src/assets/images/icons/icon16.png


--------------------------------------------------------------------------------
/src/assets/images/icons/icon24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/src/assets/images/icons/icon24.png


--------------------------------------------------------------------------------
/src/assets/images/icons/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/src/assets/images/icons/icon32.png


--------------------------------------------------------------------------------
/src/assets/images/icons/icon36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/src/assets/images/icons/icon36.png


--------------------------------------------------------------------------------
/src/assets/images/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icelam/code-copier/38925967ca2048e798f00dd77dc6901e528a2505/src/assets/images/icons/icon48.png


--------------------------------------------------------------------------------
/src/assets/images/icons/right-click.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 5 | 
 9 | 
10 | 	
12 | 	
13 | 	
15 | 	
17 | 	
18 | 	
19 | 
20 | 
21 | 


--------------------------------------------------------------------------------
/src/assets/images/icons/settings.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 5 | 
15 | 
16 | 


--------------------------------------------------------------------------------
/src/assets/js/content-script.js:
--------------------------------------------------------------------------------
 1 | import browser from 'webextension-polyfill';
 2 | import codeCopier from './lib/code-copier';
 3 | 
 4 | import uuid from './utils/uuid';
 5 | 
 6 | const getDisabledSites = async () => {
 7 |   const { disabledSites } = await browser.storage.local.get('disabledSites');
 8 |   return disabledSites || [];
 9 | };
10 | 
11 | const saveToStorage = async (content) => {
12 |   const key = 'copierHistory';
13 |   const result = await browser.storage.local.get(key);
14 |   let newHistory = JSON.parse(JSON.stringify(result));
15 | 
16 |   if (!(typeof result === 'object'
17 |     && Object.prototype.hasOwnProperty.call(result, key)
18 |     && Array.isArray(result[key]))) {
19 |     newHistory = {};
20 |     newHistory[key] = [];
21 |   }
22 | 
23 |   if (newHistory[key].length >= 10) {
24 |     newHistory[key].pop();
25 |   }
26 | 
27 |   newHistory[key].unshift({
28 |     id: uuid(),
29 |     content,
30 |     from: window.location.href,
31 |     timestamp: Date.now()
32 |   });
33 | 
34 |   await browser.storage.local.set(newHistory);
35 | };
36 | 
37 | // Main Function - Right Click to copy code
38 | const initCopier = () => {
39 |   const preTags = Array.from(document.getElementsByTagName('pre'));
40 |   const codeTags = Array.from(document.getElementsByTagName('code'));
41 |   const codeSnippets = [...preTags, ...codeTags];
42 | 
43 |   const copier = codeCopier(codeSnippets, saveToStorage);
44 |   copier.init();
45 | };
46 | 
47 | // Observe Single Page Application - debounce for 1 second
48 | let debounceTimer;
49 | const target = document.getElementsByTagName('body')[0];
50 | const config = {
51 |   childList: true,
52 |   subtree: true
53 | };
54 | 
55 | const mutationCallback = () => {
56 |   clearTimeout(debounceTimer);
57 |   debounceTimer = setTimeout(() => {
58 |     console.log('DOM changed detected: Re-initialize code copier');
59 |     initCopier();
60 |   }, 1000);
61 | };
62 | 
63 | 
64 | // Init extension if hostname does not mathc disabled settings
65 | getDisabledSites().then((disabledSites) => {
66 |   const currentHostname = window.location.hostname.match(/^www\.(.*)$/);
67 |   const hostname = (currentHostname != null ? currentHostname[1] : window.location.hostname);
68 | 
69 |   if (!disabledSites.includes(hostname)) {
70 |     initCopier();
71 | 
72 |     const observer = new MutationObserver(mutationCallback);
73 |     observer.observe(target, config);
74 |   } else {
75 |     console.log('Code Copier has been disabled for current hostname');
76 |   }
77 | }).catch((err) => {
78 |   console.log(new Error(err));
79 | });
80 | 


--------------------------------------------------------------------------------
/src/assets/js/lib/code-copier.js:
--------------------------------------------------------------------------------
 1 | const codeCopier = function (elements, callback) {
 2 |   const initIdentifier = 'data-copier-init';
 3 |   const copiedCallback = callback;
 4 |   const codeSnippets = elements;
 5 | 
 6 |   const init = function () {
 7 |     const newCodeSnippets = codeSnippets.filter(c => !c.hasAttribute(initIdentifier));
 8 | 
 9 |     newCodeSnippets.forEach((snippet) => {
10 |       // Listen to non-primary click
11 |       snippet.addEventListener('auxclick', function codeClick(e) {
12 |         const el = this;
13 |         e.stopPropagation();
14 |         e.preventDefault();
15 | 
16 |         // Add element to range for select
17 |         const range = document.createRange();
18 |         range.selectNode(el);
19 | 
20 |         // Select text of current clicking element
21 |         window.getSelection().removeAllRanges();
22 |         window.getSelection().addRange(range);
23 |         document.execCommand('copy');
24 | 
25 |         // Run custom callback, e.g. save to storage
26 |         if (typeof copiedCallback === 'function') {
27 |           const copiedContent = window.getSelection().toString();
28 |           copiedCallback(copiedContent);
29 |         }
30 | 
31 |         // Copied indicator
32 |         el.classList.add('code-copier--blink');
33 |         setTimeout(() => {
34 |           el.classList.remove('code-copier--blink');
35 |         }, 1200);
36 | 
37 |         // Clear all selection
38 |         window.getSelection().removeAllRanges();
39 |       });
40 | 
41 |       // Prevent context menu on right click
42 |       snippet.addEventListener('contextmenu', function preventContentMenu(e) {
43 |         e.preventDefault();
44 |       });
45 | 
46 |       // Add attribute to prevent duplicate event binding
47 |       snippet.setAttribute(initIdentifier, 'true');
48 |     });
49 |   };
50 | 
51 |   return {
52 |     init
53 |   };
54 | };
55 | 
56 | export default codeCopier;
57 | 


--------------------------------------------------------------------------------
/src/assets/js/options.js:
--------------------------------------------------------------------------------
 1 | import browser from 'webextension-polyfill';
 2 | 
 3 | const settingsKey = 'disabledSites';
 4 | const settingsForm = document.querySelector('form');
 5 | const hostnameListInput = document.getElementById('hostnameList');
 6 | 
 7 | const saveOptions = async (e) => {
 8 |   e.preventDefault();
 9 | 
10 |   // Remove whitespace and line breaks
11 |   const hostnameListStr = hostnameListInput.value.replace(/(\r\n|\n|\r)/gm, ',').replace(/\s/g, '');
12 | 
13 |   // Remove empty
14 |   const hostnameList = hostnameListStr.split(',').filter(h => !!h);
15 | 
16 |   const newDisableSetting = {};
17 |   newDisableSetting[settingsKey] = hostnameList;
18 |   await browser.storage.local.set(newDisableSetting);
19 | 
20 |   window.close();
21 | };
22 | 
23 | const restoreOptions = async () => {
24 |   const { disabledSites } = await browser.storage.local.get(settingsKey);
25 | 
26 |   if (disabledSites && disabledSites.length) {
27 |     hostnameListInput.value = disabledSites.join(',');
28 |   }
29 | };
30 | 
31 | document.addEventListener('DOMContentLoaded', restoreOptions);
32 | settingsForm.addEventListener('submit', saveOptions);
33 | 


--------------------------------------------------------------------------------
/src/assets/js/popup.js:
--------------------------------------------------------------------------------
 1 | import Vue from 'vue/dist/vue.esm';
 2 | import App from './popup/App';
 3 | 
 4 | Vue.config.productionTip = false;
 5 | Vue.config.devtools = false;
 6 | 
 7 | new Vue({
 8 |   render: h => h(App)
 9 | }).$mount('#app');
10 | 


--------------------------------------------------------------------------------
/src/assets/js/popup/App.js:
--------------------------------------------------------------------------------
 1 | import popupHeader from './components/header';
 2 | import popupContentContainer from './components/contentContainer';
 3 | import landing from './pages/landing';
 4 | 
 5 | const app = {
 6 |   name: 'App',
 7 |   components: {
 8 |     popupHeader,
 9 |     popupContentContainer,
10 |     landing
11 |   },
12 |   template: `
13 |     
14 | 15 | 16 | 17 | 18 |
19 | ` 20 | }; 21 | 22 | export default app; 23 | -------------------------------------------------------------------------------- /src/assets/js/popup/components/contentContainer.js: -------------------------------------------------------------------------------- 1 | const contentContainer = { 2 | name: 'contentContainer', 3 | template: ` 4 |
5 | 6 |
7 | ` 8 | }; 9 | 10 | export default contentContainer; 11 | -------------------------------------------------------------------------------- /src/assets/js/popup/components/header.js: -------------------------------------------------------------------------------- 1 | const popupHeader = { 2 | name: 'popupHeader', 3 | template: ` 4 |
5 |

Code Copier

6 |

Select and copy code with just one finger click!

7 |
8 | ` 9 | }; 10 | 11 | export default popupHeader; 12 | -------------------------------------------------------------------------------- /src/assets/js/popup/components/historyBlock.js: -------------------------------------------------------------------------------- 1 | import simplebar from 'simplebar-vue'; 2 | import formatUtils from '../../utils/format'; 3 | import codeCopier from '../../lib/code-copier'; 4 | 5 | const historyBlock = { 6 | name: 'historyBlock', 7 | components: { 8 | simplebar 9 | }, 10 | props: { 11 | id: { 12 | type: String, 13 | required: true 14 | }, 15 | content: { 16 | type: String, 17 | required: true 18 | }, 19 | from: { 20 | type: String, 21 | required: true 22 | }, 23 | timestamp: { 24 | type: Number, 25 | required: true 26 | }, 27 | index: { 28 | type: Number, 29 | required: true 30 | }, 31 | deleteHandler: { 32 | type: Function, 33 | required: true 34 | } 35 | }, 36 | methods: { 37 | initCodeCopy(snippets) { 38 | const copier = codeCopier(snippets); 39 | copier.init(); 40 | }, 41 | deleteRecord() { 42 | this.deleteHandler(this.id); 43 | } 44 | }, 45 | mounted() { 46 | this.initCodeCopy([this.$refs.codeBlock]); 47 | }, 48 | computed: { 49 | formattedTime() { 50 | return formatUtils.formatDate(this.timestamp); 51 | } 52 | }, 53 | template: ` 54 |
55 |
56 |

History #{{index + 1}} - {{formattedTime}}

57 |
58 | 64 | 65 | 66 | 72 |
73 |
74 | 78 |
{{content}}
79 |
80 |
81 | ` 82 | }; 83 | 84 | export default historyBlock; 85 | -------------------------------------------------------------------------------- /src/assets/js/popup/components/howTo.js: -------------------------------------------------------------------------------- 1 | const howTo = { 2 | name: 'howTo', 3 | props: { 4 | image: { 5 | type: String, 6 | required: true 7 | }, 8 | message: { 9 | type: String, 10 | required: true 11 | } 12 | }, 13 | template: ` 14 |
15 |
16 | 17 |
18 |

19 |
20 | ` 21 | }; 22 | 23 | export default howTo; 24 | -------------------------------------------------------------------------------- /src/assets/js/popup/components/messageBlock.js: -------------------------------------------------------------------------------- 1 | const messageBlock = { 2 | name: 'messageBlock', 3 | props: { 4 | message: { 5 | type: String, 6 | required: true 7 | } 8 | }, 9 | template: ` 10 |

11 | {{message}} 12 |

13 | ` 14 | }; 15 | 16 | export default messageBlock; 17 | -------------------------------------------------------------------------------- /src/assets/js/popup/components/settingsButton.js: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill'; 2 | 3 | const settingButtons = { 4 | name: 'settingButtons', 5 | methods: { 6 | openSettings() { 7 | browser.runtime.openOptionsPage(); 8 | } 9 | }, 10 | template: ` 11 | 17 | ` 18 | }; 19 | 20 | export default settingButtons; 21 | -------------------------------------------------------------------------------- /src/assets/js/popup/contents/index.js: -------------------------------------------------------------------------------- 1 | const contents = { 2 | loading: 'Loading...', 3 | pageError: 'Something unexpected happened.', 4 | howToImagePath: 'assets/images/how-to.png', 5 | noHistoryFound: 'No history found. Right click to copy any contents inside <code> and <pre> tags!' 6 | }; 7 | 8 | export default contents; 9 | -------------------------------------------------------------------------------- /src/assets/js/popup/pages/landing.js: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill'; 2 | 3 | import contents from '../contents'; 4 | 5 | import messageBlock from '../components/messageBlock'; 6 | import howTo from '../components/howTo'; 7 | import historyBlock from '../components/historyBlock'; 8 | 9 | import historyService from '../services/history'; 10 | import storageService from '../services/storage'; 11 | 12 | const landing = { 13 | name: 'landing', 14 | components: { 15 | messageBlock, 16 | howTo, 17 | historyBlock 18 | }, 19 | data() { 20 | return { 21 | contents, 22 | pageReady: false, 23 | pageError: false, 24 | copyHistory: [] 25 | }; 26 | }, 27 | methods: { 28 | async getHistory() { 29 | try { 30 | this.copyHistory = await historyService.getAllHistory(); 31 | } catch (err) { 32 | this.pageError = true; 33 | console.log(new Error(err)); 34 | } 35 | 36 | this.pageReady = true; 37 | }, 38 | async deleteHistory(id) { 39 | // Get latest local storage 40 | const latestHistory = await historyService.getAllHistory(); 41 | 42 | // Filter out item with same uuid 43 | const newHistory = latestHistory.filter(r => Object.prototype.hasOwnProperty.call(r, 'id') && r.id !== id); 44 | 45 | // Set local storage and update screen 46 | const newLocalStorage = { 47 | copierHistory: [...newHistory] 48 | }; 49 | 50 | try { 51 | await storageService.setLocalStorage(newLocalStorage); 52 | } catch (err) { 53 | console.log(new Error(err)); 54 | 55 | this.pageError = true; 56 | } 57 | 58 | this.copyHistory = [...newHistory]; 59 | }, 60 | listenStorage() { 61 | // Listen to storage change and refresh list 62 | browser.storage.onChanged.addListener(this.getHistory); 63 | } 64 | }, 65 | mounted() { 66 | this.getHistory(); 67 | this.listenStorage(); 68 | }, 69 | template: ` 70 |
71 | 75 | 76 | 80 | 81 | 86 | 87 |
88 | 96 |
97 |
98 | ` 99 | }; 100 | 101 | export default landing; 102 | -------------------------------------------------------------------------------- /src/assets/js/popup/services/history.js: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill'; 2 | 3 | const getAllHistory = async () => { 4 | const data = await browser.storage.local.get('copierHistory'); 5 | const { copierHistory } = data; 6 | return copierHistory; 7 | }; 8 | 9 | export default { getAllHistory }; 10 | -------------------------------------------------------------------------------- /src/assets/js/popup/services/storage.js: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill'; 2 | 3 | const getLocalStorage = async () => { 4 | const data = await browser.storage.local.get(); 5 | return data; 6 | }; 7 | 8 | const setLocalStorage = async (data) => { 9 | await browser.storage.local.set(data); 10 | }; 11 | 12 | export default { getLocalStorage, setLocalStorage }; 13 | -------------------------------------------------------------------------------- /src/assets/js/utils/format.js: -------------------------------------------------------------------------------- 1 | const zeroPad = (n, x) => { 2 | if (typeof x !== 'number') { 3 | return n; 4 | } 5 | 6 | return (n < 10 ? `${'0'.repeat(x)}${n}` : `${n}`); 7 | }; 8 | 9 | const formatDate = (timestamp) => { 10 | const lastUpdate = new Date(timestamp); 11 | 12 | if (Number.isNaN(lastUpdate.getTime())) { 13 | return undefined; 14 | } 15 | 16 | const year = lastUpdate.getFullYear(); 17 | const month = lastUpdate.getMonth() + 1; 18 | const date = lastUpdate.getDate(); 19 | const hour = lastUpdate.getHours(); 20 | const minute = lastUpdate.getMinutes(); 21 | 22 | return `${year}-${zeroPad(month, 1)}-${zeroPad(date, 1)} ${zeroPad(hour, 1)}:${zeroPad(minute, 1)}`; 23 | }; 24 | 25 | const formatUtils = { zeroPad, formatDate }; 26 | 27 | export default formatUtils; 28 | -------------------------------------------------------------------------------- /src/assets/js/utils/uuid.js: -------------------------------------------------------------------------------- 1 | /* eslint { no-bitwise: "off", no-mixed-operators: "off" } */ 2 | 3 | const uuid = () => { 4 | let d = Date.now(); 5 | 6 | if (typeof performance !== 'undefined' && typeof performance.now === 'function') { 7 | d += performance.now(); 8 | } 9 | 10 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { 11 | const r = (d + Math.random() * 16) % 16 | 0; 12 | d = Math.floor(d / 16); 13 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 14 | }); 15 | }; 16 | 17 | export default uuid; 18 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Code Copier 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Code Copier", 4 | "author": "Ice Lam", 5 | "homepage_url": "https://github.com/icelam/code-copier", 6 | "version": "1.0.2", 7 | "description": "Code Copier - Extension to copy contents inside and
 tags with right click",
 8 |   "icons":
 9 |   {
10 |     "16": "assets/images/icons/icon16.png",
11 |     "32": "assets/images/icons/icon32.png",
12 |     "36": "assets/images/icons/icon36.png",
13 |     "48": "assets/images/icons/icon48.png",
14 |     "128": "assets/images/icons/icon128.png"
15 |   },
16 |   "browser_action": {
17 |     "default_icon": {
18 |       "16": "assets/images/icons/icon16.png",
19 |       "24": "assets/images/icons/icon24.png",
20 |       "32": "assets/images/icons/icon32.png"
21 |     },
22 |     "default_title": "Code Copier",
23 |     "default_popup": "index.html"
24 |   },
25 |   "permissions": [
26 |     "clipboardWrite",
27 |     "storage"
28 |   ],
29 |   "content_scripts": [
30 |   {
31 |     "run_at": "document_end",
32 |     "matches": ["https://*/*", "http://*/*"],
33 |     "js": ["assets/js/content-script.js"],
34 |     "css": ["assets/css/web.css"]
35 |   }],
36 |   "options_ui": {
37 |     "page": "options.html",
38 |     "browser_style": true,
39 |     "chrome_style": true
40 |   },
41 |   "content_security_policy": "default-src 'self'; script-src 'self' blob:; style-src * 'unsafe-inline'; font-src *; object-src 'none'"
42 | }
43 | 


--------------------------------------------------------------------------------
/src/options.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     Code Copier Options
 9 |     
10 |     
11 |     
12 |   
13 |     
14 | 22 |
23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------