├── .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 |
Select and copy code with just one finger click!
7 |` and `` tags with right click.
3 |
4 | 
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 | 
13 | 
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 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/assets/images/icons/right-click.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------