├── .nvmrc ├── .eslintignore ├── .gitignore ├── .babelrc ├── src ├── assets │ ├── css │ │ ├── web.scss │ │ ├── popup │ │ │ ├── components │ │ │ │ ├── _content.scss │ │ │ │ ├── _messageBlock.scss │ │ │ │ ├── _howTo.scss │ │ │ │ ├── _header.scss │ │ │ │ ├── _settingsButton.scss │ │ │ │ ├── _tooltip.scss │ │ │ │ ├── _historyBlock.scss │ │ │ │ └── _simplebar.scss │ │ │ ├── _variables.scss │ │ │ ├── _common.scss │ │ │ ├── main.scss │ │ │ ├── _color.scss │ │ │ └── _reset.scss │ │ ├── options.scss │ │ └── code-copier │ │ │ └── _animation.scss │ ├── images │ │ ├── how-to.png │ │ └── icons │ │ │ ├── icon16.png │ │ │ ├── icon24.png │ │ │ ├── icon32.png │ │ │ ├── icon36.png │ │ │ ├── icon48.png │ │ │ ├── icon128.png │ │ │ ├── external-link.svg │ │ │ ├── delete.svg │ │ │ ├── right-click.svg │ │ │ └── settings.svg │ └── js │ │ ├── popup.js │ │ ├── popup │ │ ├── components │ │ │ ├── contentContainer.js │ │ │ ├── messageBlock.js │ │ │ ├── header.js │ │ │ ├── howTo.js │ │ │ ├── settingsButton.js │ │ │ └── historyBlock.js │ │ ├── services │ │ │ ├── history.js │ │ │ └── storage.js │ │ ├── contents │ │ │ └── index.js │ │ ├── App.js │ │ └── pages │ │ │ └── landing.js │ │ ├── utils │ │ ├── uuid.js │ │ └── format.js │ │ ├── options.js │ │ ├── lib │ │ └── code-copier.js │ │ └── content-script.js ├── index.html ├── options.html └── manifest.json ├── design ├── appicon.ai ├── how-to.psd ├── right-click.ai ├── github-repo-thumbnail.ai ├── chrome-promo │ ├── chrome-promo.ai │ ├── flat-browser.ai │ ├── font │ │ └── Nunito.zip │ ├── browser-screenshot.png │ └── extension-screenshot.png ├── icon-small.svg └── icon-large.svg ├── resources ├── appicon.png └── chrome-promo │ ├── large.png │ ├── small.png │ ├── marquee.png │ ├── screenshot-1.png │ └── screenshot-2.png ├── .editorconfig ├── .eslintrc ├── DEVELOPMENT_NOTES.md ├── README.md ├── LICENSE ├── package.json └── gulpfile.babel.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.16.1 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.pem 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/css/web.scss: -------------------------------------------------------------------------------- 1 | @import './code-copier/animation'; 2 | -------------------------------------------------------------------------------- /design/appicon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/appicon.ai -------------------------------------------------------------------------------- /design/how-to.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/how-to.psd -------------------------------------------------------------------------------- /design/right-click.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/right-click.ai -------------------------------------------------------------------------------- /resources/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/resources/appicon.png -------------------------------------------------------------------------------- /src/assets/css/popup/components/_content.scss: -------------------------------------------------------------------------------- 1 | .content { 2 | margin-top: 6.1875em; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/css/options.scss: -------------------------------------------------------------------------------- 1 | textarea { 2 | width: 100%; 3 | min-width: 100%; 4 | margin: 1em 0; 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/images/how-to.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/src/assets/images/how-to.png -------------------------------------------------------------------------------- /design/github-repo-thumbnail.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/github-repo-thumbnail.ai -------------------------------------------------------------------------------- /resources/chrome-promo/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/resources/chrome-promo/large.png -------------------------------------------------------------------------------- /resources/chrome-promo/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/resources/chrome-promo/small.png -------------------------------------------------------------------------------- /resources/chrome-promo/marquee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/resources/chrome-promo/marquee.png -------------------------------------------------------------------------------- /src/assets/images/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/src/assets/images/icons/icon16.png -------------------------------------------------------------------------------- /src/assets/images/icons/icon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/src/assets/images/icons/icon24.png -------------------------------------------------------------------------------- /src/assets/images/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/src/assets/images/icons/icon32.png -------------------------------------------------------------------------------- /src/assets/images/icons/icon36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/src/assets/images/icons/icon36.png -------------------------------------------------------------------------------- /src/assets/images/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/src/assets/images/icons/icon48.png -------------------------------------------------------------------------------- /design/chrome-promo/chrome-promo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/chrome-promo/chrome-promo.ai -------------------------------------------------------------------------------- /design/chrome-promo/flat-browser.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/chrome-promo/flat-browser.ai -------------------------------------------------------------------------------- /design/chrome-promo/font/Nunito.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/chrome-promo/font/Nunito.zip -------------------------------------------------------------------------------- /src/assets/images/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/src/assets/images/icons/icon128.png -------------------------------------------------------------------------------- /resources/chrome-promo/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/resources/chrome-promo/screenshot-1.png -------------------------------------------------------------------------------- /resources/chrome-promo/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/resources/chrome-promo/screenshot-2.png -------------------------------------------------------------------------------- /design/chrome-promo/browser-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/chrome-promo/browser-screenshot.png -------------------------------------------------------------------------------- /design/chrome-promo/extension-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icelam/code-copier/HEAD/design/chrome-promo/extension-screenshot.png -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/images/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/assets/images/icons/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | -------------------------------------------------------------------------------- /src/assets/images/icons/right-click.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 12 | 13 | 15 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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/_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/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/assets/images/icons/settings.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 5 | 
15 | 
16 | 


--------------------------------------------------------------------------------
/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 | 


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


--------------------------------------------------------------------------------
/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/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/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/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 | -------------------------------------------------------------------------------- /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 | 


--------------------------------------------------------------------------------
/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------